Skip to content

fix: return errors for refused requests (FIPS HMAC forgery) - #87

Closed
MarkAtwood wants to merge 2 commits into
wolfSSL:mainfrom
MarkAtwood:fix-fips-refusal-errors
Closed

MarkAtwood wants to merge 2 commits into
wolfSSL:mainfrom
MarkAtwood:fix-fips-refusal-errors

Conversation

@MarkAtwood

Copy link
Copy Markdown

Security: on FIPS builds, a forged tag verifies for HMAC with a key shorter than 14 bytes.

GnuTLS treats a provider return >= 0 as success. When wolfCrypt refuses a request under HAVE_FIPS, the wrapper returns GNUTLS_FIPS140_OP_NOT_APPROVED, which is a positive enum value (2), so GnuTLS treats the refusal as success:

  • HMAC key shorter than 14 bytes (wc_HmacSetKey returns HMAC_MIN_KEYLEN_E):
    • gnutls_hmac_init() succeeds. The MAC is then computed with all-zero pads, giving SHA256(0^64 || SHA256(0^64 || msg)) for SHA-256. That value doesn't depend on the key, so anyone can compute it.
    • gnutls_hmac_fast() returns 0 and never writes the output.
    • Measured on wolfSSL 5.9.1 FIPS Ready: the keys "Jefe", "Jeff" and "anything" all produce the same MAC (34ab7c26…479f for RFC 4231's message).
  • wc_HKDF_Extract or wc_PBKDF2_ex failure: gnutls_hkdf_extract()/gnutls_pbkdf2() return 2 to the caller, with the output unwritten.
  • RSA key generation failure (e.g. PRIME_GEN_E) or the 1024-bit refusal: GnuTLS keeps a key with no parameters.

The fix returns the same negative error as the non-FIPS path (GNUTLS_E_HASH_FAILED, GNUTLS_E_INTERNAL_ERROR, GNUTLS_E_PK_GENERATION_ERROR), and GNUTLS_E_INVALID_REQUEST for 1024-bit RSA. It also fixes two smaller issues:

  • the Ed25519/Ed448/X25519/X448 key generation curve checks returned GNUTLS_ECC_CURVE_INVALID, which is 0; they now return GNUTLS_E_ECC_UNSUPPORTED_CURVE;
  • wolfssl_hmac_fast() leaked its context when the key was refused; it now frees it.

New test test_fips_refusal_errors: HMAC-SHA256 with the 4-byte key from RFC 4231 test case 2, one-shot and streaming, must either fail with an error or give the RFC value, and two different short keys must not give the same MAC.

Results:

  • wolfSSL 5.9.1 FIPS Ready: the test fails on main and passes with this change. Forced wc_HKDF_Extract/wc_PBKDF2_ex failures and PRIME_GEN_E now return negative errors. Normal-length HMAC/HKDF/PBKDF2 and RSA-2048 results are unchanged and match OpenSSL.
  • wolfSSL master (non-FIPS): make test passes 26/26 with the provider and 26/26 without; run_valgrind is clean.

CI builds wolfSSL master non-FIPS, where short HMAC keys are allowed, so CI can't exercise the FIPS path.

GnuTLS treats a provider return >= 0 as success. Under HAVE_FIPS the
wrapper returned GNUTLS_FIPS140_OP_NOT_APPROVED, a positive enum value,
when wolfCrypt refused a request:

- wc_HmacSetKey (FIPS refuses keys shorter than 14 bytes):
  gnutls_hmac_fast() returned 0 without writing the MAC, and
  gnutls_hmac_init() succeeded with an HMAC whose output does not depend
  on the key: SHA256(0^64 || SHA256(0^64 || msg)) for SHA-256. Anyone
  can compute it, so a forged tag verifies for every short-key HMAC
  user on a FIPS build;
- wc_HKDF_Extract, and wc_PBKDF2_ex with HMAC_MIN_KEYLEN_E:
  gnutls_hkdf_extract()/gnutls_pbkdf2() returned 2 with the output
  unwritten;
- RSA key generation failure and the 1024-bit refusal: the key was
  left without parameters.

Return the negative error the non-FIPS path returns, and
GNUTLS_E_INVALID_REQUEST for 1024-bit RSA. The Ed25519/Ed448/X25519/
X448 key generation curve checks returned GNUTLS_ECC_CURVE_INVALID,
which is 0; return GNUTLS_E_ECC_UNSUPPORTED_CURVE. wolfssl_hmac_fast()
now frees its context when the key is refused.

New test test_fips_refusal_errors: HMAC-SHA256 with a 4-byte key (RFC
4231 test case 2) must fail with an error or give the RFC value, and
two different short keys must not give the same MAC.
Copilot AI balanced review requested due to automatic review settings October 1, 2026 23:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The regression test has incomplete return validation, and several corrected failure paths remain untested.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)
What changed in this PR

Ensures wolfSSL provider refusals propagate as negative GnuTLS errors, preventing FIPS HMAC forgery and invalid key acceptance.

Changes:

  • Corrects HMAC, KDF, RSA, and ECC error handling.
  • Frees failed one-shot HMAC contexts.
  • Adds short-key HMAC regression coverage.
File Description
src/​mac.c Fixes HMAC errors and cleanup.
src/​kdf.c Returns negative KDF failure codes.
src/​pk.c Corrects RSA and curve errors.
tests/​test_fips_refusal_errors.c Tests short-key HMAC refusals.
tests/​Makefile Registers the regression test.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +87 to +91
if (ret == 0 && ret2 == 0 && memcmp(mac, mac2, sizeof(mac)) == 0) {
printf("FAILURE - two different keys gave the same MAC\n");
gnutls_global_deinit();
return 1;
}
@@ -1,4 +1,4 @@
TESTS = test_hash test_shake test_aescbc test_aescfb8 test_aesgcm test_aesccm test_aesxts test_hmac test_cmac test_gmac test_rnd test_rnd_fork test_tls_prf test_hkdf test_pbkdf2 test_ecdsa_sign_and_verify test_ecdh_encrypt_and_decrypt test_eddsa_sign_and_verify test_rsa_sign_and_verify test_rsa_encrypt_and_decrypt test_dh_encrypt_and_decrypt test_pk_import_export test_long_hash test_fips test_aessiv
TESTS = test_hash test_shake test_aescbc test_aescfb8 test_aesgcm test_aesccm test_aesxts test_hmac test_cmac test_gmac test_rnd test_rnd_fork test_tls_prf test_hkdf test_pbkdf2 test_ecdsa_sign_and_verify test_ecdh_encrypt_and_decrypt test_eddsa_sign_and_verify test_rsa_sign_and_verify test_rsa_encrypt_and_decrypt test_dh_encrypt_and_decrypt test_pk_import_export test_long_hash test_fips test_aessiv test_fips_refusal_errors
When wc_InitCmac (in the CMAC setkey, hash and output callbacks) or
wc_GmacSetKey failed, the wrapper gnutls_free()d its context. GnuTLS
owns that context and calls deinit on it afterwards, so it was used
after free and freed twice. A failed CAST makes these calls fail on a
FIPS build. Leave the context to deinit, and have the CMAC/GMAC fast
functions free it when setkey fails (they leaked it on a wrong-size
key).

With wc_InitCmac/wc_GmacSetKey forced to fail, gnutls_hmac_init(
AES-CMAC-128 / AES-GMAC-128) showed invalid reads/writes and a double
free under valgrind; it now returns an error and runs clean.
@MarkAtwood

Copy link
Copy Markdown
Author

Closing in favour of #90, per Reda.

@MarkAtwood MarkAtwood closed this Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants