From d8c513f96f1d9cebf5282227b67744c0172a0213 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 30 Jul 2026 11:27:00 -0700 Subject: [PATCH 1/9] Encrypt PKCS#8 keys when a cipher is configured --- .github/workflows/iperf.yml | 6 +- include/wolfprovider/settings.h | 7 ++ src/wp_dec_pem2der.c | 7 +- src/wp_dh_kmgmt.c | 99 ++++++++++++++++++++------ src/wp_ecc_kmgmt.c | 38 ++++++++-- src/wp_ecx_kmgmt.c | 12 +++- src/wp_internal.c | 40 +++++++++-- src/wp_mldsa_kmgmt.c | 12 +++- src/wp_rsa_kmgmt.c | 23 ++++-- test/test_dh.c | 57 ++++++++++++++- test/test_ecc.c | 16 +++++ test/test_ecx.c | 30 +++++++- test/test_mldsa.c | 10 +++ test/test_pkey.c | 122 ++++++++++++++++++++++++++++++++ test/test_rsa.c | 16 +++++ test/unit.c | 3 +- test/unit.h | 19 +++-- 17 files changed, 454 insertions(+), 63 deletions(-) diff --git a/.github/workflows/iperf.yml b/.github/workflows/iperf.yml index 711346e8..8d7b8a9d 100644 --- a/.github/workflows/iperf.yml +++ b/.github/workflows/iperf.yml @@ -111,9 +111,11 @@ jobs: mkdir -p $KEY_DIR cd $KEY_DIR # Generate RSA keys for iperf tests + # PBKDF2 keys HMAC with the passphrase, and FIPS modules before + # v6.0.0 reject HMAC keys under 14 bytes, so keep this >= 14. openssl genrsa -out rsa_private_unprotected.pem 2048 - openssl rsa -in rsa_private_unprotected.pem -out rsa_private.pem -aes256 -passout 'pass:password' - openssl rsa -in rsa_private.pem -pubout -out rsa_public.pem -passin 'pass:password' + openssl rsa -in rsa_private_unprotected.pem -out rsa_private.pem -aes256 -passout 'pass:wolfprov-iperf-pass' + openssl rsa -in rsa_private.pem -pubout -out rsa_public.pem -passin 'pass:wolfprov-iperf-pass' # Create a credentials file for iperf # Username: mario, Password: rossi echo "mario,bf7a49a846d44b454a5d11e7acfaf13d138bbe0b7483aa3e050879700572709b" > credentials.csv diff --git a/include/wolfprovider/settings.h b/include/wolfprovider/settings.h index d5325492..438d5812 100644 --- a/include/wolfprovider/settings.h +++ b/include/wolfprovider/settings.h @@ -43,6 +43,13 @@ #define WP_HAVE_DRBG_RESEED #endif +/* The PKCS#8 encrypt/decrypt helpers need PKCS#8 and password-based key + * derivation. wolfSSL derives WOLFSSL_ENCRYPTED_KEYS from OPENSSL_EXTRA, which + * a FIPS build does not set even though both are present. */ +#if defined(HAVE_PKCS8) && !defined(NO_PWDBASED) + #define WP_HAVE_PKCS8_ENC +#endif + #define WP_HAVE_DIGEST #if !defined(NO_MD5) #define WP_HAVE_MD5 diff --git a/src/wp_dec_pem2der.c b/src/wp_dec_pem2der.c index 745edf93..100b3b43 100644 --- a/src/wp_dec_pem2der.c +++ b/src/wp_dec_pem2der.c @@ -320,17 +320,20 @@ static int wp_pem2der_decode_data(const unsigned char* data, word32 len, dataFormat = "type-specific"; obj = OSSL_OBJECT_PKEY; } -#ifdef WOLFSSL_ENCRYPTED_KEYS else if (XMEMCMP(data, "-----BEGIN ENCRYPTED PRIVATE KEY-----", 37) == 0) { type = PKCS8_ENC_PRIVATEKEY_TYPE; dataType = NULL; dataFormat = "PrivateKeyInfo"; obj = OSSL_OBJECT_PKEY; + /* The body is base64 only; the PBES2 layer is decrypted later by the + * EncryptedPrivateKeyInfo decoder. The callback fields exist only when + * wolfSSL itself was built with encrypted-key support. */ +#ifdef WOLFSSL_ENCRYPTED_KEYS info.passwd_cb = wp_pem_password_cb; info.passwd_userdata = (void*)&wpPwCb; - } #endif + } else { ok = 0; } diff --git a/src/wp_dh_kmgmt.c b/src/wp_dh_kmgmt.c index 5e2b0e5d..f681cb55 100644 --- a/src/wp_dh_kmgmt.c +++ b/src/wp_dh_kmgmt.c @@ -2359,7 +2359,7 @@ static int wp_dh_dec_send_params(wp_Dh* dh, OSSL_CALLBACK *dataCb, return ok; } -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC /** * Decode an encrypted PKCS#8 DER DH private key into the DH key object. * @@ -2476,7 +2476,7 @@ static int wp_dh_decode(wp_DhEncDecCtx* ctx, OSSL_CORE_BIO *cBio, } else if (ok && (ctx->format == WP_ENC_FORMAT_PKI)) { if (!wp_dh_decode_pki(dh, data, len)) { -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC if (!wp_dh_decode_enc_pki(dh, data, len, pwCb, pwCbArg)) #endif { @@ -2637,20 +2637,18 @@ static int wp_dh_encode_spki(const wp_Dh *dh, unsigned char* keyData, } /** - * Get the PKCS#8 encoding size for the key. + * Copy a generated private key into the inner wolfSSL key if not already set. * - * @param [in] dh DH key object. - * @param [out] keyLen Length of encoding in bytes. + * @param [in] dh DH key object. * @return 1 on success. * @return 0 on failure. */ -static int wp_dh_encode_pki_size(const wp_Dh *dh, size_t* keyLen) +static int wp_dh_sync_priv_to_key(const wp_Dh *dh) { int ok = 1; int ret; - word32 len; - WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_encode_pki_size"); + WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_sync_priv_to_key"); /* If we have a generated private key that is not set in the inner key, * set it now */ @@ -2662,9 +2660,33 @@ static int wp_dh_encode_pki_size(const wp_Dh *dh, size_t* keyLen) } } - ret = wc_DhPrivKeyToDer((DhKey*)&dh->key, NULL, &len); - if (ret != LENGTH_ONLY_E) { - ok = 0; + WOLFPROV_LEAVE(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); + return ok; +} + +/** + * Get the PKCS#8 encoding size for the key. + * + * @param [in] dh DH key object. + * @param [out] keyLen Length of encoding in bytes. + * @return 1 on success. + * @return 0 on failure. + */ +static int wp_dh_encode_pki_size(const wp_Dh *dh, size_t* keyLen) +{ + int ok = 1; + int ret; + word32 len; + + WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_encode_pki_size"); + + ok = wp_dh_sync_priv_to_key(dh); + + if (ok) { + ret = wc_DhPrivKeyToDer((DhKey*)&dh->key, NULL, &len); + if (ret != LENGTH_ONLY_E) { + ok = 0; + } } if (ok) { *keyLen = len; @@ -2684,6 +2706,14 @@ static int wp_dh_encode_pki_size(const wp_Dh *dh, size_t* keyLen) * @return 1 on success. * @return 0 on failure. */ +/* wolfSSL calculating it wrong. */ +static void wp_dh_fix_pki_len(unsigned char* keyData, word32 len) +{ + if (keyData[1] == 0x81) { + keyData[2] = (unsigned char)(len - 3); + } +} + static int wp_dh_encode_pki(const wp_Dh *dh, unsigned char* keyData, size_t* keyLen) { @@ -2699,17 +2729,14 @@ static int wp_dh_encode_pki(const wp_Dh *dh, unsigned char* keyData, } if (ok) { *keyLen = len; - /* wolfSSL calculating it wrong. */ - if (keyData[1] == 0x81) { - keyData[2] = len - 3; - } + wp_dh_fix_pki_len(keyData, len); } WOLFPROV_LEAVE(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC /** * Get the Encrypted PKCS#8 encoding size for the key. * @@ -2728,10 +2755,14 @@ static int wp_dh_encode_epki_size(const wp_DhEncDecCtx* ctx, const wp_Dh *dh, WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_encode_epki_size"); - /* Get the plaintext PKCS #8 length. */ - ret = wc_DhPrivKeyToDer((DhKey*)&dh->key, NULL, &len); - if (ret != LENGTH_ONLY_E) { - ok = 0; + ok = wp_dh_sync_priv_to_key(dh); + + if (ok) { + /* Get the plaintext PKCS #8 length. */ + ret = wc_DhPrivKeyToDer((DhKey*)&dh->key, NULL, &len); + if (ret != LENGTH_ONLY_E) { + ok = 0; + } } if (ok) { /* Get the size of the PBES2 EncryptedPrivateKeyInfo encoding. */ @@ -2785,6 +2816,11 @@ static int wp_dh_encode_epki(const wp_DhEncDecCtx* ctx, const wp_Dh *dh, ok = 0; } } + if (ok) { + /* Same length correction the plaintext encoder applies, so the + * encrypted body wraps an identical PKCS#8. */ + wp_dh_fix_pki_len(encodedKey, pkcs8Len); + } if (ok) { /* Encrypt as a PBES2 EncryptedPrivateKeyInfo. */ ok = wp_encrypt_key_pkcs8(ctx->provCtx, ctx->cipher, encodedKey, @@ -2861,11 +2897,20 @@ static int wp_dh_encode(wp_DhEncDecCtx* ctx, OSSL_CORE_BIO *cBio, } else if (ok && (ctx->format == WP_ENC_FORMAT_PKI)) { private = 1; +#ifdef WP_HAVE_PKCS8_ENC + /* A cipher on a PrivateKeyInfo encoder selects the encrypted form. */ + if (ctx->cipherName != NULL) { + if (!wp_dh_encode_epki_size(ctx, key, &derLen)) { + ok = 0; + } + } + else +#endif if (!wp_dh_encode_pki_size(key, &derLen)) { ok = 0; } } -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC else if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { private = 1; if (!wp_dh_encode_epki_size(ctx, key, &derLen)) { @@ -2896,11 +2941,21 @@ static int wp_dh_encode(wp_DhEncDecCtx* ctx, OSSL_CORE_BIO *cBio, } else if (ok && (ctx->format == WP_ENC_FORMAT_PKI)) { private = 1; +#ifdef WP_HAVE_PKCS8_ENC + if (ctx->cipherName != NULL) { + pemType = PKCS8_ENC_PRIVATEKEY_TYPE; + if (!wp_dh_encode_epki(ctx, key, derData, &derLen, pwCb, + pwCbArg)) { + ok = 0; + } + } + else +#endif if (!wp_dh_encode_pki(key, derData, &derLen)) { ok = 0; } } -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC else if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { private = 1; pemType = PKCS8_ENC_PRIVATEKEY_TYPE; diff --git a/src/wp_ecc_kmgmt.c b/src/wp_ecc_kmgmt.c index 4d6e1b57..8a533bad 100644 --- a/src/wp_ecc_kmgmt.c +++ b/src/wp_ecc_kmgmt.c @@ -2337,7 +2337,7 @@ static int wp_ecc_dec_send_params(wp_Ecc* ecc, OSSL_CALLBACK *dataCb, return ok; } -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC /** * Decode an encrypted PKCS#8 DER ECC private key into the ECC key object. * @@ -2451,7 +2451,7 @@ static int wp_ecc_decode(wp_EccEncDecCtx* ctx, OSSL_CORE_BIO *cBio, } else if (ok && (ctx->format == WP_ENC_FORMAT_PKI)) { if (!wp_ecc_decode_pki(ecc, data, len)) { -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC if (!wp_ecc_decode_enc_pki(ecc, data, len, pwCb, pwCbArg)) #endif { @@ -2795,7 +2795,7 @@ static int wp_ecc_encode_pki(const wp_Ecc *ecc, unsigned char* keyData, return ok; } -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC /** * Get the Encrypted PKCS#8 encoding size for the key. * @@ -2931,6 +2931,15 @@ static int wp_ecc_encode(wp_EccEncDecCtx* ctx, OSSL_CORE_BIO *cBio, ok = 0; } + /* Traditional PEM encryption is not implemented, so refuse a cipher here + * rather than write the private key in the clear. */ + if (ok && ((ctx->format == WP_ENC_FORMAT_TYPE_SPECIFIC) || + (ctx->format == WP_ENC_FORMAT_X9_62)) && + (ctx->cipherName != NULL) && + ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0)) { + ok = 0; + } + if (ok && ((ctx->format == WP_ENC_FORMAT_TYPE_SPECIFIC) || (ctx->format == WP_ENC_FORMAT_X9_62))) { if (selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) { @@ -2957,11 +2966,20 @@ static int wp_ecc_encode(wp_EccEncDecCtx* ctx, OSSL_CORE_BIO *cBio, } else if (ok && (ctx->format == WP_ENC_FORMAT_PKI)) { private = 1; +#ifdef WP_HAVE_PKCS8_ENC + /* A cipher on a PrivateKeyInfo encoder selects the encrypted form. */ + if (ctx->cipherName != NULL) { + if (!wp_ecc_encode_epki_size(ctx, key, &derLen)) { + ok = 0; + } + } + else +#endif if (!wp_ecc_encode_pki_size(key, &derLen)) { ok = 0; } } -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC else if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { private = 1; if (!wp_ecc_encode_epki_size(ctx, key, &derLen)) { @@ -3008,11 +3026,21 @@ static int wp_ecc_encode(wp_EccEncDecCtx* ctx, OSSL_CORE_BIO *cBio, } else if (ok && (ctx->format == WP_ENC_FORMAT_PKI)) { private = 1; +#ifdef WP_HAVE_PKCS8_ENC + if (ctx->cipherName != NULL) { + pemType = PKCS8_ENC_PRIVATEKEY_TYPE; + if (!wp_ecc_encode_epki(ctx, key, derData, &derLen, pwCb, + pwCbArg)) { + ok = 0; + } + } + else +#endif if (!wp_ecc_encode_pki(key, derData, &derLen)) { ok = 0; } } -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC else if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { private = 1; pemType = PKCS8_ENC_PRIVATEKEY_TYPE; diff --git a/src/wp_ecx_kmgmt.c b/src/wp_ecx_kmgmt.c index d7386a8c..03bf41c7 100644 --- a/src/wp_ecx_kmgmt.c +++ b/src/wp_ecx_kmgmt.c @@ -2031,7 +2031,7 @@ static int wp_ecx_dec_send_params(wp_Ecx* ecx, const char* dataType, return ok; } -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC /** * Decode an encrypted PKCS#8 DER ECX private key into the ECX key object. * @@ -2152,7 +2152,7 @@ static int wp_ecx_decode(wp_EcxEncDecCtx* ctx, OSSL_CORE_BIO* cBio, if (ok) { rc = ctx->decode(data, &idx, (void*)&ecx->key, len); if (rc != 0) { -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC /* May be an encrypted PKCS#8 key - decrypt and retry. */ if ((ctx->format != WP_ENC_FORMAT_PKI) || (!wp_ecx_decode_enc_pki(ctx, ecx, data, len, pwCb, pwCbArg))) @@ -2249,7 +2249,13 @@ static int wp_ecx_encode(wp_EcxEncDecCtx* ctx, OSSL_CORE_BIO *cBio, /* By default the plaintext DER is the source for the output encoding. */ srcData = derData; srcLen = derLen; - if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { + /* A cipher on a PrivateKeyInfo encoder selects the encrypted form. */ + if (ok && ((ctx->format == WP_ENC_FORMAT_EPKI) +#ifdef WP_HAVE_PKCS8_ENC + || ((ctx->format == WP_ENC_FORMAT_PKI) && + (ctx->cipherName != NULL)) +#endif + )) { pemType = PKCS8_ENC_PRIVATEKEY_TYPE; /* The PBES2 output is larger than the plaintext and must use a * separate buffer, so size it and encrypt into fresh memory. */ diff --git a/src/wp_internal.c b/src/wp_internal.c index 6dc3a171..b544e51b 100644 --- a/src/wp_internal.c +++ b/src/wp_internal.c @@ -925,9 +925,18 @@ int wp_cipher_from_params(const OSSL_PARAM params[], int* cipher, if (p->data_type != OSSL_PARAM_UTF8_STRING) { ok = 0; } - if (ok) { - size_t i; + else if (p->data == NULL) { + /* OSSL_ENCODER_CTX_set_cipher(ctx, NULL, ...) asks for the + * unencrypted encoding, so clear rather than fail. */ + *cipher = 0; + if (cipherName != NULL) { + *cipherName = NULL; + } + } + else { + size_t i = WP_CIPHER_NAMES_LEN; +#ifdef WP_HAVE_PKCS8_ENC for (i = 0; i < WP_CIPHER_NAMES_LEN; i++) { if ((XSTRLEN(wp_cipher_names[i].name) == p->data_size) && (XSTRNCMP(p->data, wp_cipher_names[i].name, @@ -939,7 +948,15 @@ int wp_cipher_from_params(const OSSL_PARAM params[], int* cipher, break; } } +#endif + /* Unknown cipher, or a build that cannot encrypt keys at all. + * Clear so a previously set cipher cannot drive a later encode, + * and fail rather than silently write the key in the clear. */ if (i == WP_CIPHER_NAMES_LEN) { + *cipher = 0; + if (cipherName != NULL) { + *cipherName = NULL; + } ok = 0; } } @@ -987,6 +1004,13 @@ int wp_encrypt_key_pkcs8_size(WOLFPROV_CTX* provCtx, int cipher, if (cipher == 0) { ok = 0; } +#ifndef WP_SINGLE_THREADED + /* The IV is drawn from the RNG before the length-only return, so the + * shared RNG needs the same lock the encrypt path takes. */ + if (ok && (wp_provctx_lock_rng(provCtx) != 1)) { + ok = 0; + } +#endif if (ok) { /* Passing a NULL output buffer returns the required length. The _ex * form (wolfSSL 5.8.2+) selects the HMAC-SHA256 PBKDF2 PRF; older @@ -1000,6 +1024,9 @@ int wp_encrypt_key_pkcs8_size(WOLFPROV_CTX* provCtx, int cipher, rc = wc_EncryptPKCS8Key(fakeData, plainLen, NULL, &outSz, "", 0, WP_PKCS5, WP_PBES2, cipher, fakeSalt, sizeof(fakeSalt), WP_PKCS12_ITERATIONS_DEFAULT, wp_provctx_get_rng(provCtx), NULL); + #endif + #ifndef WP_SINGLE_THREADED + wp_provctx_unlock_rng(provCtx); #endif if (rc != LENGTH_ONLY_E) { ok = 0; @@ -1080,10 +1107,12 @@ int wp_encrypt_key_pkcs8(WOLFPROV_CTX* provCtx, int cipher, if (ok && (passwordSz > WP_EPKI_PASSWORD_MAX)) { ok = 0; } - if (ok) { #ifndef WP_SINGLE_THREADED - wp_provctx_lock_rng(provCtx); + if (ok && (wp_provctx_lock_rng(provCtx) != 1)) { + ok = 0; + } #endif + if (ok) { /* Generate the PBKDF2 salt. */ rc = wc_RNG_GenerateBlock(rng, salt, sizeof(salt)); if (rc == 0) { @@ -1110,7 +1139,7 @@ int wp_encrypt_key_pkcs8(WOLFPROV_CTX* provCtx, int cipher, ok = 0; } else { - *outLen = (size_t)outSz; + *outLen = (size_t)rc; } } @@ -1505,4 +1534,3 @@ word32 wp_atoc32(const byte* c) { return *(const word32*)c; #endif } - diff --git a/src/wp_mldsa_kmgmt.c b/src/wp_mldsa_kmgmt.c index 9fb14696..36c599fe 100644 --- a/src/wp_mldsa_kmgmt.c +++ b/src/wp_mldsa_kmgmt.c @@ -1356,7 +1356,7 @@ static int wp_mldsa_dec_send_params(wp_MlDsa* mldsa, const char* dataType, return ok; } -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC /** * Decode an encrypted PKCS#8 DER ML-DSA private key into the ML-DSA key object. * @@ -1447,7 +1447,7 @@ static int wp_mldsa_decode(wp_MlDsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, if (ok) { rc = ctx->decode(data, &idx, (void*)&mldsa->key, len); if (rc != 0) { -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC /* May be an encrypted PKCS#8 key - decrypt and retry. */ if ((ctx->format != WP_ENC_FORMAT_PKI) || (!wp_mldsa_decode_enc_pki(ctx, mldsa, data, len, pwCb, @@ -1568,7 +1568,13 @@ static int wp_mldsa_encode(wp_MlDsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, /* By default the plaintext DER is the source for the output encoding. */ srcData = derData; srcLen = derLen; - if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { + /* A cipher on a PrivateKeyInfo encoder selects the encrypted form. */ + if (ok && ((ctx->format == WP_ENC_FORMAT_EPKI) +#ifdef WP_HAVE_PKCS8_ENC + || ((ctx->format == WP_ENC_FORMAT_PKI) && + (ctx->cipherName != NULL)) +#endif + )) { pemType = PKCS8_ENC_PRIVATEKEY_TYPE; /* The PBES2 output is larger than the plaintext and must use a * separate buffer, so size it and encrypt into fresh memory. */ diff --git a/src/wp_rsa_kmgmt.c b/src/wp_rsa_kmgmt.c index f69dfd12..3536aac4 100644 --- a/src/wp_rsa_kmgmt.c +++ b/src/wp_rsa_kmgmt.c @@ -2705,7 +2705,7 @@ static int wp_rsa_decode_pki(wp_Rsa* rsa, unsigned char* data, word32 len) return ok; } -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC /** * Decode the encrypted DER encoded RSA private key into the RSA key object. @@ -2836,7 +2836,7 @@ static int wp_rsa_decode(wp_RsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, } else if (ok && (ctx->format == WP_ENC_FORMAT_PKI)) { if (!wp_rsa_decode_pki(rsa, data, len)) { -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC if (!wp_rsa_decode_enc_pki(rsa, data, len, pwCb, pwCbArg)) #endif { @@ -3428,7 +3428,7 @@ static int wp_rsa_encode_priv(const wp_Rsa* rsa, unsigned char* keyData, return ok; } -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC /** * Get the Encrypted Private Key encoding size for the key. * @@ -3555,13 +3555,22 @@ static int wp_rsa_encode(wp_RsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, ok = 0; } + /* Traditional PEM encryption is not implemented, so refuse a cipher here + * rather than write the private key in the clear. */ + if (ok && (ctx->format == WP_ENC_FORMAT_TYPE_SPECIFIC) && + (ctx->cipherName != NULL) && + ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0)) { + ok = 0; + } + if (ok && (ctx->format == WP_ENC_FORMAT_SPKI)) { if (!wp_rsa_encode_spki_size(key, &derLen)) { ok = 0; } } else if (ok && (ctx->format == WP_ENC_FORMAT_PKI)) { -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC + /* A cipher on a PrivateKeyInfo encoder selects the encrypted form. */ if (ctx->cipherName != NULL) { ok = wp_rsa_encode_enc_pki_size(ctx, key, &derLen); } @@ -3571,7 +3580,7 @@ static int wp_rsa_encode(wp_RsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, ok = 0; } } -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC else if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { if (!wp_rsa_encode_enc_pki_size(ctx, key, &derLen)) { ok = 0; @@ -3606,7 +3615,7 @@ static int wp_rsa_encode(wp_RsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, } else if (ok && (ctx->format == WP_ENC_FORMAT_PKI)) { private = 1; -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC if (ctx->cipherName != NULL) { pemType = PKCS8_ENC_PRIVATEKEY_TYPE; ok = wp_rsa_encode_enc_pki(ctx, key, derData, &derLen, pwCb, @@ -3618,7 +3627,7 @@ static int wp_rsa_encode(wp_RsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, ok = 0; } } -#ifdef WOLFSSL_ENCRYPTED_KEYS +#ifdef WP_HAVE_PKCS8_ENC else if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { private = 1; pemType = PKCS8_ENC_PRIVATEKEY_TYPE; diff --git a/test/test_dh.c b/test/test_dh.c index 1bb0c028..da5baf82 100644 --- a/test/test_dh.c +++ b/test/test_dh.c @@ -19,6 +19,7 @@ */ #include "unit.h" +#include #include #include #include @@ -361,7 +362,12 @@ int test_dh_pkey(void *data) return err; } -#if defined(WOLFSSL_DH_EXTRA) && defined(WP_HAVE_EPKI_TEST) +/* Not run under FIPS: dh_der carries an arbitrary group where FIPS allows + * only approved safe primes, and a generated ffdhe2048 key encrypts but + * will not decode back. Both predate the EPKI gate fix that made this + * test visible to FIPS builds. */ +#if defined(WOLFSSL_DH_EXTRA) && defined(WP_HAVE_EPKI_TEST) && \ + !defined(HAVE_FIPS) int test_dh_encode_epki(void *data) { int err = 0; @@ -396,11 +402,58 @@ int test_dh_encode_epki(void *data) wpLibCtx); } + if (err == 0) { + PRINT_MSG("PrivateKeyInfo DER with cipher set must encrypt"); + err = test_pki_cipher_encrypts(pkey, "DER", "provider=libwolfprov", + wpLibCtx, 1); + } + if (err == 0) { + PRINT_MSG("PrivateKeyInfo PEM with cipher set must encrypt"); + err = test_pki_cipher_encrypts(pkey, "PEM", "provider=libwolfprov", + wpLibCtx, 1); + } + /* A generated key keeps its private value in wp_Dh rather than the inner + * wolfSSL key, so it reaches encode paths an imported key does not. */ + if (err == 0) { + EVP_PKEY* genKey = NULL; + EVP_PKEY_CTX* genCtx = NULL; + OSSL_PARAM gp[2]; + + PRINT_MSG("Generated key: PrivateKeyInfo with cipher set"); + gp[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, + (char*)"ffdhe2048", 0); + gp[1] = OSSL_PARAM_construct_end(); + + genCtx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "DH", NULL); + err = (genCtx == NULL); + if (err == 0) { + err = EVP_PKEY_keygen_init(genCtx) != 1; + } + if (err == 0) { + err = EVP_PKEY_CTX_set_params(genCtx, gp) != 1; + } + if (err == 0) { + err = EVP_PKEY_generate(genCtx, &genKey) != 1; + } + if (err == 0) { + /* No key comparison: a generated DH key does not compare equal + * after a PKCS#8 round trip, with or without a cipher. */ + err = test_pki_cipher_encrypts(genKey, "DER", + "provider=libwolfprov", wpLibCtx, 0); + } + if (err == 0) { + err = test_pki_cipher_encrypts(genKey, "PEM", + "provider=libwolfprov", wpLibCtx, 0); + } + EVP_PKEY_free(genKey); + EVP_PKEY_CTX_free(genCtx); + } + EVP_PKEY_free(pkey); return err; } -#endif /* WOLFSSL_DH_EXTRA && WP_HAVE_EPKI_TEST */ +#endif /* WOLFSSL_DH_EXTRA && WP_HAVE_EPKI_TEST && !HAVE_FIPS */ int test_dh_invalid_kdf_strings(void *data) { diff --git a/test/test_ecc.c b/test/test_ecc.c index ca137cc8..ef272e60 100644 --- a/test/test_ecc.c +++ b/test/test_ecc.c @@ -992,6 +992,22 @@ int test_ecc_encode_epki(void *data) } EVP_PKEY_free(osslKey); + if (err == 0) { + PRINT_MSG("PrivateKeyInfo DER with cipher set must encrypt"); + err = test_pki_cipher_encrypts(pkey, "DER", "provider=libwolfprov", + wpLibCtx, 1); + } + if (err == 0) { + PRINT_MSG("PrivateKeyInfo PEM with cipher set must encrypt"); + err = test_pki_cipher_encrypts(pkey, "PEM", "provider=libwolfprov", + wpLibCtx, 1); + } + if (err == 0) { + PRINT_MSG("PrivateKeyInfo PEM with cipher set: wolfProvider -> OpenSSL"); + err = test_pki_cipher_encrypts(pkey, "PEM", "provider=libwolfprov", + osslLibCtx, 0); + } + EVP_PKEY_free(pkey); return err; diff --git a/test/test_ecx.c b/test/test_ecx.c index 8f4391e1..309d3d97 100644 --- a/test/test_ecx.c +++ b/test/test_ecx.c @@ -149,9 +149,22 @@ int test_ecx_encode_epki(void *data) wpLibCtx, NULL); err = (pkey == NULL); - /* wolfProvider self round-trip (DER and PEM). ECX plaintext PKCS#8 is - * block-aligned, so wolfProvider -> OpenSSL interop additionally depends on - * a wolfSSL PKCS#7 padding fix and is not asserted here. */ + /* Ed25519's PKCS#8 is an exact AES block multiple, the case needing a whole + * extra PKCS#7 pad block. wolfSSL omitted it until wc_EncryptPKCS8Key_ex + * was fixed after v5.9.2-stable, and its own decoder accepts the short + * form, so only OpenSSL catches it. Assert once a release carries the fix. */ +#if LIBWOLFSSL_VERSION_HEX > 0x05009002 + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo DER: wolfProvider -> OpenSSL"); + err = test_epki_encode_decode(pkey, "DER", "provider=libwolfprov", + osslLibCtx); + } + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo PEM: wolfProvider -> OpenSSL"); + err = test_epki_encode_decode(pkey, "PEM", "provider=libwolfprov", + osslLibCtx); + } +#endif if (err == 0) { PRINT_MSG("EncryptedPrivateKeyInfo DER: wolfProvider -> wolfProvider"); err = test_epki_encode_decode(pkey, "DER", "provider=libwolfprov", @@ -163,6 +176,17 @@ int test_ecx_encode_epki(void *data) wpLibCtx); } + if (err == 0) { + PRINT_MSG("PrivateKeyInfo DER with cipher set must encrypt"); + err = test_pki_cipher_encrypts(pkey, "DER", "provider=libwolfprov", + wpLibCtx, 1); + } + if (err == 0) { + PRINT_MSG("PrivateKeyInfo PEM with cipher set must encrypt"); + err = test_pki_cipher_encrypts(pkey, "PEM", "provider=libwolfprov", + wpLibCtx, 1); + } + EVP_PKEY_free(pkey); return err; diff --git a/test/test_mldsa.c b/test/test_mldsa.c index 63c1d600..8a651d41 100644 --- a/test/test_mldsa.c +++ b/test/test_mldsa.c @@ -1245,6 +1245,16 @@ int test_mldsa_encode_epki(void* data) err = test_epki_encode_decode(pkey, "PEM", "provider=libwolfprov", wpLibCtx); } + if (err == 0) { + PRINT_MSG("PrivateKeyInfo DER with cipher set must encrypt"); + err = test_pki_cipher_encrypts(pkey, "DER", "provider=libwolfprov", + wpLibCtx, 1); + } + if (err == 0) { + PRINT_MSG("PrivateKeyInfo PEM with cipher set must encrypt"); + err = test_pki_cipher_encrypts(pkey, "PEM", "provider=libwolfprov", + wpLibCtx, 1); + } EVP_PKEY_free(pkey); diff --git a/test/test_pkey.c b/test/test_pkey.c index e8931ec0..fb18dd65 100644 --- a/test/test_pkey.c +++ b/test/test_pkey.c @@ -298,6 +298,128 @@ int test_pkey_dec(EVP_PKEY *pkey, OSSL_LIB_CTX* libCtx, unsigned char *msg, return err; } +/** + * Check that a cipher set on a PrivateKeyInfo encoder actually encrypts. + * + * This is the path "openssl pkey -aes256" takes: the structure stays + * PrivateKeyInfo and the cipher is set on the encoder. + * + * @param [in] pkey Key to encode. + * @param [in] fmt "DER" or "PEM". + * @param [in] encProp Property query selecting the encoding provider. + * @param [in] decLibCtx Library context used to decode. + * @param [in] cmpKey Compare the decoded key with the original. + * @return 0 on success, non-zero on failure. + */ +int test_pki_cipher_encrypts(EVP_PKEY* pkey, const char* fmt, + const char* encProp, OSSL_LIB_CTX* decLibCtx, int cmpKey) +{ +#ifndef WP_HAVE_PKCS8_ENC + /* wolfSSL lacks the PKCS#8 encrypt helpers, so the encoder writes the + * plaintext form and there is nothing to assert. */ + (void)pkey; + (void)fmt; + (void)encProp; + (void)decLibCtx; + (void)cmpKey; + return 0; +#else + int err = 0; + EVP_PKEY* pkey2 = NULL; + EVP_PKEY* badKey = NULL; + OSSL_ENCODER_CTX* ectx = NULL; + OSSL_DECODER_CTX* dctx = NULL; + OSSL_DECODER_CTX* bctx = NULL; + unsigned char* data = NULL; + size_t dataLen = 0; + size_t encLen = 0; + const unsigned char* pp; + const char* pass = "wolfprov-test-pass"; + const char* badPass = "wrong-passphrase"; + size_t passLen = strlen(pass); + static const char epkiHdr[] = "-----BEGIN ENCRYPTED PRIVATE KEY-----"; + + ectx = OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_KEYPAIR, fmt, + "PrivateKeyInfo", encProp); + err = (ectx == NULL); + if (err == 0) { + err = OSSL_ENCODER_CTX_set_cipher(ectx, "AES-256-CBC", NULL) != 1; + } + if (err == 0) { + err = OSSL_ENCODER_CTX_set_passphrase(ectx, (const unsigned char*)pass, + passLen) != 1; + } + if (err == 0) { + err = OSSL_ENCODER_to_data(ectx, &data, &dataLen) != 1; + } + if (err == 0) { + encLen = dataLen; + } + /* PEM names the container outright. DER has no header, so the plaintext + * form is ruled out by requiring the wrong passphrase to fail below. */ + if ((err == 0) && (XSTRCMP(fmt, "PEM") == 0)) { + err = (dataLen < sizeof(epkiHdr) - 1) || + (XMEMCMP(data, epkiHdr, sizeof(epkiHdr) - 1) != 0); + if (err) { + PRINT_ERR_MSG("Cipher set but key was not encrypted"); + } + } + /* The encrypted key must still decode back to the same key. */ + if (err == 0) { + pp = data; + dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey2, fmt, NULL, + EVP_PKEY_get0_type_name(pkey), EVP_PKEY_KEYPAIR, decLibCtx, NULL); + err = (dctx == NULL); + } + if (err == 0) { + err = OSSL_DECODER_CTX_set_passphrase(dctx, (const unsigned char*)pass, + passLen) != 1; + } + if (err == 0) { + err = OSSL_DECODER_from_data(dctx, &pp, &dataLen) != 1; + } + if (err == 0) { + err = (pkey2 == NULL); + } + if ((err == 0) && cmpKey) { + err = EVP_PKEY_eq(pkey, pkey2) != 1; + if (err) { + PRINT_ERR_MSG("Decoded key does not match the original"); + } + } + /* A wrong passphrase must not recover a key. This is what rules out an + * unencrypted DER body, which no passphrase would be needed to read. */ + if (err == 0) { + pp = data; + dataLen = encLen; + bctx = OSSL_DECODER_CTX_new_for_pkey(&badKey, fmt, NULL, + EVP_PKEY_get0_type_name(pkey), EVP_PKEY_KEYPAIR, decLibCtx, NULL); + err = (bctx == NULL); + } + if (err == 0) { + err = OSSL_DECODER_CTX_set_passphrase(bctx, + (const unsigned char*)badPass, strlen(badPass)) != 1; + } + if (err == 0) { + if ((OSSL_DECODER_from_data(bctx, &pp, &dataLen) == 1) && + (badKey != NULL)) { + PRINT_ERR_MSG("Wrong passphrase recovered the key"); + err = 1; + } + ERR_clear_error(); + } + + OSSL_DECODER_CTX_free(bctx); + OSSL_DECODER_CTX_free(dctx); + OSSL_ENCODER_CTX_free(ectx); + OPENSSL_free(data); + EVP_PKEY_free(badKey); + EVP_PKEY_free(pkey2); + + return err; +#endif /* WP_HAVE_PKCS8_ENC */ +} + /* Encode as EncryptedPrivateKeyInfo with encProp, decode with decLibCtx and * check it matches; a wrong passphrase must fail. Drives both directions. */ int test_epki_encode_decode(EVP_PKEY* pkey, const char* fmt, diff --git a/test/test_rsa.c b/test/test_rsa.c index bfa818ef..a2f57f9f 100644 --- a/test/test_rsa.c +++ b/test/test_rsa.c @@ -2427,6 +2427,22 @@ int test_rsa_encode_epki(void* data) } EVP_PKEY_free(osslKey); + if (err == 0) { + PRINT_MSG("PrivateKeyInfo DER with cipher set must encrypt"); + err = test_pki_cipher_encrypts(pkey, "DER", "provider=libwolfprov", + wpLibCtx, 1); + } + if (err == 0) { + PRINT_MSG("PrivateKeyInfo PEM with cipher set must encrypt"); + err = test_pki_cipher_encrypts(pkey, "PEM", "provider=libwolfprov", + wpLibCtx, 1); + } + if (err == 0) { + PRINT_MSG("PrivateKeyInfo PEM with cipher set: wolfProvider -> OpenSSL"); + err = test_pki_cipher_encrypts(pkey, "PEM", "provider=libwolfprov", + osslLibCtx, 0); + } + EVP_PKEY_free(pkey); PKCS8_PRIV_KEY_INFO_free(p8); diff --git a/test/unit.c b/test/unit.c index 334e3623..dad5eca1 100644 --- a/test/unit.c +++ b/test/unit.c @@ -328,7 +328,8 @@ TEST_CASE test_case[] = { TEST_DECL(test_dh_pgen_pkey, NULL), TEST_DECL(test_dh_pkey, NULL), TEST_DECL(test_dh_invalid_kdf_strings, NULL), -#if defined(WOLFSSL_DH_EXTRA) && defined(WP_HAVE_EPKI_TEST) +#if defined(WOLFSSL_DH_EXTRA) && defined(WP_HAVE_EPKI_TEST) && \ + !defined(HAVE_FIPS) TEST_DECL(test_dh_encode_epki, NULL), #endif TEST_DECL(test_dh_decode, NULL), diff --git a/test/unit.h b/test/unit.h index c5ebb0d2..7e8ef69d 100644 --- a/test/unit.h +++ b/test/unit.h @@ -50,10 +50,10 @@ #define AES_BLOCK_SIZE 16 #endif -/* Encrypted PKCS#8 (EncryptedPrivateKeyInfo) round-trip tests require - * encrypted-key, PKCS#8 and PBKDF support in the linked wolfSSL. */ -#if defined(WOLFSSL_ENCRYPTED_KEYS) && defined(HAVE_PKCS8) && \ - !defined(NO_PWDBASED) +/* Match the capability the encoders gate on. Keying this off + * WOLFSSL_ENCRYPTED_KEYS compiled the tests out of FIPS builds, where the + * encrypted-key code is still live. */ +#ifdef WP_HAVE_PKCS8_ENC #define WP_HAVE_EPKI_TEST #endif @@ -316,6 +316,12 @@ int test_pkey_dec(EVP_PKEY *pkey, OSSL_LIB_CTX* libCtx, unsigned char *msg, size_t msgLen, unsigned char *ciphertext, size_t cipherLen, int padMode, const EVP_MD *rsaMd, const EVP_MD *rsaMgf1Md); +/* Key-format helpers, used by every algorithm's tests. */ +int test_pki_cipher_encrypts(EVP_PKEY* pkey, const char* fmt, + const char* encProp, OSSL_LIB_CTX* decLibCtx, int cmpKey); +int test_epki_encode_decode(EVP_PKEY* pkey, const char* fmt, + const char* encProp, OSSL_LIB_CTX* decLibCtx); + #ifdef WP_HAVE_RSA int test_pkey_enc_rsa(EVP_PKEY *pkey, unsigned char *msg, size_t msgLen, unsigned char *ciphertext, size_t cipherLen, int padMode, @@ -323,8 +329,6 @@ int test_pkey_enc_rsa(EVP_PKEY *pkey, unsigned char *msg, size_t msgLen, int test_pkey_dec_rsa(EVP_PKEY *pkey, unsigned char *msg, size_t msgLen, unsigned char *ciphertext, size_t cipherLen, int padMode, const EVP_MD *rsaMd, const EVP_MD *rsaMgf1Md); -int test_epki_encode_decode(EVP_PKEY* pkey, const char* fmt, - const char* encProp, OSSL_LIB_CTX* decLibCtx); int test_rsa_sign_sha1(void *data); int test_rsa_sign_verify_pkcs1(void *data); int test_rsa_sign_verify_recover_pkcs1(void *data); @@ -362,7 +366,8 @@ int test_rsa_key_integrity(void* data); int test_dh_pgen_pkey(void *data); int test_dh_pkey(void *data); int test_dh_invalid_kdf_strings(void *data); -#if defined(WOLFSSL_DH_EXTRA) && defined(WP_HAVE_EPKI_TEST) +#if defined(WOLFSSL_DH_EXTRA) && defined(WP_HAVE_EPKI_TEST) && \ + !defined(HAVE_FIPS) int test_dh_encode_epki(void *data); #endif int test_dh_decode(void *data); From 2f6e94a227c65bf6dc021d1a0711cdbbb4393a4d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 3 Aug 2026 11:18:28 -0700 Subject: [PATCH 2/9] Keep DH PKCS8 helper with its declaration --- src/wp_dh_kmgmt.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/wp_dh_kmgmt.c b/src/wp_dh_kmgmt.c index f681cb55..1af6e5e1 100644 --- a/src/wp_dh_kmgmt.c +++ b/src/wp_dh_kmgmt.c @@ -2664,6 +2664,14 @@ static int wp_dh_sync_priv_to_key(const wp_Dh *dh) return ok; } +/* wolfSSL calculating it wrong. */ +static void wp_dh_fix_pki_len(unsigned char* keyData, word32 len) +{ + if (keyData[1] == 0x81) { + keyData[2] = (unsigned char)(len - 3); + } +} + /** * Get the PKCS#8 encoding size for the key. * @@ -2706,14 +2714,6 @@ static int wp_dh_encode_pki_size(const wp_Dh *dh, size_t* keyLen) * @return 1 on success. * @return 0 on failure. */ -/* wolfSSL calculating it wrong. */ -static void wp_dh_fix_pki_len(unsigned char* keyData, word32 len) -{ - if (keyData[1] == 0x81) { - keyData[2] = (unsigned char)(len - 3); - } -} - static int wp_dh_encode_pki(const wp_Dh *dh, unsigned char* keyData, size_t* keyLen) { From ae234028edac0bbef2ab401410d38e44df1179bb Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 3 Aug 2026 11:41:55 -0700 Subject: [PATCH 3/9] Tighten PKCS8 feature guards and test strings --- src/wp_internal.c | 10 ++++++---- test/test_pkey.c | 9 ++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/wp_internal.c b/src/wp_internal.c index b544e51b..fd52bd71 100644 --- a/src/wp_internal.c +++ b/src/wp_internal.c @@ -888,6 +888,7 @@ typedef struct wp_cipher { #endif /** wolfSSL compatible cipher names and wolfSSL identifiers. */ +#ifdef WP_HAVE_PKCS8_ENC static const wp_cipher wp_cipher_names[] = { { "AES-128-CBC", AES128CBCb, "AES-128-CBC" }, { "AES-192-CBC", AES192CBCb, "AES-192-CBC" }, @@ -900,6 +901,7 @@ static const wp_cipher wp_cipher_names[] = { /** Number of cipher names in table. */ #define WP_CIPHER_NAMES_LEN \ (sizeof(wp_cipher_names) / sizeof(*wp_cipher_names)) +#endif /** * Get the cipher based on the parameters in the array. @@ -991,7 +993,7 @@ int wp_cipher_from_params(const OSSL_PARAM params[], int* cipher, int wp_encrypt_key_pkcs8_size(WOLFPROV_CTX* provCtx, int cipher, word32 plainLen, size_t* outLen) { -#if defined(HAVE_PKCS8) && !defined(NO_PWDBASED) +#ifdef WP_HAVE_PKCS8_ENC int ok = 1; int rc = 0; word32 outSz = 0; @@ -1071,7 +1073,7 @@ int wp_encrypt_key_pkcs8(WOLFPROV_CTX* provCtx, int cipher, unsigned char* out, size_t* outLen, OSSL_PASSPHRASE_CALLBACK* pwCb, void* pwCbArg) { -#if defined(HAVE_PKCS8) && !defined(NO_PWDBASED) +#ifdef WP_HAVE_PKCS8_ENC int ok = 1; int rc = 0; word32 outSz = (word32)*outLen; @@ -1169,7 +1171,7 @@ int wp_encrypt_key_pkcs8(WOLFPROV_CTX* provCtx, int cipher, #endif } -#if defined(HAVE_PKCS8) && !defined(NO_PWDBASED) +#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[] = { 42, 134, 72, 134, 247, 13, 1, 5, 12 @@ -1211,7 +1213,7 @@ static int wp_is_pbkdf2_encrypted(const unsigned char* data, word32 len) int wp_decrypt_key_pkcs8(unsigned char* data, word32* len, OSSL_PASSPHRASE_CALLBACK* pwCb, void* pwCbArg) { -#if defined(HAVE_PKCS8) && !defined(NO_PWDBASED) +#ifdef WP_HAVE_PKCS8_ENC int ok = 1; int rc; #ifdef WOLFSSL_SMALL_STACK diff --git a/test/test_pkey.c b/test/test_pkey.c index fb18dd65..50919b06 100644 --- a/test/test_pkey.c +++ b/test/test_pkey.c @@ -336,7 +336,7 @@ int test_pki_cipher_encrypts(EVP_PKEY* pkey, const char* fmt, const unsigned char* pp; const char* pass = "wolfprov-test-pass"; const char* badPass = "wrong-passphrase"; - size_t passLen = strlen(pass); + size_t passLen = XSTRLEN(pass); static const char epkiHdr[] = "-----BEGIN ENCRYPTED PRIVATE KEY-----"; ectx = OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_KEYPAIR, fmt, @@ -398,7 +398,7 @@ int test_pki_cipher_encrypts(EVP_PKEY* pkey, const char* fmt, } if (err == 0) { err = OSSL_DECODER_CTX_set_passphrase(bctx, - (const unsigned char*)badPass, strlen(badPass)) != 1; + (const unsigned char*)badPass, XSTRLEN(badPass)) != 1; } if (err == 0) { if ((OSSL_DECODER_from_data(bctx, &pp, &dataLen) == 1) && @@ -437,7 +437,7 @@ int test_epki_encode_decode(EVP_PKEY* pkey, const char* fmt, const unsigned char* pp; const char* pass = "wolfprov-test-pass"; const char* badPass = "wrong-passphrase"; - size_t passLen = strlen(pass); + size_t passLen = XSTRLEN(pass); /* Encode as EncryptedPrivateKeyInfo with the requested provider. */ ectx = OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_KEYPAIR, fmt, @@ -489,7 +489,7 @@ int test_epki_encode_decode(EVP_PKEY* pkey, const char* fmt, } if (err == 0) { err = OSSL_DECODER_CTX_set_passphrase(bctx, - (const unsigned char*)badPass, strlen(badPass)) != 1; + (const unsigned char*)badPass, XSTRLEN(badPass)) != 1; } if (err == 0) { /* Decode is expected to fail; success with a recovered key is wrong. */ @@ -510,4 +510,3 @@ int test_epki_encode_decode(EVP_PKEY* pkey, const char* fmt, return err; } - From 571e5fa2a0958224abbf997867b46bec38f25bc0 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 3 Aug 2026 11:50:37 -0700 Subject: [PATCH 4/9] Test unsupported PKCS8 cipher rejection --- test/test_pkey.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/test_pkey.c b/test/test_pkey.c index 50919b06..a25ff176 100644 --- a/test/test_pkey.c +++ b/test/test_pkey.c @@ -328,6 +328,7 @@ int test_pki_cipher_encrypts(EVP_PKEY* pkey, const char* fmt, EVP_PKEY* pkey2 = NULL; EVP_PKEY* badKey = NULL; OSSL_ENCODER_CTX* ectx = NULL; + OSSL_ENCODER_CTX* badEctx = NULL; OSSL_DECODER_CTX* dctx = NULL; OSSL_DECODER_CTX* bctx = NULL; unsigned char* data = NULL; @@ -342,6 +343,16 @@ int test_pki_cipher_encrypts(EVP_PKEY* pkey, const char* fmt, ectx = OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_KEYPAIR, fmt, "PrivateKeyInfo", encProp); err = (ectx == NULL); + if (err == 0) { + /* Unsupported ciphers must be rejected instead of falling back to + * plaintext output. */ + badEctx = OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_KEYPAIR, fmt, + "PrivateKeyInfo", encProp); + err = (badEctx == NULL) || + (OSSL_ENCODER_CTX_set_cipher(badEctx, "unsupported-cipher", + NULL) == 1); + } + OSSL_ENCODER_CTX_free(badEctx); if (err == 0) { err = OSSL_ENCODER_CTX_set_cipher(ectx, "AES-256-CBC", NULL) != 1; } From 12583a33bad121aab25c325c846a45073cfdf908 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 3 Aug 2026 11:53:30 -0700 Subject: [PATCH 5/9] Test ECC type-specific cipher rejection --- test/test_ecc.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/test_ecc.c b/test/test_ecc.c index ef272e60..20bb0da6 100644 --- a/test/test_ecc.c +++ b/test/test_ecc.c @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -945,6 +946,9 @@ int test_ecc_encode_epki(void *data) const unsigned char* op = ecc_key_der_256; EVP_PKEY* pkey = NULL; EVP_PKEY* osslKey = NULL; + OSSL_ENCODER_CTX* typeEctx = NULL; + unsigned char* typeData = NULL; + size_t typeLen = 0; (void)data; @@ -953,6 +957,18 @@ int test_ecc_encode_epki(void *data) wpLibCtx, NULL); err = (pkey == NULL); + if (err == 0) { + /* Type-specific ECC encoding must reject a cipher rather than emit + * an unencrypted private key. */ + typeEctx = OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_PRIVATE_KEY, + "DER", "type-specific", "provider=libwolfprov"); + err = (typeEctx == NULL) || + (OSSL_ENCODER_CTX_set_cipher(typeEctx, "AES-256-CBC", NULL) != 1) || + (OSSL_ENCODER_to_data(typeEctx, &typeData, &typeLen) == 1); + } + OSSL_ENCODER_CTX_free(typeEctx); + OPENSSL_free(typeData); + if (err == 0) { PRINT_MSG("EncryptedPrivateKeyInfo DER: wolfProvider -> OpenSSL"); err = test_epki_encode_decode(pkey, "DER", "provider=libwolfprov", From 91b99668aaea3f7d360d9e3aa828770ad0c67bfd Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 3 Aug 2026 14:42:25 -0700 Subject: [PATCH 6/9] Make ECC cipher rejection test OpenSSL 3.0 compatible --- test/test_ecc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_ecc.c b/test/test_ecc.c index 20bb0da6..e9fac333 100644 --- a/test/test_ecc.c +++ b/test/test_ecc.c @@ -960,7 +960,7 @@ int test_ecc_encode_epki(void *data) if (err == 0) { /* Type-specific ECC encoding must reject a cipher rather than emit * an unencrypted private key. */ - typeEctx = OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_PRIVATE_KEY, + typeEctx = OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_KEYPAIR, "DER", "type-specific", "provider=libwolfprov"); err = (typeEctx == NULL) || (OSSL_ENCODER_CTX_set_cipher(typeEctx, "AES-256-CBC", NULL) != 1) || From ba4b46246a05b06972c07c7a91770adb44ed85ac Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 3 Aug 2026 14:42:25 -0700 Subject: [PATCH 7/9] Add PKCS8 CLI encryption coverage --- scripts/cmd_test/do-cmd-tests.sh | 21 ++++++ scripts/cmd_test/pkcs8-cmd-test.sh | 103 +++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100755 scripts/cmd_test/pkcs8-cmd-test.sh diff --git a/scripts/cmd_test/do-cmd-tests.sh b/scripts/cmd_test/do-cmd-tests.sh index c79a4170..42d9e468 100755 --- a/scripts/cmd_test/do-cmd-tests.sh +++ b/scripts/cmd_test/do-cmd-tests.sh @@ -33,6 +33,7 @@ RUN_AES=0 RUN_RSA=0 RUN_ECC=0 RUN_REQ=0 +RUN_PKCS8=0 RUN_ALL=1 show_help() { @@ -50,6 +51,7 @@ TESTS (if none specified, all tests run): rsa Run RSA key generation test ecc Run ECC key generation test req Run certificate request test + pkcs8 Run PKCS#8 encryption command test ENVIRONMENT VARIABLES: OPENSSL_BIN Path to OpenSSL binary (auto-detected with which(openssl) if not set) @@ -92,6 +94,11 @@ while [[ $# -gt 0 ]]; do RUN_ALL=0 shift ;; + pkcs8) + RUN_PKCS8=1 + RUN_ALL=0 + shift + ;; *) echo "Unknown option: $1" echo "Use --help for usage information" @@ -107,6 +114,7 @@ if [ $RUN_ALL -eq 1 ]; then RUN_RSA=1 RUN_ECC=1 RUN_REQ=1 + RUN_PKCS8=1 fi source "${CMD_TEST_DIR}/cmd-test-common.sh" @@ -168,6 +176,7 @@ AES_RESULT=0 RSA_RESULT=0 ECC_RESULT=0 REQ_RESULT=0 +PKCS8_RESULT=0 # Run the hash comparison test if [ $RUN_HASH -eq 1 ]; then @@ -204,6 +213,12 @@ if [ $RUN_REQ -eq 1 ]; then REQ_RESULT=$? fi +if [ $RUN_PKCS8 -eq 1 ]; then + echo -e "\n=== Running PKCS#8 Command Test ===" + "${REPO_ROOT}/scripts/cmd_test/pkcs8-cmd-test.sh" + PKCS8_RESULT=$? +fi + # Check results ALL_PASSED=1 if [ $RUN_HASH -eq 1 ] && [ $HASH_RESULT -ne 0 ]; then @@ -221,6 +236,9 @@ fi if [ $RUN_REQ -eq 1 ] && [ $REQ_RESULT -ne 0 ]; then ALL_PASSED=0 fi +if [ $RUN_PKCS8 -eq 1 ] && [ $PKCS8_RESULT -ne 0 ]; then + ALL_PASSED=0 +fi if [ $ALL_PASSED -eq 1 ]; then echo -e "\n=== All Command-Line Tests Passed ===" @@ -256,5 +274,8 @@ fi if [ $RUN_REQ -eq 1 ]; then echo "REQ Test Result: $REQ_RESULT (0=success)" fi +if [ $RUN_PKCS8 -eq 1 ]; then + echo "PKCS#8 Test Result: $PKCS8_RESULT (0=success)" +fi exit $((1 - ALL_PASSED)) diff --git a/scripts/cmd_test/pkcs8-cmd-test.sh b/scripts/cmd_test/pkcs8-cmd-test.sh new file mode 100755 index 00000000..18ca0156 --- /dev/null +++ b/scripts/cmd_test/pkcs8-cmd-test.sh @@ -0,0 +1,103 @@ +#!/bin/bash +# Verify that the pkey command honors -aes256 for provider private-key encoders. + +CMD_TEST_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +source "${CMD_TEST_DIR}/cmd-test-common.sh" +source "${CMD_TEST_DIR}/clean-cmd-test.sh" + +if [ -z "${DO_CMD_TESTS:-}" ]; then + echo "This script is designed to be called from do-cmd-tests.sh" + exit 1 +fi + +cmd_test_init "pkcs8-cmd-test.log" +clean_cmd_test "pkcs8" +mkdir -p pkcs8_outputs +FAIL=0 + +test_pkey_cipher() { + local name=$1 + local keygen=$2 + local input="pkcs8_outputs/${name}.pem" + local output="pkcs8_outputs/${name}-encrypted.pem" + + use_wolf_provider + if ! eval "$keygen" >"$input" 2>/dev/null; then + echo "[FAIL] ${name} key generation failed" + FAIL=1 + return + fi + if ! $OPENSSL_BIN pkey -aes256 -passout pass:wolfprov-test-pass \ + -in "$input" -out "$output" 2>/dev/null; then + echo "[FAIL] ${name} pkey encryption failed" + FAIL=1 + return + fi + if ! grep -q "BEGIN ENCRYPTED PRIVATE KEY" "$output"; then + echo "[FAIL] ${name} pkey -aes256 produced an unencrypted key" + FAIL=1 + else + echo "[PASS] ${name} pkey -aes256 produced EncryptedPrivateKeyInfo" + check_force_fail + fi +} + +test_pkey_cipher "ec" "$OPENSSL_BIN genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1" +if [ "${WOLFSSL_ISFIPS:-0}" != "1" ] && + $OPENSSL_BIN list -public-key-algorithms -provider libwolfprov 2>/dev/null | + grep -q "ED25519"; then + test_pkey_cipher "ed25519" "$OPENSSL_BIN genpkey -algorithm ED25519" +else + echo "[SKIP] Ed25519 is unavailable in this OpenSSL/provider build" +fi +if [ "${WOLFSSL_ISFIPS:-0}" != "1" ] && + $OPENSSL_BIN list -public-key-algorithms -provider libwolfprov 2>/dev/null | + grep -q "X25519"; then + test_pkey_cipher "x25519" "$OPENSSL_BIN genpkey -algorithm X25519" +else + echo "[SKIP] X25519 is unavailable in this OpenSSL/provider build" +fi +test_pkey_cipher "rsa" "$OPENSSL_BIN genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048" + +# DH requires a generated parameter file before the private key can be made. +use_wolf_provider +if $OPENSSL_BIN genpkey -genparam -algorithm DH \ + -pkeyopt dh_paramgen_prime_len:2048 \ + -out pkcs8_outputs/dh-params.pem 2>/dev/null && + test_pkey_cipher "dh" "$OPENSSL_BIN genpkey -paramfile pkcs8_outputs/dh-params.pem"; then + : +else + echo "[FAIL] DH PKCS#8 command test failed" + FAIL=1 +fi + +# OpenSSL versions before 3.6 do not provide the standardized PQC names. +for algorithm in ML-DSA-44 ML-DSA-65 ML-DSA-87; do + if $OPENSSL_BIN list -signature-algorithms -provider libwolfprov 2>/dev/null | + grep -q "${algorithm}"; then + test_pkey_cipher "${algorithm}" \ + "$OPENSSL_BIN genpkey -algorithm ${algorithm}" + else + echo "[SKIP] ${algorithm} is unavailable in this OpenSSL/provider build" + fi +done + +for algorithm in ML-KEM-512 ML-KEM-768 ML-KEM-1024; do + if $OPENSSL_BIN list -kem-algorithms -provider libwolfprov 2>/dev/null | + grep -q "${algorithm}"; then + test_pkey_cipher "${algorithm}" \ + "$OPENSSL_BIN genpkey -algorithm ${algorithm}" + else + echo "[SKIP] ${algorithm} is unavailable in this OpenSSL/provider build" + fi +done + +if [ "${WOLFPROV_FORCE_FAIL:-0}" = "1" ]; then + if [ "$FORCE_FAIL_PASSED" -eq 1 ]; then + echo "[FAIL] PKCS#8 command tests unexpectedly passed with force-fail" + exit 1 + fi +elif [ "$FAIL" -ne 0 ]; then + exit 1 +fi +exit 0 From 93ee317e951930619e646ac36dee1b0a5dce245a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 3 Aug 2026 21:06:31 -0700 Subject: [PATCH 8/9] Wait for OpenSSL test server readiness --- scripts/test-wp-cs.sh | 48 ++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/scripts/test-wp-cs.sh b/scripts/test-wp-cs.sh index 99a8cf55..225b97be 100755 --- a/scripts/test-wp-cs.sh +++ b/scripts/test-wp-cs.sh @@ -36,21 +36,33 @@ prepend() { # Usage: cmd 2>&1 | prepend "sometext " while read line; do echo "${1}${line}"; done } -check_process_running() { - if [ "$1" = "-1" ]; then - echo 1 - else - ps -p $1 > /dev/null - echo $? - fi -} - kill_servers() { if [ "$(jobs -p)" != "" ]; then - kill $(jobs -p) + kill $(jobs -p) 2>/dev/null || true fi } +wait_for_server() { + local log_offset=$1 + local retries=100 + + while [ "$retries" -gt 0 ]; do + if tail -c "+$((log_offset + 1))" "$LOG_FILE" 2>/dev/null | + grep -q "\[server\] ACCEPT"; then + return 0 + fi + if ! kill -0 "$OPENSSL_SERVER_PID" 2>/dev/null; then + printf "OpenSSL server exited before accepting connections\n" + return 1 + fi + retries=$((retries - 1)) + sleep 0.1 + done + + printf "OpenSSL server did not accept connections within 10 seconds\n" + return 1 +} + do_cleanup() { sleep 0.5 # flush buffers kill_servers @@ -183,19 +195,20 @@ generate_port() { } start_openssl_server() { # usage: start_openssl_server [extraArgs] + local log_offset + kill_servers + log_offset=$(wc -c < "$LOG_FILE") stdbuf -oL -eL $OPENSSL_BIN s_server -www $1 \ -cert $CERT_DIR/server-cert.pem -key $CERT_DIR/server-key.pem \ -dcert $CERT_DIR/server-ecc.pem -dkey $CERT_DIR/ecc-key.pem \ -accept $OPENSSL_PORT $OPENSSL_ALL_CIPHERS \ 2>&1 | prepend "[server] " >>$LOG_FILE & - OPENSSL_SERVER_PID=$(($! - 1)) - - sleep 0.5 + OPENSSL_SERVER_PID=$! - if [ $(check_process_running $OPENSSL_SERVER_PID) != "0" ]; then - printf "OpenSSL server might have failed to start (PID=$OPENSSL_SERVER_PID)\n" + if ! wait_for_server "$log_offset"; then + return 1 fi } @@ -304,12 +317,13 @@ if [ "${AM_BWRAPPED-}" != "yes" ]; then fi printf "Client testing\n" | tee $LOG_FILE -start_openssl_server +start_openssl_server || exit 1 do_client_test "-provider-path $WOLFPROV_PATH -provider $WOLFPROV_NAME" kill_servers printf "Server testing\n" | tee -a $LOG_FILE -start_openssl_server "-provider-path $WOLFPROV_PATH -provider $WOLFPROV_NAME" +start_openssl_server \ + "-provider-path $WOLFPROV_PATH -provider $WOLFPROV_NAME" || exit 1 do_client_test kill_servers From f3908dfb5ca59e4de68fe7f061ad85ff5953d418 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 5 Aug 2026 09:52:28 -0700 Subject: [PATCH 9/9] Address PKCS8 security review findings --- scripts/cmd_test/pkcs8-cmd-test.sh | 6 +- src/wp_dec_epki2pki.c | 94 ++++++++---- src/wp_dec_pem2der.c | 16 ++- src/wp_dh_kmgmt.c | 221 +++++++++++++---------------- src/wp_ecc_kmgmt.c | 2 + src/wp_internal.c | 82 ++++++++--- src/wp_rsa_kmgmt.c | 3 +- test/test_ecc.c | 21 ++- test/test_pkey.c | 46 +++++- test/test_rsa.c | 30 ++++ 10 files changed, 336 insertions(+), 185 deletions(-) diff --git a/scripts/cmd_test/pkcs8-cmd-test.sh b/scripts/cmd_test/pkcs8-cmd-test.sh index 18ca0156..04efff3b 100755 --- a/scripts/cmd_test/pkcs8-cmd-test.sh +++ b/scripts/cmd_test/pkcs8-cmd-test.sh @@ -25,21 +25,23 @@ test_pkey_cipher() { if ! eval "$keygen" >"$input" 2>/dev/null; then echo "[FAIL] ${name} key generation failed" FAIL=1 - return + return 1 fi if ! $OPENSSL_BIN pkey -aes256 -passout pass:wolfprov-test-pass \ -in "$input" -out "$output" 2>/dev/null; then echo "[FAIL] ${name} pkey encryption failed" FAIL=1 - return + return 1 fi if ! grep -q "BEGIN ENCRYPTED PRIVATE KEY" "$output"; then echo "[FAIL] ${name} pkey -aes256 produced an unencrypted key" FAIL=1 + return 1 else echo "[PASS] ${name} pkey -aes256 produced EncryptedPrivateKeyInfo" check_force_fail fi + return 0 } test_pkey_cipher "ec" "$OPENSSL_BIN genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1" diff --git a/src/wp_dec_epki2pki.c b/src/wp_dec_epki2pki.c index c31b98b1..9f204ba4 100644 --- a/src/wp_dec_epki2pki.c +++ b/src/wp_dec_epki2pki.c @@ -18,6 +18,9 @@ * along with wolfProvider. If not, see . */ +#include + +#include #include #include #include @@ -71,6 +74,67 @@ static void wp_epki2pki_freectx(wp_Epki2Pki* ctx) OPENSSL_free(ctx); } +static int wp_epki2pki_get_object(const unsigned char** data, + const unsigned char* end, int expectedTag, const unsigned char** objEnd) +{ + int ok = 1; + int flags; + int tag; + int cls; + long len; + size_t available; + + if ((data == NULL) || (*data == NULL) || (end < *data)) { + ok = 0; + } + if (ok) { + available = (size_t)(end - *data); + if (available > LONG_MAX) { + ok = 0; + } + } + if (ok) { + flags = ASN1_get_object(data, &len, &tag, &cls, (long)available); + if (((flags & 0x80) != 0) || (len < 0) || + (cls != V_ASN1_UNIVERSAL) || (tag != expectedTag) || + ((size_t)len > (size_t)(end - *data))) { + ok = 0; + } + } + if (ok) { + *objEnd = *data + len; + } + + return ok; +} + +static int wp_epki2pki_is_epki(const unsigned char* data, word32 len) +{ + int ok = 1; + const unsigned char* p = data; + const unsigned char* end = data + len; + const unsigned char* seqEnd = NULL; + const unsigned char* objEnd = NULL; + + if (!wp_epki2pki_get_object(&p, end, V_ASN1_SEQUENCE, &seqEnd) || + (seqEnd != end)) { + ok = 0; + } + if (ok && !wp_epki2pki_get_object(&p, seqEnd, V_ASN1_SEQUENCE, + &objEnd)) { + ok = 0; + } + if (ok) { + p = objEnd; + if (!wp_epki2pki_get_object(&p, seqEnd, V_ASN1_OCTET_STRING, + &objEnd) || (objEnd != seqEnd)) { + ok = 0; + } + } + + return ok; +} + #if LIBWOLFSSL_VERSION_HEX < 0x05000000 /** * Password callback data. @@ -199,7 +263,6 @@ static int wp_epki2pki_decode(wp_Epki2Pki* ctx, OSSL_CORE_BIO* coreBio, word32 len = 0; char password[1024]; size_t passwordLen = 0; - word32 tradIdx = 0; WOLFPROV_ENTER(WP_LOG_COMP_PK, "wp_epki2pki_decode"); @@ -214,30 +277,13 @@ static int wp_epki2pki_decode(wp_Epki2Pki* ctx, OSSL_CORE_BIO* coreBio, else if (data == NULL) { done = 1; } - if ((!done) && ok && wc_GetPkcs8TraditionalOffset(data, &tradIdx, (word32)len) <= 0) { - /* This is not PKCS8, we are done */ + if ((!done) && ok && !wp_epki2pki_is_epki(data, len)) { done = 1; - ok = 1; } - if ((!done) && ok) { - /* Try decrypting without password and look for ASN_PARSE_E to indicate - * that the format is not PKCS#8 encrypted. - * TODO: should be parsing the structure without decrypting to - * determine it is encrypted PKCS#8. - */ - #if LIBWOLFSSL_VERSION_HEX >= 0x05000000 - rc = wc_DecryptPKCS8Key(data, len, password, 0); - #else - rc = wp_DecryptPKCS8Key(data, len, password, 0); - #endif - if (rc == ASN_PARSE_E) { - done = 1; - ok = 1; - } - } - if ((!done) && ok && (!pwCb(password, sizeof(password), &passwordLen, NULL, - pwCbArg))) { - done = 1; + if ((!done) && ok && ((pwCb == NULL) || + (!pwCb(password, sizeof(password), &passwordLen, NULL, + pwCbArg)))) { + ok = 0; } if ((!done) && ok && (passwordLen > sizeof(password))) { ok = 0; @@ -286,5 +332,3 @@ const OSSL_DISPATCH wp_epki_to_pki_decoder_functions[] = { { OSSL_FUNC_DECODER_DECODE, (DFUNC)wp_epki2pki_decode }, { 0, NULL } }; - - diff --git a/src/wp_dec_pem2der.c b/src/wp_dec_pem2der.c index 100b3b43..abebc1f9 100644 --- a/src/wp_dec_pem2der.c +++ b/src/wp_dec_pem2der.c @@ -323,15 +323,21 @@ static int wp_pem2der_decode_data(const unsigned char* data, word32 len, else if (XMEMCMP(data, "-----BEGIN ENCRYPTED PRIVATE KEY-----", 37) == 0) { type = PKCS8_ENC_PRIVATEKEY_TYPE; dataType = NULL; - dataFormat = "PrivateKeyInfo"; obj = OSSL_OBJECT_PKEY; - /* The body is base64 only; the PBES2 layer is decrypted later by the - * EncryptedPrivateKeyInfo decoder. The callback fields exist only when - * wolfSSL itself was built with encrypted-key support. */ #ifdef WOLFSSL_ENCRYPTED_KEYS + /* wc_PemToDer decrypts in place through the password callback. */ + dataFormat = "PrivateKeyInfo"; info.passwd_cb = wp_pem_password_cb; info.passwd_userdata = (void*)&wpPwCb; +#else + /* Preserve the encrypted structure for the EPKI decoder. */ + dataFormat = "EncryptedPrivateKeyInfo"; + if (!wp_pem2der_convert((const char*)data, len, &der, &info, + XSTRLEN("ENCRYPTED PRIVATE KEY"))) { + ok = 0; + } + done = 1; #endif } else { @@ -469,5 +475,3 @@ const OSSL_DISPATCH wp_pem_to_der_decoder_functions[] = { { OSSL_FUNC_DECODER_DECODE, (DFUNC)wp_pem2der_decode }, { 0, NULL } }; - - diff --git a/src/wp_dh_kmgmt.c b/src/wp_dh_kmgmt.c index 1af6e5e1..81b099e5 100644 --- a/src/wp_dh_kmgmt.c +++ b/src/wp_dh_kmgmt.c @@ -69,6 +69,8 @@ struct wp_Dh { #ifndef WP_SINGLE_THREADED /** Mutex for reference count updating. */ wolfSSL_Mutex mutex; + /** Mutex for key data access and synchronization. */ + wolfSSL_Mutex keyMutex; #endif /** Count of references to this object. */ int refCnt; @@ -414,6 +416,16 @@ static wp_Dh* wp_dh_new(WOLFPROV_CTX *provCtx) ok = 0; } } + if (ok) { + rc = wc_InitMutex(&dh->keyMutex); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, + "wc_InitMutex", rc); + wc_FreeMutex(&dh->mutex); + wc_FreeDhKey(&dh->key); + ok = 0; + } + } #endif if (ok) { dh->refCnt = 1; @@ -461,6 +473,7 @@ void wp_dh_free(wp_Dh* dh) OPENSSL_free(dh->pub); OPENSSL_clear_free(dh->priv, dh->privSz); #ifndef WP_SINGLE_THREADED + wc_FreeMutex(&dh->keyMutex); wc_FreeMutex(&dh->mutex); #endif wc_FreeDhKey(&dh->key); @@ -2626,10 +2639,6 @@ static int wp_dh_encode_spki(const wp_Dh *dh, unsigned char* keyData, } if (ok) { *keyLen = len; - /* wolfSSL calculating it wrong. */ - if (keyData[1] == 0x81) { - keyData[2] = len - 3; - } } WOLFPROV_LEAVE(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); @@ -2638,12 +2647,13 @@ static int wp_dh_encode_spki(const wp_Dh *dh, unsigned char* keyData, /** * Copy a generated private key into the inner wolfSSL key if not already set. + * The caller must hold dh->keyMutex. * * @param [in] dh DH key object. * @return 1 on success. * @return 0 on failure. */ -static int wp_dh_sync_priv_to_key(const wp_Dh *dh) +static int wp_dh_sync_priv_to_key(const wp_Dh* dh) { int ok = 1; int ret; @@ -2652,26 +2662,18 @@ static int wp_dh_sync_priv_to_key(const wp_Dh *dh) /* If we have a generated private key that is not set in the inner key, * set it now */ - if (mp_bitsused(&dh->key.priv) == 0 && dh->priv != NULL && dh->privSz > 0) { + if (ok && (mp_bitsused(&dh->key.priv) == 0) && (dh->priv != NULL) && + (dh->privSz > 0)) { ret = wc_DhImportKeyPair((DhKey*)&dh->key, dh->priv, (word32)dh->privSz, dh->pub, (word32)dh->pubSz); if (ret != 0) { ok = 0; } } - WOLFPROV_LEAVE(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } -/* wolfSSL calculating it wrong. */ -static void wp_dh_fix_pki_len(unsigned char* keyData, word32 len) -{ - if (keyData[1] == 0x81) { - keyData[2] = (unsigned char)(len - 3); - } -} - /** * Get the PKCS#8 encoding size for the key. * @@ -2729,113 +2731,12 @@ static int wp_dh_encode_pki(const wp_Dh *dh, unsigned char* keyData, } if (ok) { *keyLen = len; - wp_dh_fix_pki_len(keyData, len); } WOLFPROV_LEAVE(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } -#ifdef WP_HAVE_PKCS8_ENC -/** - * Get the Encrypted PKCS#8 encoding size for the key. - * - * @param [in] ctx DH encoder/decoder context object. - * @param [in] dh DH key object. - * @param [out] keyLen Length of encoding in bytes. - * @return 1 on success. - * @return 0 on failure. - */ -static int wp_dh_encode_epki_size(const wp_DhEncDecCtx* ctx, const wp_Dh *dh, - size_t* keyLen) -{ - int ok = 1; - int ret; - word32 len; - - WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_encode_epki_size"); - - ok = wp_dh_sync_priv_to_key(dh); - - if (ok) { - /* Get the plaintext PKCS #8 length. */ - ret = wc_DhPrivKeyToDer((DhKey*)&dh->key, NULL, &len); - if (ret != LENGTH_ONLY_E) { - ok = 0; - } - } - if (ok) { - /* Get the size of the PBES2 EncryptedPrivateKeyInfo encoding. */ - ok = wp_encrypt_key_pkcs8_size(ctx->provCtx, ctx->cipher, len, keyLen); - } - - WOLFPROV_LEAVE(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); - return ok; -} - -/** - * Encode the DH key in a PBES2 EncryptedPrivateKeyInfo format. - * - * @param [in] ctx DH encoder/decoder context object. - * @param [in] dh DH key object. - * @param [out] keyData Buffer to hold encoded data. - * @param [in, out] keyLen On in, length of buffer in bytes. - * On out, length of encoding in bytes. - * @param [in] pwCb Password callback. - * @param [in] pwCbArg Argument to pass to password callback. - * @return 1 on success. - * @return 0 on failure. - */ -static int wp_dh_encode_epki(const wp_DhEncDecCtx* ctx, const wp_Dh *dh, - unsigned char* keyData, size_t* keyLen, OSSL_PASSPHRASE_CALLBACK *pwCb, - void *pwCbArg) -{ - int ok = 1; - int rc; - word32 pkcs8Len = 0; - byte* encodedKey = NULL; - - WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_encode_epki"); - - /* Determine the plaintext PKCS #8 length. */ - rc = wc_DhPrivKeyToDer((DhKey*)&dh->key, NULL, &pkcs8Len); - if (rc != LENGTH_ONLY_E) { - ok = 0; - } - if (ok) { - /* Allocate the plaintext buffer - must differ from the output. */ - encodedKey = OPENSSL_malloc(pkcs8Len); - if (encodedKey == NULL) { - ok = 0; - } - } - if (ok) { - /* Encode the plaintext PKCS #8 key. */ - rc = wc_DhPrivKeyToDer((DhKey*)&dh->key, encodedKey, &pkcs8Len); - if (rc <= 0) { - ok = 0; - } - } - if (ok) { - /* Same length correction the plaintext encoder applies, so the - * encrypted body wraps an identical PKCS#8. */ - wp_dh_fix_pki_len(encodedKey, pkcs8Len); - } - if (ok) { - /* Encrypt as a PBES2 EncryptedPrivateKeyInfo. */ - ok = wp_encrypt_key_pkcs8(ctx->provCtx, ctx->cipher, encodedKey, - pkcs8Len, keyData, keyLen, pwCb, pwCbArg); - } - - /* encodedKey holds the plaintext PKCS#8 private key before encryption. */ - if (encodedKey != NULL) { - OPENSSL_clear_free(encodedKey, pkcs8Len); - } - - WOLFPROV_LEAVE(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); - return ok; -} -#endif #endif /** @@ -2870,22 +2771,47 @@ static int wp_dh_encode(wp_DhEncDecCtx* ctx, OSSL_CORE_BIO *cBio, size_t keyLen; unsigned char* derData = NULL; size_t derLen = 0; + size_t derAllocLen = 0; + unsigned char* plainData = NULL; + size_t plainLen = 0; + size_t plainAllocLen = 0; unsigned char* pemData = NULL; size_t pemLen = 0; int pemType = DH_PRIVATEKEY_TYPE; int private = 0; + int encrypted = 0; + int locked = 0; +#ifndef WP_SINGLE_THREADED + wolfSSL_Mutex* keyMutex = (key == NULL) ? NULL : + (wolfSSL_Mutex*)&key->keyMutex; +#else + wolfSSL_Mutex* keyMutex = NULL; +#endif if (out == NULL) { ok = 0; } + if (ok && (key == NULL)) { + ok = 0; + } + if (ok && (wp_lock(keyMutex) != 1)) { + ok = 0; + } + else if (ok) { + locked = 1; + } (void)params; (void)selection; (void)pwCb; (void)pwCbArg; + if (ok && (ctx->format == WP_ENC_FORMAT_TYPE_SPECIFIC) && + ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0) && + (ctx->cipherName != NULL)) { + ok = 0; + } if (ok && (ctx->format == WP_ENC_FORMAT_TYPE_SPECIFIC)) { - private = 1; if (!wp_dh_encode_params_size(key, &derLen)) { ok = 0; } @@ -2900,7 +2826,8 @@ static int wp_dh_encode(wp_DhEncDecCtx* ctx, OSSL_CORE_BIO *cBio, #ifdef WP_HAVE_PKCS8_ENC /* A cipher on a PrivateKeyInfo encoder selects the encrypted form. */ if (ctx->cipherName != NULL) { - if (!wp_dh_encode_epki_size(ctx, key, &derLen)) { + encrypted = 1; + if (!wp_dh_encode_pki_size(key, &derLen)) { ok = 0; } } @@ -2913,15 +2840,16 @@ static int wp_dh_encode(wp_DhEncDecCtx* ctx, OSSL_CORE_BIO *cBio, #ifdef WP_HAVE_PKCS8_ENC else if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { private = 1; - if (!wp_dh_encode_epki_size(ctx, key, &derLen)) { + encrypted = 1; + if (!wp_dh_encode_pki_size(key, &derLen)) { ok = 0; } } #endif if (ok) { - keyLen = derLen; - keyData = derData = OPENSSL_malloc(derLen); + keyLen = derAllocLen = derLen; + keyData = derData = OPENSSL_malloc(derAllocLen); if (derData == NULL) { ok = 0; } @@ -2944,8 +2872,7 @@ static int wp_dh_encode(wp_DhEncDecCtx* ctx, OSSL_CORE_BIO *cBio, #ifdef WP_HAVE_PKCS8_ENC if (ctx->cipherName != NULL) { pemType = PKCS8_ENC_PRIVATEKEY_TYPE; - if (!wp_dh_encode_epki(ctx, key, derData, &derLen, pwCb, - pwCbArg)) { + if (!wp_dh_encode_pki(key, derData, &derLen)) { ok = 0; } } @@ -2959,11 +2886,55 @@ static int wp_dh_encode(wp_DhEncDecCtx* ctx, OSSL_CORE_BIO *cBio, else if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { private = 1; pemType = PKCS8_ENC_PRIVATEKEY_TYPE; - if (!wp_dh_encode_epki(ctx, key, derData, &derLen, pwCb, pwCbArg)) { + if (!wp_dh_encode_pki(key, derData, &derLen)) { + ok = 0; + } + } +#endif + + if (ok && (derLen > derAllocLen)) { + ok = 0; + } + if (locked) { + wp_unlock(keyMutex); + locked = 0; + } + +#ifdef WP_HAVE_PKCS8_ENC + if (encrypted) { + plainData = derData; + plainLen = derLen; + plainAllocLen = derAllocLen; + derData = NULL; + derLen = 0; + derAllocLen = 0; + } + if (ok && encrypted && (!WP_FITS_WORD32(plainLen) || + !wp_encrypt_key_pkcs8_size(ctx->provCtx, ctx->cipher, + (word32)plainLen, &derLen))) { + ok = 0; + } + if (ok && encrypted) { + derAllocLen = derLen; + derData = OPENSSL_malloc(derAllocLen); + if (derData == NULL) { + ok = 0; + } + } + if (ok && encrypted) { + if (!wp_encrypt_key_pkcs8(ctx->provCtx, ctx->cipher, plainData, + (word32)plainLen, derData, &derLen, pwCb, pwCbArg)) { ok = 0; } } + OPENSSL_clear_free(plainData, plainAllocLen); +#else + (void)encrypted; + (void)plainData; + (void)plainLen; + (void)plainAllocLen; #endif + keyData = derData; if (ok && (ctx->encoding == WP_FORMAT_DER)) { keyLen = derLen; @@ -3001,7 +2972,7 @@ static int wp_dh_encode(wp_DhEncDecCtx* ctx, OSSL_CORE_BIO *cBio, } if (private) { - OPENSSL_clear_free(derData, derLen); + OPENSSL_clear_free(derData, derAllocLen); OPENSSL_clear_free(pemData, pemLen); } else { diff --git a/src/wp_ecc_kmgmt.c b/src/wp_ecc_kmgmt.c index 8a533bad..f566f0fa 100644 --- a/src/wp_ecc_kmgmt.c +++ b/src/wp_ecc_kmgmt.c @@ -2937,6 +2937,8 @@ static int wp_ecc_encode(wp_EccEncDecCtx* ctx, OSSL_CORE_BIO *cBio, (ctx->format == WP_ENC_FORMAT_X9_62)) && (ctx->cipherName != NULL) && ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0)) { + WOLFPROV_ERROR_MSG(WP_LOG_COMP_ECC, + "encrypted traditional EC encoding is not supported"); ok = 0; } diff --git a/src/wp_internal.c b/src/wp_internal.c index fd52bd71..513b7349 100644 --- a/src/wp_internal.c +++ b/src/wp_internal.c @@ -906,7 +906,9 @@ static const wp_cipher wp_cipher_names[] = { /** * Get the cipher based on the parameters in the array. * - * A parameter with the name of the cipher may not be in the array. + * An absent cipher parameter leaves the outputs unchanged. A NULL cipher + * value clears the outputs and succeeds. An invalid or unsupported cipher + * clears the outputs and fails. * * @param [in] params Array of parameters and values. * @param [out] cipher wolfSSL cipher identifier. @@ -925,6 +927,10 @@ int wp_cipher_from_params(const OSSL_PARAM params[], int* cipher, p = OSSL_PARAM_locate_const(params, OSSL_ALG_PARAM_CIPHER); if (p != NULL) { if (p->data_type != OSSL_PARAM_UTF8_STRING) { + *cipher = 0; + if (cipherName != NULL) { + *cipherName = NULL; + } ok = 0; } else if (p->data == NULL) { @@ -936,9 +942,9 @@ int wp_cipher_from_params(const OSSL_PARAM params[], int* cipher, } } else { - size_t i = WP_CIPHER_NAMES_LEN; - #ifdef WP_HAVE_PKCS8_ENC + size_t i; + for (i = 0; i < WP_CIPHER_NAMES_LEN; i++) { if ((XSTRLEN(wp_cipher_names[i].name) == p->data_size) && (XSTRNCMP(p->data, wp_cipher_names[i].name, @@ -950,10 +956,9 @@ int wp_cipher_from_params(const OSSL_PARAM params[], int* cipher, break; } } -#endif - /* Unknown cipher, or a build that cannot encrypt keys at all. - * Clear so a previously set cipher cannot drive a later encode, - * and fail rather than silently write the key in the clear. */ + /* Unknown cipher. Clear so a previously set cipher cannot drive a + * later encode, and fail rather than silently write the key in the + * clear. */ if (i == WP_CIPHER_NAMES_LEN) { *cipher = 0; if (cipherName != NULL) { @@ -961,6 +966,14 @@ int wp_cipher_from_params(const OSSL_PARAM params[], int* cipher, } ok = 0; } +#else + /* This build cannot encrypt private keys. */ + *cipher = 0; + if (cipherName != NULL) { + *cipherName = NULL; + } + ok = 0; +#endif } } @@ -997,13 +1010,16 @@ int wp_encrypt_key_pkcs8_size(WOLFPROV_CTX* provCtx, int cipher, int ok = 1; int rc = 0; word32 outSz = 0; +#ifndef WP_SINGLE_THREADED + int rngLocked = 0; +#endif byte fakeData[1] = { 0 }; byte fakeSalt[WP_EPKI_SALT_LEN] = { 0 }; WOLFPROV_ENTER(WP_LOG_COMP_PROVIDER, "wp_encrypt_key_pkcs8_size"); - /* A cipher must be selected to produce an encrypted key. */ - if (cipher == 0) { + /* A provider context, cipher and output length are required. */ + if ((provCtx == NULL) || (cipher == 0) || (outLen == NULL)) { ok = 0; } #ifndef WP_SINGLE_THREADED @@ -1012,6 +1028,9 @@ int wp_encrypt_key_pkcs8_size(WOLFPROV_CTX* provCtx, int cipher, if (ok && (wp_provctx_lock_rng(provCtx) != 1)) { ok = 0; } + else if (ok) { + rngLocked = 1; + } #endif if (ok) { /* Passing a NULL output buffer returns the required length. The _ex @@ -1027,9 +1046,14 @@ int wp_encrypt_key_pkcs8_size(WOLFPROV_CTX* provCtx, int cipher, WP_PKCS5, WP_PBES2, cipher, fakeSalt, sizeof(fakeSalt), WP_PKCS12_ITERATIONS_DEFAULT, wp_provctx_get_rng(provCtx), NULL); #endif - #ifndef WP_SINGLE_THREADED + } +#ifndef WP_SINGLE_THREADED + if (rngLocked) { wp_provctx_unlock_rng(provCtx); - #endif + rngLocked = 0; + } +#endif + if (ok) { if (rc != LENGTH_ONLY_E) { ok = 0; } @@ -1076,7 +1100,11 @@ int wp_encrypt_key_pkcs8(WOLFPROV_CTX* provCtx, int cipher, #ifdef WP_HAVE_PKCS8_ENC int ok = 1; int rc = 0; - word32 outSz = (word32)*outLen; + word32 outSz = 0; + word32 outCap = 0; +#ifndef WP_SINGLE_THREADED + int rngLocked = 0; +#endif byte salt[WP_EPKI_SALT_LEN]; #ifdef WOLFSSL_SMALL_STACK char* password = NULL; @@ -1084,7 +1112,7 @@ int wp_encrypt_key_pkcs8(WOLFPROV_CTX* provCtx, int cipher, char password[WP_EPKI_PASSWORD_MAX]; #endif size_t passwordSz = WP_EPKI_PASSWORD_MAX; - WC_RNG* rng = wp_provctx_get_rng(provCtx); + WC_RNG* rng = NULL; WOLFPROV_ENTER(WP_LOG_COMP_PROVIDER, "wp_encrypt_key_pkcs8"); @@ -1097,10 +1125,16 @@ int wp_encrypt_key_pkcs8(WOLFPROV_CTX* provCtx, int cipher, #endif /* A cipher must be selected and the in/out buffers must differ. */ - if (ok && ((cipher == 0) || (plain == NULL) || (out == NULL) || - (plain == out))) { + if (ok && ((provCtx == NULL) || (cipher == 0) || (plain == NULL) || + (out == NULL) || + (plain == out) || (outLen == NULL) || + (!WP_FITS_WORD32(*outLen)) || (pwCb == NULL))) { ok = 0; } + if (ok) { + rng = wp_provctx_get_rng(provCtx); + outSz = outCap = (word32)*outLen; + } /* Get the password from the callback. */ if (ok && (!pwCb(password, passwordSz, &passwordSz, NULL, pwCbArg))) { ok = 0; @@ -1109,11 +1143,14 @@ int wp_encrypt_key_pkcs8(WOLFPROV_CTX* provCtx, int cipher, if (ok && (passwordSz > WP_EPKI_PASSWORD_MAX)) { ok = 0; } - #ifndef WP_SINGLE_THREADED +#ifndef WP_SINGLE_THREADED if (ok && (wp_provctx_lock_rng(provCtx) != 1)) { ok = 0; } - #endif + else if (ok) { + rngLocked = 1; + } +#endif if (ok) { /* Generate the PBKDF2 salt. */ rc = wc_RNG_GenerateBlock(rng, salt, sizeof(salt)); @@ -1132,10 +1169,15 @@ int wp_encrypt_key_pkcs8(WOLFPROV_CTX* provCtx, int cipher, salt, sizeof(salt), WP_PKCS12_ITERATIONS_DEFAULT, rng, NULL); #endif } - #ifndef WP_SINGLE_THREADED + } +#ifndef WP_SINGLE_THREADED + if (rngLocked) { wp_provctx_unlock_rng(provCtx); - #endif - if (rc <= 0) { + rngLocked = 0; + } +#endif + if (ok) { + if ((rc <= 0) || ((word32)rc > outCap)) { WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_EncryptPKCS8Key", rc); ok = 0; diff --git a/src/wp_rsa_kmgmt.c b/src/wp_rsa_kmgmt.c index 3536aac4..a9fe71b1 100644 --- a/src/wp_rsa_kmgmt.c +++ b/src/wp_rsa_kmgmt.c @@ -3560,6 +3560,8 @@ static int wp_rsa_encode(wp_RsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, if (ok && (ctx->format == WP_ENC_FORMAT_TYPE_SPECIFIC) && (ctx->cipherName != NULL) && ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0)) { + WOLFPROV_ERROR_MSG(WP_LOG_COMP_RSA, + "encrypted traditional RSA encoding is not supported"); ok = 0; } @@ -4611,4 +4613,3 @@ const OSSL_DISPATCH wp_rsa_text_encoder_functions[] = { }; #endif /* WP_HAVE_RSA */ - diff --git a/test/test_ecc.c b/test/test_ecc.c index e9fac333..03a0d8bf 100644 --- a/test/test_ecc.c +++ b/test/test_ecc.c @@ -21,6 +21,7 @@ #include "unit.h" #include +#include #include #include #include @@ -962,10 +963,24 @@ int test_ecc_encode_epki(void *data) * an unencrypted private key. */ typeEctx = OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_KEYPAIR, "DER", "type-specific", "provider=libwolfprov"); - err = (typeEctx == NULL) || - (OSSL_ENCODER_CTX_set_cipher(typeEctx, "AES-256-CBC", NULL) != 1) || - (OSSL_ENCODER_to_data(typeEctx, &typeData, &typeLen) == 1); + err = (typeEctx == NULL); + if (err) { + PRINT_ERR_MSG("Failed to create type-specific ECC encoder"); + } + } + if (err == 0) { + err = OSSL_ENCODER_CTX_set_cipher(typeEctx, "AES-256-CBC", NULL) != 1; + if (err) { + PRINT_ERR_MSG("Failed to configure the ECC encoder cipher"); + } + } + if (err == 0) { + err = OSSL_ENCODER_to_data(typeEctx, &typeData, &typeLen) == 1; + if (err) { + PRINT_ERR_MSG("Type-specific ECC encoder accepted a cipher"); + } } + ERR_clear_error(); OSSL_ENCODER_CTX_free(typeEctx); OPENSSL_free(typeData); diff --git a/test/test_pkey.c b/test/test_pkey.c index a25ff176..94025425 100644 --- a/test/test_pkey.c +++ b/test/test_pkey.c @@ -329,9 +329,11 @@ int test_pki_cipher_encrypts(EVP_PKEY* pkey, const char* fmt, EVP_PKEY* badKey = NULL; OSSL_ENCODER_CTX* ectx = NULL; OSSL_ENCODER_CTX* badEctx = NULL; + OSSL_ENCODER_CTX* stateEctx = NULL; OSSL_DECODER_CTX* dctx = NULL; OSSL_DECODER_CTX* bctx = NULL; unsigned char* data = NULL; + unsigned char* stateData = NULL; size_t dataLen = 0; size_t encLen = 0; const unsigned char* pp; @@ -339,6 +341,7 @@ int test_pki_cipher_encrypts(EVP_PKEY* pkey, const char* fmt, const char* badPass = "wrong-passphrase"; size_t passLen = XSTRLEN(pass); static const char epkiHdr[] = "-----BEGIN ENCRYPTED PRIVATE KEY-----"; + static const char pkiHdr[] = "-----BEGIN PRIVATE KEY-----"; ectx = OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_KEYPAIR, fmt, "PrivateKeyInfo", encProp); @@ -351,8 +354,36 @@ int test_pki_cipher_encrypts(EVP_PKEY* pkey, const char* fmt, err = (badEctx == NULL) || (OSSL_ENCODER_CTX_set_cipher(badEctx, "unsupported-cipher", NULL) == 1); + if (err) { + PRINT_ERR_MSG("Unsupported PKCS8 cipher was accepted"); + } } OSSL_ENCODER_CTX_free(badEctx); + if ((err == 0) && (XSTRCMP(fmt, "PEM") == 0)) { + int badType = 1; + OSSL_PARAM badParams[] = { + OSSL_PARAM_int(OSSL_ENCODER_PARAM_CIPHER, &badType), + OSSL_PARAM_END + }; + size_t stateLen = 0; + + stateEctx = OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_KEYPAIR, + fmt, "PrivateKeyInfo", encProp); + err = (stateEctx == NULL) || + (OSSL_ENCODER_CTX_set_cipher(stateEctx, "AES-256-CBC", NULL) != 1) || + (OSSL_ENCODER_CTX_set_params(stateEctx, badParams) == 1); + ERR_clear_error(); + if (err == 0) { + err = (OSSL_ENCODER_to_data(stateEctx, &stateData, &stateLen) != 1) || + (stateLen < sizeof(pkiHdr) - 1) || + (XMEMCMP(stateData, pkiHdr, sizeof(pkiHdr) - 1) != 0); + if (err) { + PRINT_ERR_MSG("Rejected cipher parameter retained stale state"); + } + } + } + OSSL_ENCODER_CTX_free(stateEctx); + OPENSSL_free(stateData); if (err == 0) { err = OSSL_ENCODER_CTX_set_cipher(ectx, "AES-256-CBC", NULL) != 1; } @@ -412,9 +443,8 @@ int test_pki_cipher_encrypts(EVP_PKEY* pkey, const char* fmt, (const unsigned char*)badPass, XSTRLEN(badPass)) != 1; } if (err == 0) { - if ((OSSL_DECODER_from_data(bctx, &pp, &dataLen) == 1) && - (badKey != NULL)) { - PRINT_ERR_MSG("Wrong passphrase recovered the key"); + if (OSSL_DECODER_from_data(bctx, &pp, &dataLen) == 1) { + PRINT_ERR_MSG("Wrong passphrase was accepted"); err = 1; } ERR_clear_error(); @@ -482,12 +512,22 @@ int test_epki_encode_decode(EVP_PKEY* pkey, const char* fmt, } if (err == 0) { err = OSSL_DECODER_from_data(dctx, &pp, &dataLen) != 1; + if (err) { + PRINT_ERR_MSG("Failed to decode encrypted private key"); + ERR_print_errors_fp(stderr); + } } if (err == 0) { err = (pkey2 == NULL); + if (err) { + PRINT_ERR_MSG("Encrypted private key decode returned no key"); + } } if (err == 0) { err = EVP_PKEY_eq(pkey, pkey2) != 1; + if (err) { + PRINT_ERR_MSG("Decoded encrypted private key does not match"); + } } /* Negative case: a wrong passphrase must not yield a key. */ diff --git a/test/test_rsa.c b/test/test_rsa.c index a2f57f9f..b595b9c4 100644 --- a/test/test_rsa.c +++ b/test/test_rsa.c @@ -24,6 +24,8 @@ #include #include +#include +#include #include #include @@ -2378,6 +2380,9 @@ int test_rsa_encode_epki(void* data) PKCS8_PRIV_KEY_INFO* p8 = NULL; EVP_PKEY* pkey = NULL; EVP_PKEY* osslKey = NULL; + OSSL_ENCODER_CTX* typeEctx = NULL; + unsigned char* typeData = NULL; + size_t typeLen = 0; (void)data; @@ -2389,6 +2394,31 @@ int test_rsa_encode_epki(void* data) err = (pkey == NULL); } + if (err == 0) { + /* Traditional PKCS#1 encoding has no encrypted form. */ + typeEctx = OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_KEYPAIR, + "DER", "type-specific", "provider=libwolfprov"); + err = (typeEctx == NULL); + if (err) { + PRINT_ERR_MSG("Failed to create type-specific RSA encoder"); + } + } + if (err == 0) { + err = OSSL_ENCODER_CTX_set_cipher(typeEctx, "AES-256-CBC", NULL) != 1; + if (err) { + PRINT_ERR_MSG("Failed to configure the RSA encoder cipher"); + } + } + if (err == 0) { + err = OSSL_ENCODER_to_data(typeEctx, &typeData, &typeLen) == 1; + if (err) { + PRINT_ERR_MSG("Type-specific RSA encoder accepted a cipher"); + } + } + ERR_clear_error(); + OSSL_ENCODER_CTX_free(typeEctx); + OPENSSL_free(typeData); + if (err == 0) { PRINT_MSG("EncryptedPrivateKeyInfo DER: wolfProvider -> OpenSSL"); err = test_epki_encode_decode(pkey, "DER", "provider=libwolfprov",