diff --git a/IDE/WINVS/unit-test/unit-test.vcxproj b/IDE/WINVS/unit-test/unit-test.vcxproj
index 0b7bf991..2466d7c8 100644
--- a/IDE/WINVS/unit-test/unit-test.vcxproj
+++ b/IDE/WINVS/unit-test/unit-test.vcxproj
@@ -31,6 +31,7 @@
+
diff --git a/docs/FIPS_INTEGRATION_GUIDE.md b/docs/FIPS_INTEGRATION_GUIDE.md
index b1818aa1..2613379e 100644
--- a/docs/FIPS_INTEGRATION_GUIDE.md
+++ b/docs/FIPS_INTEGRATION_GUIDE.md
@@ -295,12 +295,32 @@ Always use `--distclean` when switching between production and testing builds:
| Category | Algorithms |
|----------|------------|
-| Digests | MD5, SHA-1 (signing), BLAKE2, SM3 |
+| Digests | MD5, MD5-SHA1, SHA-1 (signing), BLAKE2, SM3 |
| Symmetric | DES, 3DES, ChaCha20, RC4 |
| Asymmetric | RSA < 2048, DSA, Ed25519/Ed448 |
| Key Exchange | X25519/X448, DH (MODP) |
| Curves | P-192 |
+In a default FIPS build, wolfProvider registers MD5, MD5-SHA1, and
+DES-EDE3-CBC (3DES), including their aliases, with `fips=yes`. Fetches and
+metadata queries succeed, but cryptographic use raises an error. These entries
+claim the names for rejection; they do not provide approved cryptographic
+services. The MD5 restriction also applies to wolfProvider KDF paths.
+
+Encrypted-key import is restricted to PBES2/PBKDF2 with AES-CBC and a SHA-based
+PRF. Keys encrypted with DES/3DES, MD5-based password schemes, and traditional
+encrypted PEM are rejected before decryption. Traditional encrypted PEM uses
+MD5 for key derivation even when its cipher is AES.
+
+A successful fetch is therefore not a capability check for these algorithms.
+Configure applications for TLS 1.2 or later and exclude 3DES cipher suites.
+Legacy TLS availability checks can accept the metadata and fail later when
+the handshake attempts to use a denied algorithm.
+
+Defining `WP_ALLOW_NON_FIPS` when building wolfProvider explicitly opts out of
+these restrictions, provided the underlying wolfCrypt algorithms are compiled
+in. This override is disabled by default and permits non-FIPS operations.
+
---
## Troubleshooting
diff --git a/include/wolfprovider/internal.h b/include/wolfprovider/internal.h
index 0b10515c..f1d00821 100644
--- a/include/wolfprovider/internal.h
+++ b/include/wolfprovider/internal.h
@@ -262,6 +262,9 @@ int wp_encrypt_key_pkcs8(WOLFPROV_CTX* provCtx, int cipher,
OSSL_PASSPHRASE_CALLBACK* pwCb, void* pwCbArg);
int wp_decrypt_key_pkcs8(unsigned char* data, word32* len,
OSSL_PASSPHRASE_CALLBACK* pwCb, void* pwCbArg);
+#ifdef WP_FIPS_DENY_LEGACY
+int wp_check_fips_pkcs8(const unsigned char* data, word32 len);
+#endif
int wp_read_der_bio(WOLFPROV_CTX* provCtx, OSSL_CORE_BIO *coreBio, unsigned char** data, word32* len);
int wp_read_pem_bio(WOLFPROV_CTX *provctx, OSSL_CORE_BIO *coreBio,
diff --git a/include/wolfprovider/settings.h b/include/wolfprovider/settings.h
index cdda021c..c1d85529 100644
--- a/include/wolfprovider/settings.h
+++ b/include/wolfprovider/settings.h
@@ -52,6 +52,15 @@
#define WP_HAVE_PKCS8_ENC
#endif
+/* This is the intended FIPS design: claim MD5, MD5-SHA1 and 3DES with
+ * fips=yes, return valid metadata, and fail loudly on attempts to use them.
+ * Do not drop legacy algorithm registration in FIPS builds, even when
+ * wolfCrypt compiles them out. WP_ALLOW_NON_FIPS is the explicit opt-in
+ * override. */
+#if defined(HAVE_FIPS) && !defined(WP_ALLOW_NON_FIPS)
+ #define WP_FIPS_DENY_LEGACY
+#endif
+
#define WP_HAVE_DIGEST
#if !defined(NO_MD5)
#define WP_HAVE_MD5
diff --git a/src/wp_dec_epki2pki.c b/src/wp_dec_epki2pki.c
index 9f204ba4..71759771 100644
--- a/src/wp_dec_epki2pki.c
+++ b/src/wp_dec_epki2pki.c
@@ -280,6 +280,11 @@ static int wp_epki2pki_decode(wp_Epki2Pki* ctx, OSSL_CORE_BIO* coreBio,
if ((!done) && ok && !wp_epki2pki_is_epki(data, len)) {
done = 1;
}
+#ifdef WP_FIPS_DENY_LEGACY
+ if ((!done) && ok && !wp_check_fips_pkcs8(data, len)) {
+ ok = 0;
+ }
+#endif
if ((!done) && ok && ((pwCb == NULL) ||
(!pwCb(password, sizeof(password), &passwordLen, NULL,
pwCbArg)))) {
diff --git a/src/wp_dec_pem2der.c b/src/wp_dec_pem2der.c
index 0d93d4d7..e368af11 100644
--- a/src/wp_dec_pem2der.c
+++ b/src/wp_dec_pem2der.c
@@ -156,6 +156,7 @@ static int wp_pem2der_convert(const char* data, word32 len, DerBuffer** pDer,
const char* footer;
const char* base64Data;
size_t base64Len;
+ size_t i;
WOLFPROV_ENTER(WP_LOG_COMP_PK, "wp_pem2der_convert");
@@ -167,7 +168,14 @@ static int wp_pem2der_convert(const char* data, word32 len, DerBuffer** pDer,
if (ok) {
base64Data = data + 16 + nameLen + 1;
base64Len = len - (16 + nameLen + 1);
- footer = XSTRSTR(base64Data, "-----END ");
+ /* Older wolfSSL builds do not export the XSTRNSTR helper. */
+ footer = NULL;
+ for (i = 0; (base64Len >= 9) && (i <= base64Len - 9); i++) {
+ if (XMEMCMP(base64Data + i, "-----END ", 9) == 0) {
+ footer = base64Data + i;
+ break;
+ }
+ }
if (footer == NULL) {
info->consumed = len;
ok = 0;
@@ -325,7 +333,7 @@ static int wp_pem2der_decode_data(const unsigned char* data, word32 len,
dataType = NULL;
obj = OSSL_OBJECT_PKEY;
-#ifdef WOLFSSL_ENCRYPTED_KEYS
+#if defined(WOLFSSL_ENCRYPTED_KEYS) && !defined(WP_FIPS_DENY_LEGACY)
/* wc_PemToDer decrypts in place through the password callback. */
dataFormat = "PrivateKeyInfo";
info.passwd_cb = wp_pem_password_cb;
@@ -344,6 +352,22 @@ static int wp_pem2der_decode_data(const unsigned char* data, word32 len,
ok = 0;
}
+#ifdef WP_FIPS_DENY_LEGACY
+ /* Traditional encrypted PEM derives its key with MD5, even for AES.
+ * Reject its encryption headers before wc_PemToDer can decrypt it. */
+ if (ok && (obj == OSSL_OBJECT_PKEY)) {
+ word32 i;
+ for (i = 0; i <= len - 8; i++) {
+ if (XMEMCMP(data + i, "DEK-Info", 8) == 0) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_SUPPORTED,
+ "Traditional encrypted PEM is disabled in FIPS mode");
+ ok = 0;
+ break;
+ }
+ }
+ }
+#endif
+
if (ok && !done) {
/* Decode the PEM to DER using wolfSSL. */
rc = wc_PemToDer(data, len, type, &der, NULL, &info, &algoId);
diff --git a/src/wp_des.c b/src/wp_des.c
index be9f8d37..a9afb89b 100644
--- a/src/wp_des.c
+++ b/src/wp_des.c
@@ -30,8 +30,77 @@
#include
-#if defined(WP_HAVE_DES3CBC)
-#if !defined(HAVE_FIPS) || defined(WP_ALLOW_NON_FIPS)
+#if defined(WP_HAVE_DES3CBC) || defined(WP_FIPS_DENY_LEGACY)
+/**
+ * Returns the parameters that can be retrieved.
+ *
+ * @param [in] provCtx wolfProvider context object. Unused.
+ * @return Array of parameters.
+ */
+static const OSSL_PARAM *wp_cipher_gettable_params(void *provCtx)
+{
+ /**
+ * Parameters able to be retrieved for a cipher.
+ */
+ static const OSSL_PARAM cipher_supported_gettable_params[] = {
+ OSSL_PARAM_uint(OSSL_CIPHER_PARAM_MODE, NULL),
+ OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_KEYLEN, NULL),
+ OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_IVLEN, NULL),
+ OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_BLOCK_SIZE, NULL),
+ OSSL_PARAM_END
+ };
+ (void)provCtx;
+ return cipher_supported_gettable_params;
+}
+
+/**
+ * Get the values from the DES3 block context for the parameters.
+ *
+ * @param [in, out] params Array of parameters to retrieve.
+ * @param [in] mode DES3 cipher mode.
+ * @param [in] kBits Number of bits in key.
+ * @param [in] ivBits Number of bits in IV.
+ * @return 1 on success.
+ * @return 0 on failure.
+ */
+static int wp_des3_block_get_params(OSSL_PARAM params[], unsigned int mode,
+ size_t kBits, size_t ivBits)
+{
+ int ok = 1;
+ OSSL_PARAM *p;
+
+ WOLFPROV_ENTER(WP_LOG_COMP_DES, "wp_des3_block_get_params");
+
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_MODE);
+ if ((p != NULL) && (!OSSL_PARAM_set_uint(p, mode))) {
+ ok = 0;
+ }
+ if (ok) {
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_KEYLEN);
+ if ((p != NULL) && (!OSSL_PARAM_set_size_t(p, kBits / 8))) {
+ ok = 0;
+ }
+ }
+ if (ok) {
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_BLOCK_SIZE);
+ /* This metadata is also needed when wolfCrypt has NO_DES3 set. */
+ if ((p != NULL) && (!OSSL_PARAM_set_size_t(p, 8))) {
+ ok = 0;
+ }
+ }
+ if (ok) {
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_IVLEN);
+ if ((p != NULL) && (!OSSL_PARAM_set_size_t(p, ivBits / 8))) {
+ ok = 0;
+ }
+ }
+
+ WOLFPROV_LEAVE(WP_LOG_COMP_DES,
+ __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
+ return ok;
+}
+
+#ifndef WP_FIPS_DENY_LEGACY
/**
* Data structure for DES3 ciphers that are block based.
*/
@@ -105,74 +174,6 @@ static void *wp_des3_block_dupctx(wp_Des3BlockCtx *src)
return dst;
}
-/**
- * Returns the parameters that can be retrieved.
- *
- * @param [in] provCtx wolfProvider context object. Unused.
- * @return Array of parameters.
- */
-static const OSSL_PARAM *wp_cipher_gettable_params(
- WOLFPROV_CTX *provCtx)
-{
- /**
- * Parameters able to be retrieved for a cipher.
- */
- static const OSSL_PARAM cipher_supported_gettable_params[] = {
- OSSL_PARAM_uint(OSSL_CIPHER_PARAM_MODE, NULL),
- OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_KEYLEN, NULL),
- OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_IVLEN, NULL),
- OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_BLOCK_SIZE, NULL),
- OSSL_PARAM_END
- };
- (void)provCtx;
- return cipher_supported_gettable_params;
-}
-
-/**
- * Get the values from the DES3 block context for the parameters.
- *
- * @param [in, out] params Array of parameters to retrieve.
- * @param [in] mode DES3 cipher mode.
- * @param [in] kBits Number of bits in key.
- * @param [in] ivBits Number of bits in IV.
- * @return 1 on success.
- * @return 0 on failure.
- */
-static int wp_des3_block_get_params(OSSL_PARAM params[], unsigned int mode,
- size_t kBits, size_t ivBits)
-{
- int ok = 1;
- OSSL_PARAM *p;
-
- WOLFPROV_ENTER(WP_LOG_COMP_DES, "wp_des3_block_get_params");
-
- p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_MODE);
- if ((p != NULL) && (!OSSL_PARAM_set_uint(p, mode))) {
- ok = 0;
- }
- if (ok) {
- p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_KEYLEN);
- if ((p != NULL) && (!OSSL_PARAM_set_size_t(p, kBits / 8))) {
- ok = 0;
- }
- }
- if (ok) {
- p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_BLOCK_SIZE);
- if ((p != NULL) && (!OSSL_PARAM_set_size_t(p, DES_BLOCK_SIZE))) {
- ok = 0;
- }
- }
- if (ok) {
- p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_IVLEN);
- if ((p != NULL) && (!OSSL_PARAM_set_size_t(p, ivBits / 8))) {
- ok = 0;
- }
- }
-
- WOLFPROV_LEAVE(WP_LOG_COMP_DES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
- return ok;
-}
-
/**
* Returns the parameters of a cipher context that can be retrieved.
*
@@ -914,32 +915,84 @@ IMPLEMENT_DES3_BLOCK_DISPATCH(lcmode, kBits, ivBits)
/** wp_des3cbc_functions_functions */
IMPLEMENT_DES3_BLOCK(cbc, CBC, 192, 64)
-#else /* defined(HAVE_FIPS) && !defined(WP_ALLOW_NON_FIPS */
+#else /* WP_FIPS_DENY_LEGACY */
-#define IMPLEMENT_DES3_BLOCK_NULL(mode) \
-const OSSL_DISPATCH wp_des3##mode##_functions[] = { \
- { OSSL_FUNC_CIPHER_NEWCTX, (DFUNC)wp_des3_null }, \
- { OSSL_FUNC_CIPHER_FREECTX, (DFUNC)wp_des3_void }, \
- { OSSL_FUNC_CIPHER_DUPCTX, (DFUNC)wp_des3_null }, \
- { OSSL_FUNC_CIPHER_ENCRYPT_INIT, (DFUNC)wp_des3_null }, \
- { OSSL_FUNC_CIPHER_DECRYPT_INIT, (DFUNC)wp_des3_null }, \
- { OSSL_FUNC_CIPHER_UPDATE, (DFUNC)wp_des3_null }, \
- { OSSL_FUNC_CIPHER_FINAL, (DFUNC)wp_des3_null }, \
- { OSSL_FUNC_CIPHER_CIPHER, (DFUNC)wp_des3_null }, \
- { OSSL_FUNC_CIPHER_GET_PARAMS, (DFUNC)wp_des3_null }, \
- { OSSL_FUNC_CIPHER_GET_CTX_PARAMS, (DFUNC)wp_des3_null }, \
- { OSSL_FUNC_CIPHER_SET_CTX_PARAMS, (DFUNC)wp_des3_null }, \
- { OSSL_FUNC_CIPHER_GETTABLE_PARAMS, (DFUNC)wp_des3_null }, \
- { OSSL_FUNC_CIPHER_GETTABLE_CTX_PARAMS, \
- (DFUNC)wp_des3_null }, \
- { OSSL_FUNC_CIPHER_SETTABLE_CTX_PARAMS, \
- (DFUNC)wp_des3_null }, \
- { 0, NULL } \
-};
-static int wp_des3_null(void) { return 0; }
-static void wp_des3_void(void) {}
+static int wp_des3cbc_get_params(OSSL_PARAM params[])
+{
+ return wp_des3_block_get_params(params, EVP_CIPH_CBC_MODE, 192, 64);
+}
+
+static int wp_des3_denied(void)
+{
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_SUPPORTED,
+ "3DES is disabled in FIPS mode");
+ return 0;
+}
+
+static void* wp_des3_denied_newctx(void* provCtx)
+{
+ (void)provCtx;
+ wp_des3_denied();
+ return NULL;
+}
-IMPLEMENT_DES3_BLOCK_NULL(cbc)
+static void wp_des3_denied_freectx(void* ctx)
+{
+ (void)ctx;
+}
+
+static int wp_des3_denied_init(void* ctx, const unsigned char* key,
+ size_t keyLen, const unsigned char* iv, size_t ivLen,
+ const OSSL_PARAM params[])
+{
+ (void)ctx;
+ (void)key;
+ (void)keyLen;
+ (void)iv;
+ (void)ivLen;
+ (void)params;
+ return wp_des3_denied();
+}
+
+static int wp_des3_denied_update(void* ctx, unsigned char* out, size_t* outLen,
+ size_t outSize, const unsigned char* in, size_t inLen)
+{
+ (void)ctx;
+ (void)out;
+ (void)outSize;
+ (void)in;
+ (void)inLen;
+ if (outLen != NULL) {
+ *outLen = 0;
+ }
+ return wp_des3_denied();
+}
+
+static int wp_des3_denied_final(void* ctx, unsigned char* out, size_t* outLen,
+ size_t outSize)
+{
+ (void)ctx;
+ (void)out;
+ (void)outSize;
+ if (outLen != NULL) {
+ *outLen = 0;
+ }
+ return wp_des3_denied();
+}
+
+const OSSL_DISPATCH wp_des3cbc_functions[] = {
+ { OSSL_FUNC_CIPHER_NEWCTX, (DFUNC)wp_des3_denied_newctx },
+ { OSSL_FUNC_CIPHER_FREECTX, (DFUNC)wp_des3_denied_freectx },
+ { OSSL_FUNC_CIPHER_DUPCTX, (DFUNC)wp_des3_denied_newctx },
+ { OSSL_FUNC_CIPHER_ENCRYPT_INIT, (DFUNC)wp_des3_denied_init },
+ { OSSL_FUNC_CIPHER_DECRYPT_INIT, (DFUNC)wp_des3_denied_init },
+ { OSSL_FUNC_CIPHER_UPDATE, (DFUNC)wp_des3_denied_update },
+ { OSSL_FUNC_CIPHER_FINAL, (DFUNC)wp_des3_denied_final },
+ { OSSL_FUNC_CIPHER_CIPHER, (DFUNC)wp_des3_denied_update },
+ { OSSL_FUNC_CIPHER_GET_PARAMS, (DFUNC)wp_des3cbc_get_params },
+ { OSSL_FUNC_CIPHER_GETTABLE_PARAMS, (DFUNC)wp_cipher_gettable_params },
+ { 0, NULL }
+};
#endif
-#endif /* WP_HAVE_DES3CBC */
+#endif /* WP_HAVE_DES3CBC || WP_FIPS_DENY_LEGACY */
diff --git a/src/wp_digests.c b/src/wp_digests.c
index fb027688..7451e0cb 100644
--- a/src/wp_digests.c
+++ b/src/wp_digests.c
@@ -231,21 +231,69 @@ const OSSL_DISPATCH name##_functions[] = { \
{ 0, NULL } \
};
-#define IMPLEMENT_DIGEST_NULL(name) \
-/** Dispatch table for digest_null algorithms. */ \
-static int name##_digest_null(void) { return 0; } \
-static void name##_digest_void(void) {} \
-const OSSL_DISPATCH name##_functions[] = { \
- { OSSL_FUNC_DIGEST_NEWCTX, (DFUNC)name##_digest_null }, \
- { OSSL_FUNC_DIGEST_INIT, (DFUNC)name##_digest_null }, \
- { OSSL_FUNC_DIGEST_UPDATE, (DFUNC)name##_digest_null }, \
- { OSSL_FUNC_DIGEST_FINAL, (DFUNC)name##_digest_null }, \
- { OSSL_FUNC_DIGEST_FREECTX, (DFUNC)name##_digest_void }, \
- { OSSL_FUNC_DIGEST_DUPCTX, (DFUNC)name##_digest_null }, \
- { OSSL_FUNC_DIGEST_GET_PARAMS, (DFUNC)name##_digest_null }, \
- { OSSL_FUNC_DIGEST_GETTABLE_PARAMS, (DFUNC)name##_digest_null }, \
- { 0, NULL } \
+#ifdef WP_FIPS_DENY_LEGACY
+static int wp_digest_denied(void)
+{
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_SUPPORTED,
+ "MD5-based digests are disabled in FIPS mode");
+ return 0;
+}
+
+static void* wp_digest_denied_newctx(void* provCtx)
+{
+ (void)provCtx;
+ wp_digest_denied();
+ return NULL;
+}
+
+static void wp_digest_denied_freectx(void* ctx)
+{
+ (void)ctx;
+}
+
+static int wp_digest_denied_init(void* ctx, const OSSL_PARAM params[])
+{
+ (void)ctx;
+ (void)params;
+ return wp_digest_denied();
+}
+
+static int wp_digest_denied_update(void* ctx, const unsigned char* in,
+ size_t inLen)
+{
+ (void)ctx;
+ (void)in;
+ (void)inLen;
+ return wp_digest_denied();
+}
+
+static int wp_digest_denied_final(void* ctx, unsigned char* out,
+ size_t* outLen, size_t outSize)
+{
+ (void)ctx;
+ (void)out;
+ (void)outSize;
+ if (outLen != NULL) {
+ *outLen = 0;
+ }
+ return wp_digest_denied();
+}
+
+/* Fetching describes the method; creating or using a context is forbidden. */
+#define IMPLEMENT_DIGEST_DENIED(name, blkSize, dgstSize) \
+IMPLEMENT_DIGEST_GET_PARAM(name, blkSize, dgstSize, 0) \
+const OSSL_DISPATCH name##_functions[] = { \
+ { OSSL_FUNC_DIGEST_NEWCTX, (DFUNC)wp_digest_denied_newctx }, \
+ { OSSL_FUNC_DIGEST_INIT, (DFUNC)wp_digest_denied_init }, \
+ { OSSL_FUNC_DIGEST_UPDATE, (DFUNC)wp_digest_denied_update }, \
+ { OSSL_FUNC_DIGEST_FINAL, (DFUNC)wp_digest_denied_final }, \
+ { OSSL_FUNC_DIGEST_FREECTX, (DFUNC)wp_digest_denied_freectx }, \
+ { OSSL_FUNC_DIGEST_DUPCTX, (DFUNC)wp_digest_denied_newctx }, \
+ { OSSL_FUNC_DIGEST_GET_PARAMS, (DFUNC)name##_get_params }, \
+ { OSSL_FUNC_DIGEST_GETTABLE_PARAMS, (DFUNC)wp_digest_gettable_params }, \
+ { 0, NULL } \
};
+#endif /* WP_FIPS_DENY_LEGACY */
/**
* Get parameters of a digest algorithm.
@@ -319,9 +367,10 @@ static const OSSL_PARAM* wp_digest_gettable_params(void* provCtx)
* MD5
******************************************************************************/
-#ifdef WP_HAVE_MD5
-#if defined(HAVE_FIPS) && !defined(WP_ALLOW_NON_FIPS)
-IMPLEMENT_DIGEST_NULL(wp_md5)
+#if defined(WP_HAVE_MD5) || defined(WP_FIPS_DENY_LEGACY)
+#ifdef WP_FIPS_DENY_LEGACY
+/* Metadata must not depend on wolfCrypt's MD5 implementation being present. */
+IMPLEMENT_DIGEST_DENIED(wp_md5, 64, 16)
#else
IMPLEMENT_DIGEST(wp_md5, wc_Md5,
WC_MD5_BLOCK_SIZE, WC_MD5_DIGEST_SIZE,
@@ -335,8 +384,8 @@ IMPLEMENT_DIGEST(wp_md5, wc_Md5,
* SHA1-MD5
******************************************************************************/
-#ifdef WP_HAVE_MD5_SHA1
-#if !defined(HAVE_FIPS) || defined(WP_ALLOW_NON_FIPS)
+#if defined(WP_HAVE_MD5_SHA1) || defined(WP_FIPS_DENY_LEGACY)
+#ifndef WP_FIPS_DENY_LEGACY
/**
* Combined MD5 and SHA-1 digest.
*/
@@ -472,8 +521,8 @@ IMPLEMENT_DIGEST(wp_md5_sha1, wp_Md5Sha,
0,
wp_InitMd5Sha_ex, wp_Md5ShaUpdate, wp_Md5ShaFinal,
wp_Md5ShaCopy, wp_Md5ShaFree)
-#else /* defined(HAVE_FIPS) && !defined(WP_ALLOW_NON_FIPS) */
-IMPLEMENT_DIGEST_NULL(wp_md5_sha1)
+#else /* WP_FIPS_DENY_LEGACY */
+IMPLEMENT_DIGEST_DENIED(wp_md5_sha1, 64, 36)
#endif
#endif
diff --git a/src/wp_internal.c b/src/wp_internal.c
index d4bb7e3c..d6f376ed 100644
--- a/src/wp_internal.c
+++ b/src/wp_internal.c
@@ -19,10 +19,12 @@
*/
+#include
#include
#include
#include
#include
+#include
#include
#include
@@ -578,6 +580,16 @@ enum wc_HashType wp_nid_to_wc_hash_type(int nid)
{
enum wc_HashType hashType = WC_HASH_TYPE_NONE;
+#ifdef WP_FIPS_DENY_LEGACY
+ /* A fetch can succeed for a denial method. KDFs and MACs calling wolfCrypt
+ * directly must not treat that metadata as permission to use it. */
+ if ((nid == NID_md5) || (nid == NID_md5_sha1)) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_SUPPORTED,
+ "MD5-based digests are disabled in FIPS mode");
+ nid = NID_undef;
+ }
+#endif
+
switch (nid) {
case NID_md5:
hashType = WC_HASH_TYPE_MD5;
@@ -1217,6 +1229,70 @@ int wp_encrypt_key_pkcs8(WOLFPROV_CTX* provCtx, int cipher,
#endif
}
+#ifdef WP_FIPS_DENY_LEGACY
+/**
+ * Check encryption metadata before wolfCrypt can decrypt a key directly.
+ * Allow PBES2/PBKDF2 with AES-CBC and a SHA-based PRF.
+ *
+ * @param [in] data DER-encoded EncryptedPrivateKeyInfo.
+ * @param [in] len Length of the DER encoding in bytes.
+ * @return 1 when the encryption scheme is allowed, or 0 when rejected.
+ */
+int wp_check_fips_pkcs8(const unsigned char* data, word32 len)
+{
+ const unsigned char* p = data;
+ const X509_ALGOR* alg = NULL;
+ X509_SIG* epki = NULL;
+ PBE2PARAM* pbe = NULL;
+ PBKDF2PARAM* kdf = NULL;
+ int nid, ok = 0;
+
+ WOLFPROV_ENTER(WP_LOG_COMP_PROVIDER, "wp_check_fips_pkcs8");
+
+ if ((data != NULL) && (len <= INT_MAX)) {
+ epki = d2i_X509_SIG(NULL, &p, (long)len);
+ }
+ if ((epki != NULL) && (p == data + len)) {
+ X509_SIG_get0(epki, &alg, NULL);
+ if ((OBJ_obj2nid(alg->algorithm) == NID_pbes2) &&
+ (alg->parameter != NULL)) {
+ pbe = ASN1_TYPE_unpack_sequence(ASN1_ITEM_rptr(PBE2PARAM),
+ alg->parameter);
+ }
+ }
+ if ((pbe != NULL) &&
+ (OBJ_obj2nid(pbe->keyfunc->algorithm) == NID_id_pbkdf2) &&
+ (pbe->keyfunc->parameter != NULL)) {
+ nid = OBJ_obj2nid(pbe->encryption->algorithm);
+ if ((nid == NID_aes_128_cbc) || (nid == NID_aes_192_cbc) ||
+ (nid == NID_aes_256_cbc)) {
+ kdf = ASN1_TYPE_unpack_sequence(ASN1_ITEM_rptr(PBKDF2PARAM),
+ pbe->keyfunc->parameter);
+ }
+ }
+ if (kdf != NULL) {
+ /* Omitted PBKDF2 PRF means HMAC-SHA1. */
+ nid = (kdf->prf == NULL) ? NID_hmacWithSHA1 :
+ OBJ_obj2nid(kdf->prf->algorithm);
+ ok = (nid == NID_hmacWithSHA1) || (nid == NID_hmacWithSHA224) ||
+ (nid == NID_hmacWithSHA256) || (nid == NID_hmacWithSHA384) ||
+ (nid == NID_hmacWithSHA512) || (nid == NID_hmacWithSHA512_224) ||
+ (nid == NID_hmacWithSHA512_256);
+ }
+ PBKDF2PARAM_free(kdf);
+ PBE2PARAM_free(pbe);
+ X509_SIG_free(epki);
+ if (!ok) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_SUPPORTED,
+ "Encrypted keys require PBES2/PBKDF2 with AES-CBC and a SHA PRF "
+ "in FIPS mode");
+ }
+ WOLFPROV_LEAVE(WP_LOG_COMP_PROVIDER,
+ __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
+ return ok;
+}
+#endif
+
#ifdef WP_HAVE_PKCS8_ENC
/* DER encoding of the PBKDF2 OID (1.2.840.113549.1.5.12). */
static const unsigned char wp_pbkdf2_oid[] = {
@@ -1275,6 +1351,11 @@ int wp_decrypt_key_pkcs8(unsigned char* data, word32* len,
if ((data == NULL) || (!wp_is_pbkdf2_encrypted(data, *len))) {
ok = 0;
}
+#ifdef WP_FIPS_DENY_LEGACY
+ if (ok && !wp_check_fips_pkcs8(data, *len)) {
+ ok = 0;
+ }
+#endif
#ifdef WOLFSSL_SMALL_STACK
if (ok) {
password = (char*)XMALLOC(WP_EPKI_PASSWORD_MAX, NULL,
diff --git a/src/wp_wolfprov.c b/src/wp_wolfprov.c
index 69446838..12d898ad 100644
--- a/src/wp_wolfprov.c
+++ b/src/wp_wolfprov.c
@@ -404,12 +404,12 @@ static int wolfprov_get_params(void* provCtx, OSSL_PARAM params[])
/* List of digest algorithm implementations available in wolfSSL provider. */
static const OSSL_ALGORITHM wolfprov_digests[] = {
-#ifdef WP_HAVE_MD5
+#if defined(WP_HAVE_MD5) || defined(WP_FIPS_DENY_LEGACY)
/* MD5 */
{ WP_NAMES_MD5, WOLFPROV_PROPERTIES, wp_md5_functions,
"" },
#endif
-#ifdef WP_HAVE_MD5_SHA1
+#if defined(WP_HAVE_MD5_SHA1) || defined(WP_FIPS_DENY_LEGACY)
/* MD5-SHA1 */
{ WP_NAMES_MD5_SHA1, WOLFPROV_PROPERTIES, wp_md5_sha1_functions,
"" },
@@ -551,7 +551,7 @@ static const OSSL_ALGORITHM wolfprov_ciphers[] = {
"" },
#endif
-#ifdef WP_HAVE_DES3CBC
+#if defined(WP_HAVE_DES3CBC) || defined(WP_FIPS_DENY_LEGACY)
{ WP_NAMES_DES_EDE3_CBC, WOLFPROV_PROPERTIES, wp_des3cbc_functions,
"" },
#endif
diff --git a/test/include.am b/test/include.am
index cab0700f..bfedbddc 100644
--- a/test/include.am
+++ b/test/include.am
@@ -21,6 +21,7 @@ test_unit_test_SOURCES = \
test/test_rand_seed.c \
test/test_ecc.c \
test/test_ecx.c \
+ test/test_fips_policy.c \
test/test_gmac.c \
test/test_krb5kdf.c \
test/test_sshkdf.c \
diff --git a/test/test_ecc.c b/test/test_ecc.c
index 87003a73..7607859c 100644
--- a/test/test_ecc.c
+++ b/test/test_ecc.c
@@ -3661,36 +3661,40 @@ static int test_ec_derive_off_curve_peer(void)
#endif /* WP_HAVE_ECDH */
/*
- * A truncated PEM (header only) must be rejected without an out-of-bounds read
- * in wp_pem2der_convert (base64Data past the buffer / base64Len underflow).
+ * Reject truncated PEM without reading past the input. The longer cases
+ * reach the footer search in wp_pem2der_convert rather than the header guard.
*/
int test_ec_decode_short_pem(void *data)
{
int err = 0;
- OSSL_DECODER_CTX *dctx = NULL;
- EVP_PKEY *pkey = NULL;
- static const char shortPem[] = "-----BEGIN EC PARAMETERS-----\n";
- const unsigned char *p = (const unsigned char *)shortPem;
- size_t pLen = sizeof(shortPem) - 1;
+ static const char* shortPem[] = {
+ "-----BEGIN EC PARAMETERS-----\n",
+ "-----BEGIN EC PARAMETERS-----\nBggqhkjOPQMBBw==\n",
+ "-----BEGIN EC PARAMETERS-----\nBggqhkjOPQMBBw==\n-----END"
+ };
+ size_t i;
(void)data;
- dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "PEM", NULL, "EC",
- EVP_PKEY_KEY_PARAMETERS, wpLibCtx, NULL);
- if (dctx == NULL) {
- err = 1;
- }
- if (err == 0) {
- /* Truncated PEM simply fails to decode; must not read out of bounds. */
- (void)OSSL_DECODER_from_data(dctx, &p, &pLen);
- if (pkey != NULL) {
- PRINT_ERR_MSG("Truncated PEM unexpectedly produced a key");
+ for (i = 0; !err && (i < sizeof(shortPem) / sizeof(shortPem[0])); i++) {
+ OSSL_DECODER_CTX* dctx;
+ EVP_PKEY* pkey = NULL;
+ const unsigned char* p = (const unsigned char*)shortPem[i];
+ size_t pLen = XSTRLEN(shortPem[i]);
+
+ ERR_clear_error();
+ dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "PEM", NULL, "EC",
+ EVP_PKEY_KEY_PARAMETERS, wpLibCtx, "provider=wolfprov");
+ err = (dctx == NULL);
+ if (!err && ((OSSL_DECODER_from_data(dctx, &p, &pLen) != 0) ||
+ (pkey != NULL))) {
+ PRINT_ERR_MSG("Truncated PEM unexpectedly decoded");
err = 1;
}
+ OSSL_DECODER_CTX_free(dctx);
+ EVP_PKEY_free(pkey);
+ ERR_clear_error();
}
-
- OSSL_DECODER_CTX_free(dctx);
- EVP_PKEY_free(pkey);
return err;
}
diff --git a/test/test_fips_policy.c b/test/test_fips_policy.c
new file mode 100644
index 00000000..17d0862b
--- /dev/null
+++ b/test/test_fips_policy.c
@@ -0,0 +1,349 @@
+/* test_fips_policy.c
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfProvider.
+ *
+ * wolfProvider is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfProvider is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wolfProvider. If not, see .
+ */
+
+#include "unit.h"
+#include
+#include
+#include
+#include
+#include
+
+#ifdef WP_FIPS_DENY_LEGACY
+
+/* Run first: a successful fetch must not leave errors from other algorithms
+ * being constructed, and denial methods must survive NO_MD5/NO_DES3 builds. */
+int test_fips_legacy_algorithms(void* data)
+{
+ static const char* digests[] = {
+ "MD5", "SSL3-MD5", "1.2.840.113549.2.5", "MD5-SHA1"
+ };
+ static const char* ciphers[] = {
+ "DES-EDE3-CBC", "DES3", "1.2.840.113549.3.7"
+ };
+ EVP_MD* md;
+ EVP_CIPHER* cipher;
+ unsigned char out[EVP_MAX_MD_SIZE], key[24], iv[8] = {0};
+ unsigned int outLen;
+ size_t i;
+ int enc, err = 0;
+
+ (void)data;
+ ERR_clear_error();
+ md = EVP_MD_fetch(wpLibCtx, "SHA256", "provider=wolfprov");
+ if ((md == NULL) || (ERR_peek_error() != 0)) {
+ PRINT_ERR_MSG("FIPS digest discovery left an error");
+ err = 1;
+ }
+ EVP_MD_free(md);
+#ifdef WP_HAVE_AESCBC
+ cipher = EVP_CIPHER_fetch(wpLibCtx, "AES-128-CBC", "provider=wolfprov");
+ if ((cipher == NULL) || (ERR_peek_error() != 0)) {
+ PRINT_ERR_MSG("FIPS cipher discovery left an error");
+ err = 1;
+ }
+ EVP_CIPHER_free(cipher);
+#endif
+
+ for (i = 0; !err && (i < sizeof(digests) / sizeof(digests[0])); i++) {
+ EVP_MD_CTX* ctx = EVP_MD_CTX_new();
+ md = EVP_MD_fetch(wpLibCtx, digests[i], "provider=wolfprov");
+ if ((ctx == NULL) || (md == NULL) || (ERR_peek_error() != 0) ||
+ (EVP_MD_get0_provider(md) != wpProv) ||
+ (EVP_MD_get_block_size(md) != 64) ||
+ (EVP_MD_get_size(md) != ((i == 3) ? 36 : 16)) ||
+ (EVP_MD_gettable_params(md) == NULL)) {
+ PRINT_ERR_MSG("Invalid denial metadata: %s", digests[i]);
+ err = 1;
+ }
+ if (!err && ((EVP_DigestInit_ex2(ctx, md, NULL) != 0) ||
+ (ERR_peek_error() == 0))) {
+ PRINT_ERR_MSG("FIPS digest initialization was not rejected: %s",
+ digests[i]);
+ err = 1;
+ }
+ ERR_clear_error();
+ if (!err && ((EVP_Digest("abc", 3, out, &outLen, md, NULL) != 0) ||
+ (ERR_peek_error() == 0))) {
+ PRINT_ERR_MSG("FIPS digest operation was not rejected: %s",
+ digests[i]);
+ err = 1;
+ }
+ ERR_clear_error();
+ EVP_MD_CTX_free(ctx);
+ EVP_MD_free(md);
+ }
+
+ for (i = 0; i < sizeof(key); i++) {
+ key[i] = (unsigned char)(i + 1);
+ }
+ for (i = 0; !err && (i < sizeof(ciphers) / sizeof(ciphers[0])); i++) {
+ cipher = EVP_CIPHER_fetch(wpLibCtx, ciphers[i], "provider=wolfprov");
+ if ((cipher == NULL) || (ERR_peek_error() != 0) ||
+ (EVP_CIPHER_get0_provider(cipher) != wpProv) ||
+ (EVP_CIPHER_get_key_length(cipher) != 24) ||
+ (EVP_CIPHER_get_iv_length(cipher) != 8) ||
+ (EVP_CIPHER_get_block_size(cipher) != 8) ||
+ (EVP_CIPHER_gettable_params(cipher) == NULL)) {
+ PRINT_ERR_MSG("Invalid denial metadata: %s", ciphers[i]);
+ err = 1;
+ }
+ for (enc = 0; !err && (enc <= 1); enc++) {
+ EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
+ if ((ctx == NULL) ||
+ (EVP_CipherInit_ex2(ctx, cipher, key, iv, enc, NULL) != 0) ||
+ (ERR_peek_error() == 0)) {
+ PRINT_ERR_MSG("FIPS cipher initialization was not rejected: %s",
+ ciphers[i]);
+ err = 1;
+ }
+ ERR_clear_error();
+ EVP_CIPHER_CTX_free(ctx);
+ }
+ EVP_CIPHER_free(cipher);
+ }
+ ERR_clear_error();
+ return err;
+}
+
+#if defined(WP_HAVE_HKDF) || defined(WP_HAVE_PBE) || defined(WP_HAVE_TLS1_PRF)
+/* These KDFs call wolfCrypt directly after resolving a digest name. A denial
+ * method's successful fetch must never enable the underlying MD5 operation. */
+static int test_fips_kdf_digest(const char* name, const char* deniedDigest)
+{
+ EVP_KDF* kdf = EVP_KDF_fetch(wpLibCtx, name, "provider=wolfprov");
+ const char* digests[] = { "SHA256", NULL };
+ char properties[] = "provider=wolfprov";
+ unsigned char key[64] = {0}, salt[16] = {0}, out[32];
+ uint64_t iterations = 1000;
+ int i, err = 0;
+
+ digests[1] = deniedDigest;
+ if ((kdf == NULL) || (EVP_KDF_get0_provider(kdf) != wpProv)) {
+ PRINT_ERR_MSG("Failed to fetch FIPS KDF: %s", name);
+ err = 1;
+ }
+ for (i = 0; !err && (i < 2); i++) {
+ EVP_KDF_CTX* ctx = EVP_KDF_CTX_new(kdf);
+ OSSL_PARAM params[6];
+ size_t n = 0;
+ params[n++] = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST,
+ (char*)digests[i], 0);
+ params[n++] = OSSL_PARAM_construct_utf8_string(
+ OSSL_KDF_PARAM_PROPERTIES, properties, 0);
+ if (strcmp(name, "PBKDF2") == 0) {
+ params[n++] = OSSL_PARAM_construct_octet_string(
+ OSSL_KDF_PARAM_PASSWORD, key, sizeof(key));
+ params[n++] = OSSL_PARAM_construct_uint64(OSSL_KDF_PARAM_ITER,
+ &iterations);
+ }
+ else {
+ params[n++] = OSSL_PARAM_construct_octet_string(
+ (strcmp(name, "TLS1-PRF") == 0) ? OSSL_KDF_PARAM_SECRET :
+ OSSL_KDF_PARAM_KEY, key, sizeof(key));
+ }
+ params[n++] = OSSL_PARAM_construct_octet_string(
+ (strcmp(name, "TLS1-PRF") == 0) ? OSSL_KDF_PARAM_SEED :
+ OSSL_KDF_PARAM_SALT, salt, sizeof(salt));
+ params[n] = OSSL_PARAM_construct_end();
+ ERR_clear_error();
+ if ((ctx == NULL) ||
+ (EVP_KDF_derive(ctx, out, sizeof(out), params) != (i == 0)) ||
+ ((i == 1) && (ERR_peek_error() == 0))) {
+ PRINT_ERR_MSG("Incorrect FIPS KDF policy: %s / %s", name,
+ digests[i]);
+ err = 1;
+ }
+ ERR_clear_error();
+ EVP_KDF_CTX_free(ctx);
+ }
+ EVP_KDF_free(kdf);
+ return err;
+}
+#endif
+
+int test_fips_legacy_kdfs(void* data)
+{
+ int err = 0;
+ (void)data;
+#ifdef WP_HAVE_HKDF
+ err = test_fips_kdf_digest("HKDF", "MD5");
+#endif
+#ifdef WP_HAVE_PBE
+ if (!err) {
+ err = test_fips_kdf_digest("PBKDF2", "MD5");
+ }
+#endif
+#ifdef WP_HAVE_TLS1_PRF
+ if (!err) {
+ err = test_fips_kdf_digest("TLS1-PRF", "MD5-SHA1");
+ }
+#endif
+ return err;
+}
+
+/* Use real encrypted keys to exercise the cipher, PRF and PEM restrictions. */
+int test_fips_legacy_key_decrypt(void* data)
+{
+ int err = 0;
+#ifdef WP_HAVE_RSA
+ static const struct {
+ const char* cipher;
+ int pbe, prf, allowed, traditional;
+ } cases[] = {
+ { "AES-128-CBC", -1, NID_hmacWithSHA256, 1, 0 },
+ { "AES-192-CBC", -1, NID_hmacWithSHA256, 1, 0 },
+ { "AES-256-CBC", -1, NID_hmacWithSHA256, 1, 0 },
+ /* SHA1 is the default PBKDF2 PRF and is omitted from the DER. */
+ { "AES-256-CBC", -1, NID_hmacWithSHA1, 1, 0 },
+#ifndef OPENSSL_NO_MD5
+ { "AES-256-CBC", -1, NID_hmacWithMD5, 0, 0 },
+ { "AES-256-CBC", -1, NID_undef, 0, 1 },
+#endif
+#ifndef OPENSSL_NO_DES
+ { "DES-EDE3-CBC", -1, NID_hmacWithSHA256, 0, 0 },
+ { "DES-EDE3-CBC", NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
+ NID_undef, 0, 0 },
+#endif
+ };
+ BIO* input = BIO_new_file(CERTS_DIR "/server-key.pem", "r");
+ EVP_PKEY* key = NULL;
+ PKCS8_PRIV_KEY_INFO* p8 = NULL;
+ const char* pass = "wolfprov-test-pass";
+ unsigned char salt[16] = {0};
+ int scheme, format;
+
+ if (input != NULL) {
+ key = PEM_read_bio_PrivateKey_ex(input, NULL, NULL, NULL,
+ osslLibCtx, NULL);
+ }
+ if (key != NULL) {
+ p8 = EVP_PKEY2PKCS8(key);
+ }
+ err = (p8 == NULL);
+ for (scheme = 0; !err &&
+ (scheme < (int)(sizeof(cases) / sizeof(cases[0]))); scheme++) {
+ EVP_CIPHER* cipher = EVP_CIPHER_fetch(osslLibCtx,
+ cases[scheme].cipher, NULL);
+ X509_SIG* epki = NULL;
+#ifndef WP_HAVE_PKCS8_ENC
+ /* Keep rejection coverage when this build cannot import AES keys. */
+ if (cases[scheme].allowed) {
+ EVP_CIPHER_free(cipher);
+ continue;
+ }
+#endif
+ err = (cipher == NULL);
+ if (!err && !cases[scheme].traditional) {
+ if (cases[scheme].pbe == -1) {
+ X509_ALGOR* alg = PKCS5_pbe2_set_iv_ex(cipher, 2048, salt,
+ sizeof(salt), NULL, cases[scheme].prf, osslLibCtx);
+ if (alg != NULL) {
+ epki = PKCS8_set0_pbe_ex(pass, (int)XSTRLEN(pass), p8,
+ alg, osslLibCtx, NULL);
+ if (epki == NULL) {
+ X509_ALGOR_free(alg);
+ }
+ }
+ }
+ else {
+ epki = PKCS8_encrypt_ex(cases[scheme].pbe, cipher,
+ pass, (int)XSTRLEN(pass), salt, sizeof(salt), 2048, p8,
+ osslLibCtx, NULL);
+ }
+ err = (epki == NULL);
+ }
+ for (format = cases[scheme].traditional; !err && (format < 2);
+ format++) {
+ BIO* encoded = BIO_new(BIO_s_mem());
+ EVP_PKEY* decoded = NULL;
+ OSSL_DECODER_CTX* ctx = NULL;
+ char* bytes = NULL;
+ unsigned long code;
+ int rc = 0, denied = 0;
+
+ if (encoded != NULL) {
+ if (cases[scheme].traditional) {
+ /* This writer has no libctx argument. Create the legacy
+ * fixture with OpenSSL, even under a global FIPS config. */
+ OSSL_LIB_CTX* saved = OSSL_LIB_CTX_set0_default(osslLibCtx);
+ if (saved != NULL) {
+ rc = PEM_write_bio_PrivateKey_traditional(encoded, key,
+ cipher, (const unsigned char*)pass,
+ (int)XSTRLEN(pass), NULL, NULL);
+ OSSL_LIB_CTX_set0_default(saved);
+ }
+ }
+ else {
+ rc = (format == 0) ? i2d_PKCS8_bio(encoded, epki) :
+ PEM_write_bio_PKCS8(encoded, epki);
+ }
+ }
+ err = (rc != 1);
+ if (!err) {
+ ctx = OSSL_DECODER_CTX_new_for_pkey(&decoded,
+ (format == 0) ? "DER" : "PEM", NULL, "RSA",
+ EVP_PKEY_KEYPAIR, wpLibCtx, "provider=wolfprov");
+ err = (ctx == NULL) ||
+ (OSSL_DECODER_CTX_set_passphrase(ctx,
+ (const unsigned char*)pass, XSTRLEN(pass)) != 1);
+ }
+ ERR_clear_error();
+ if (!err) {
+ size_t len = (size_t)BIO_get_mem_data(encoded, &bytes);
+ const unsigned char* p = (const unsigned char*)bytes;
+ rc = OSSL_DECODER_from_data(ctx, &p, &len);
+ while ((code = ERR_get_error()) != 0) {
+ denied |= (ERR_GET_LIB(code) == ERR_LIB_PROV) &&
+ (ERR_GET_REASON(code) == PROV_R_NOT_SUPPORTED);
+ }
+ /* Some bundles lack AES-192 PKCS#8 support. The policy must
+ * allow it; verify the key when the decoder supports it. */
+ if (cases[scheme].allowed && (rc == 0) &&
+ (strcmp(cases[scheme].cipher, "AES-192-CBC") == 0)) {
+ err = denied || (decoded != NULL);
+ }
+ else if (cases[scheme].allowed) {
+ err = (rc != 1) || (decoded == NULL) ||
+ (EVP_PKEY_eq(key, decoded) != 1);
+ }
+ else {
+ err = (rc != 0) || (decoded != NULL) || !denied;
+ }
+ }
+ if (err) {
+ PRINT_ERR_MSG("Incorrect FIPS encrypted-key policy: %d / %d",
+ scheme, format);
+ }
+ OSSL_DECODER_CTX_free(ctx);
+ EVP_PKEY_free(decoded);
+ BIO_free(encoded);
+ }
+ X509_SIG_free(epki);
+ EVP_CIPHER_free(cipher);
+ }
+ PKCS8_PRIV_KEY_INFO_free(p8);
+ EVP_PKEY_free(key);
+ BIO_free(input);
+#endif
+ (void)data;
+ ERR_clear_error();
+ return err;
+}
+#endif /* WP_FIPS_DENY_LEGACY */
diff --git a/test/test_tls1_prf.c b/test/test_tls1_prf.c
index 65ee8842..16c966e6 100644
--- a/test/test_tls1_prf.c
+++ b/test/test_tls1_prf.c
@@ -19,6 +19,7 @@
*/
#include "unit.h"
+#include
#ifdef WP_HAVE_TLS1_PRF
@@ -531,7 +532,23 @@ int test_tls1_prf(void *data)
(void)data;
-#ifdef WP_HAVE_MD5_SHA1
+#ifdef WP_FIPS_DENY_LEGACY
+ {
+ unsigned char key[32];
+ EVP_MD* md;
+
+ /* MD5-SHA1 must remain unusable after metadata discovery succeeds. */
+ ERR_clear_error();
+ md = EVP_MD_fetch(wpLibCtx, "MD5-SHA1", "provider=wolfprov");
+ err = (md == NULL) || (ERR_peek_error() != 0);
+ if (err == 0) {
+ err = (test_tls1_prf_calc(wpLibCtx, key, sizeof(key), md) == 0) ||
+ (ERR_peek_error() == 0);
+ }
+ ERR_clear_error();
+ EVP_MD_free(md);
+ }
+#elif defined(WP_HAVE_MD5_SHA1)
err = test_tls1_prf_md(EVP_md5_sha1());
#endif
#ifdef WP_HAVE_SHA256
diff --git a/test/unit.c b/test/unit.c
index be7f72a2..fa0dceb3 100644
--- a/test/unit.c
+++ b/test/unit.c
@@ -180,6 +180,12 @@ static int debug = 1;
static unsigned long flags = 0;
TEST_CASE test_case[] = {
+#ifdef WP_FIPS_DENY_LEGACY
+ /* Exercise the first fetch before another test constructs the methods. */
+ TEST_DECL(test_fips_legacy_algorithms, NULL),
+ TEST_DECL(test_fips_legacy_kdfs, NULL),
+ TEST_DECL(test_fips_legacy_key_decrypt, NULL),
+#endif
TEST_DECL(test_logging, &debug),
TEST_DECL(test_ct_masks, NULL),
#ifdef WP_HAVE_SHA1
diff --git a/test/unit.h b/test/unit.h
index 35bc245b..f822ee2a 100644
--- a/test/unit.h
+++ b/test/unit.h
@@ -78,8 +78,8 @@
#ifdef TEST_MULTITHREADED
-#define PRINT_MSG(str)
-#define PRINT_ERR_MSG(str)
+#define PRINT_MSG(...)
+#define PRINT_ERR_MSG(...)
#else
#define PRINT_MSG(str, ...) printf("MSG: " str "\n", ##__VA_ARGS__)
#define PRINT_ERR_MSG(str, ...) printf("ERR: " str "\n", ##__VA_ARGS__)
@@ -117,6 +117,9 @@ typedef struct TEST_CASE {
int test_logging(void *data);
int test_ct_masks(void *data);
+int test_fips_legacy_algorithms(void *data);
+int test_fips_legacy_kdfs(void *data);
+int test_fips_legacy_key_decrypt(void *data);
#define WP_VALGRIND_TEST 0x1