From 7c4c01b6a21e7d31cbeabd832e1660250d79181c Mon Sep 17 00:00:00 2001 From: Yosuke Shimizu Date: Thu, 2 Jul 2026 15:21:14 +0900 Subject: [PATCH] Fix EncryptedPrivateKeyInfo encode + decode (PBES2) across all key types --- include/wolfprovider/internal.h | 11 +- src/wp_dh_kmgmt.c | 105 ++++++-- src/wp_ecc_kmgmt.c | 107 +++++++-- src/wp_ecx_kmgmt.c | 98 +++++++- src/wp_internal.c | 411 +++++++++++++++++--------------- src/wp_mldsa_kmgmt.c | 109 +++++++-- src/wp_rsa_kmgmt.c | 188 ++------------- test/test_dh.c | 37 +++ test/test_ecc.c | 37 +++ test/test_ecx.c | 32 +++ test/test_mldsa.c | 30 +++ test/test_pkey.c | 97 ++++++++ test/test_rsa.c | 44 ++++ test/unit.c | 15 ++ test/unit.h | 24 ++ 15 files changed, 896 insertions(+), 449 deletions(-) diff --git a/include/wolfprovider/internal.h b/include/wolfprovider/internal.h index e0660520..5a9b8f9b 100644 --- a/include/wolfprovider/internal.h +++ b/include/wolfprovider/internal.h @@ -233,9 +233,14 @@ int wp_hash_copy(wc_HashAlg* src, wc_HashAlg* dst, enum wc_HashType hashType); int wp_cipher_from_params(const OSSL_PARAM params[], int* cipher, const char** cipherName); -int wp_encrypt_key(WOLFPROV_CTX* provCtx, const char* cipherName, - unsigned char* keyData, size_t* keyLen, word32 pkcs8Len, - OSSL_PASSPHRASE_CALLBACK *pwCb, void *pwCbArg, byte** cipherInfo); +int wp_encrypt_key_pkcs8_size(WOLFPROV_CTX* provCtx, int cipher, + word32 plainLen, size_t* outLen); +int wp_encrypt_key_pkcs8(WOLFPROV_CTX* provCtx, int cipher, + const unsigned char* plain, word32 plainLen, + unsigned char* out, size_t* outLen, + OSSL_PASSPHRASE_CALLBACK* pwCb, void* pwCbArg); +int wp_decrypt_key_pkcs8(unsigned char* data, word32* len, + OSSL_PASSPHRASE_CALLBACK* pwCb, void* pwCbArg); 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/src/wp_dh_kmgmt.c b/src/wp_dh_kmgmt.c index 1f1cdde2..b47d6572 100644 --- a/src/wp_dh_kmgmt.c +++ b/src/wp_dh_kmgmt.c @@ -2284,6 +2284,43 @@ static int wp_dh_dec_send_params(wp_Dh* dh, OSSL_CALLBACK *dataCb, return ok; } +#ifdef WOLFSSL_ENCRYPTED_KEYS +/** + * Decode an encrypted PKCS#8 DER DH private key into the DH key object. + * + * @param [in, out] dh DH key object. + * @param [in] data DER encoding (decrypted in place). + * @param [in] len Length, in bytes, of DER encoding. + * @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_decode_enc_pki(wp_Dh* dh, unsigned char* data, word32 len, + OSSL_PASSPHRASE_CALLBACK* pwCb, void* pwCbArg) +{ + int ok = 1; + + WOLFPROV_ENTER_SILENT(WP_LOG_COMP_DH, WOLFPROV_FUNC_NAME); + + if (!wolfssl_prov_is_running()) { + ok = 0; + } + /* Decrypt the PBES2 EncryptedPrivateKeyInfo in place. */ + if (ok && (!wp_decrypt_key_pkcs8(data, &len, pwCb, pwCbArg))) { + ok = 0; + } + if (ok) { + /* Decode the recovered plaintext private key. */ + ok = wp_dh_decode_pki(dh, data, len); + } + + WOLFPROV_LEAVE_SILENT(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), + ok); + return ok; +} +#endif + /** * Decode the data in the core BIO. * @@ -2364,8 +2401,13 @@ 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)) { - ok = 0; - decoded = 0; +#ifdef WOLFSSL_ENCRYPTED_KEYS + if (!wp_dh_decode_enc_pki(dh, data, len, pwCb, pwCbArg)) +#endif + { + ok = 0; + decoded = 0; + } } } @@ -2602,7 +2644,8 @@ static int wp_dh_encode_pki(const wp_Dh *dh, unsigned char* keyData, * @return 1 on success. * @return 0 on failure. */ -static int wp_dh_encode_epki_size(const wp_Dh *dh, size_t* keyLen) +static int wp_dh_encode_epki_size(const wp_DhEncDecCtx* ctx, const wp_Dh *dh, + size_t* keyLen) { int ok = 1; int ret; @@ -2610,12 +2653,14 @@ static int wp_dh_encode_epki_size(const wp_Dh *dh, size_t* keyLen) 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; } if (ok) { - *keyLen = ((len + 15) / 16) * 16; + /* 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); @@ -2623,7 +2668,7 @@ static int wp_dh_encode_epki_size(const wp_Dh *dh, size_t* keyLen) } /** - * Encode the DH key in an Encrypted PKCS#8 format. + * Encode the DH key in a PBES2 EncryptedPrivateKeyInfo format. * * @param [in] ctx DH encoder/decoder context object. * @param [in] dh DH key object. @@ -2632,28 +2677,48 @@ static int wp_dh_encode_epki_size(const wp_Dh *dh, size_t* keyLen) * On out, length of encoding in bytes. * @param [in] pwCb Password callback. * @param [in] pwCbArg Argument to pass to password callback. - * @param [out] cipherInfo Information about encryption. * @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, byte** cipherInfo) + void *pwCbArg) { int ok = 1; int rc; - word32 pkcs8Len = (word32)*keyLen; + word32 pkcs8Len = 0; + byte* encodedKey = NULL; WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_encode_epki"); - /* Encode key. */ - rc = wc_DhPrivKeyToDer((DhKey*)&dh->key, keyData, &pkcs8Len); - if (rc <= 0) { + /* Determine the plaintext PKCS #8 length. */ + rc = wc_DhPrivKeyToDer((DhKey*)&dh->key, NULL, &pkcs8Len); + if (rc != LENGTH_ONLY_E) { ok = 0; } - if (ok && (!wp_encrypt_key(ctx->provCtx, ctx->cipherName, keyData, keyLen, - pkcs8Len, pwCb, pwCbArg, cipherInfo))) { - 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) { + /* 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); @@ -2698,7 +2763,6 @@ static int wp_dh_encode(wp_DhEncDecCtx* ctx, OSSL_CORE_BIO *cBio, size_t pemLen = 0; int pemType = DH_PRIVATEKEY_TYPE; int private = 0; - byte* cipherInfo = NULL; if (out == NULL) { ok = 0; @@ -2729,7 +2793,7 @@ static int wp_dh_encode(wp_DhEncDecCtx* ctx, OSSL_CORE_BIO *cBio, #ifdef WOLFSSL_ENCRYPTED_KEYS else if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { private = 1; - if (!wp_dh_encode_epki_size(key, &derLen)) { + if (!wp_dh_encode_epki_size(ctx, key, &derLen)) { ok = 0; } } @@ -2764,8 +2828,8 @@ static int wp_dh_encode(wp_DhEncDecCtx* ctx, OSSL_CORE_BIO *cBio, #ifdef WOLFSSL_ENCRYPTED_KEYS else if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { private = 1; - if (!wp_dh_encode_epki(ctx, key, derData, &derLen, pwCb, pwCbArg, - (ctx->encoding == WP_FORMAT_PEM) ? &cipherInfo : NULL)) { + pemType = PKCS8_ENC_PRIVATEKEY_TYPE; + if (!wp_dh_encode_epki(ctx, key, derData, &derLen, pwCb, pwCbArg)) { ok = 0; } } @@ -2775,7 +2839,7 @@ static int wp_dh_encode(wp_DhEncDecCtx* ctx, OSSL_CORE_BIO *cBio, keyLen = derLen; } else if (ok && (ctx->encoding == WP_FORMAT_PEM)) { - rc = wc_DerToPemEx(derData, (word32)derLen, NULL, 0, cipherInfo, + rc = wc_DerToPemEx(derData, (word32)derLen, NULL, 0, NULL, pemType); if (rc <= 0) { ok = 0; @@ -2789,7 +2853,7 @@ static int wp_dh_encode(wp_DhEncDecCtx* ctx, OSSL_CORE_BIO *cBio, } if (ok) { rc = wc_DerToPemEx(derData, (word32)derLen, pemData, (word32)pemLen, - cipherInfo, pemType); + NULL, pemType); if (rc <= 0) { ok = 0; } @@ -2814,7 +2878,6 @@ static int wp_dh_encode(wp_DhEncDecCtx* ctx, OSSL_CORE_BIO *cBio, OPENSSL_free(derData); OPENSSL_free(pemData); } - OPENSSL_free(cipherInfo); BIO_free(out); #else diff --git a/src/wp_ecc_kmgmt.c b/src/wp_ecc_kmgmt.c index 2d50e0d0..163eba67 100644 --- a/src/wp_ecc_kmgmt.c +++ b/src/wp_ecc_kmgmt.c @@ -2285,6 +2285,43 @@ static int wp_ecc_dec_send_params(wp_Ecc* ecc, OSSL_CALLBACK *dataCb, return ok; } +#ifdef WOLFSSL_ENCRYPTED_KEYS +/** + * Decode an encrypted PKCS#8 DER ECC private key into the ECC key object. + * + * @param [in, out] ecc ECC key object. + * @param [in] data DER encoding (decrypted in place). + * @param [in] len Length, in bytes, of DER encoding. + * @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_ecc_decode_enc_pki(wp_Ecc* ecc, unsigned char* data, word32 len, + OSSL_PASSPHRASE_CALLBACK* pwCb, void* pwCbArg) +{ + int ok = 1; + + WOLFPROV_ENTER_SILENT(WP_LOG_COMP_ECC, WOLFPROV_FUNC_NAME); + + if (!wolfssl_prov_is_running()) { + ok = 0; + } + /* Decrypt the PBES2 EncryptedPrivateKeyInfo in place. */ + if (ok && (!wp_decrypt_key_pkcs8(data, &len, pwCb, pwCbArg))) { + ok = 0; + } + if (ok) { + /* Decode the recovered plaintext private key. */ + ok = wp_ecc_decode_pki(ecc, data, len); + } + + WOLFPROV_LEAVE_SILENT(WP_LOG_COMP_ECC, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), + ok); + return ok; +} +#endif + /** * Decode the data in the core BIO. * @@ -2362,8 +2399,13 @@ 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)) { - ok = 0; - decoded = 0; +#ifdef WOLFSSL_ENCRYPTED_KEYS + if (!wp_ecc_decode_enc_pki(ecc, data, len, pwCb, pwCbArg)) +#endif + { + ok = 0; + decoded = 0; + } } } @@ -2710,7 +2752,8 @@ static int wp_ecc_encode_pki(const wp_Ecc *ecc, unsigned char* keyData, * @return 1 on success. * @return 0 on failure. */ -static int wp_ecc_encode_epki_size(const wp_Ecc *ecc, size_t* keyLen) +static int wp_ecc_encode_epki_size(const wp_EccEncDecCtx* ctx, + const wp_Ecc *ecc, size_t* keyLen) { int ok = 1; int rc; @@ -2718,6 +2761,7 @@ static int wp_ecc_encode_epki_size(const wp_Ecc *ecc, size_t* keyLen) WOLFPROV_ENTER(WP_LOG_COMP_ECC, "wp_ecc_encode_epki_size"); + /* Get the plaintext PKCS #8 length. */ PRIVATE_KEY_UNLOCK(); rc = wc_EccKeyToPKCS8((ecc_key*)&ecc->key, NULL, &len); PRIVATE_KEY_LOCK(); @@ -2725,7 +2769,8 @@ static int wp_ecc_encode_epki_size(const wp_Ecc *ecc, size_t* keyLen) ok = 0; } if (ok) { - *keyLen = ((len + 15) / 16) * 16; + /* 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_ECC, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); @@ -2733,7 +2778,7 @@ static int wp_ecc_encode_epki_size(const wp_Ecc *ecc, size_t* keyLen) } /** - * Encode the ECC key in an Encrypted PKCS#8 format. + * Encode the ECC key in a PBES2 EncryptedPrivateKeyInfo format. * * @param [in] ctx ECC encoder/decoder context object. * @param [in] ecc ECC key object. @@ -2742,30 +2787,52 @@ static int wp_ecc_encode_epki_size(const wp_Ecc *ecc, size_t* keyLen) * On out, length of encoding in bytes. * @param [in] pwCb Password callback. * @param [in] pwCbArg Argument to pass to password callback. - * @param [out] cipherInfo Information about encryption. * @return 1 on success. * @return 0 on failure. */ static int wp_ecc_encode_epki(const wp_EccEncDecCtx* ctx, const wp_Ecc *ecc, unsigned char* keyData, size_t* keyLen, OSSL_PASSPHRASE_CALLBACK *pwCb, - void *pwCbArg, byte** cipherInfo) + void *pwCbArg) { int ok = 1; int rc; - word32 len = (word32)*keyLen; + word32 len = 0; + byte* encodedKey = NULL; WOLFPROV_ENTER(WP_LOG_COMP_ECC, "wp_ecc_encode_epki"); - /* Encode key. */ + /* Determine the plaintext PKCS #8 length. */ PRIVATE_KEY_UNLOCK(); - rc = wc_EccKeyToPKCS8((ecc_key*)&ecc->key, keyData, &len); + rc = wc_EccKeyToPKCS8((ecc_key*)&ecc->key, NULL, &len); PRIVATE_KEY_LOCK(); - if (rc <= 0) { + if (rc != LENGTH_ONLY_E) { ok = 0; } - if (ok && (!wp_encrypt_key(ctx->provCtx, ctx->cipherName, keyData, keyLen, - len, pwCb, pwCbArg, cipherInfo))) { - ok = 0; + if (ok) { + /* Allocate the plaintext buffer - must differ from the output. */ + encodedKey = OPENSSL_malloc(len); + if (encodedKey == NULL) { + ok = 0; + } + } + if (ok) { + /* Encode the plaintext PKCS #8 key. */ + PRIVATE_KEY_UNLOCK(); + rc = wc_EccKeyToPKCS8((ecc_key*)&ecc->key, encodedKey, &len); + PRIVATE_KEY_LOCK(); + if (rc <= 0) { + ok = 0; + } + } + if (ok) { + /* Encrypt as a PBES2 EncryptedPrivateKeyInfo. */ + ok = wp_encrypt_key_pkcs8(ctx->provCtx, ctx->cipher, encodedKey, len, + keyData, keyLen, pwCb, pwCbArg); + } + + /* encodedKey holds the plaintext PKCS#8 private key before encryption. */ + if (encodedKey != NULL) { + OPENSSL_clear_free(encodedKey, len); } WOLFPROV_LEAVE(WP_LOG_COMP_ECC, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); @@ -2801,7 +2868,6 @@ static int wp_ecc_encode(wp_EccEncDecCtx* ctx, OSSL_CORE_BIO *cBio, size_t pemLen = 0; int pemType = PKCS8_PRIVATEKEY_TYPE; int private = 0; - byte* cipherInfo = NULL; WOLFPROV_ENTER(WP_LOG_COMP_ECC, "wp_ecc_encode"); @@ -2846,7 +2912,7 @@ static int wp_ecc_encode(wp_EccEncDecCtx* ctx, OSSL_CORE_BIO *cBio, #ifdef WOLFSSL_ENCRYPTED_KEYS else if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { private = 1; - if (!wp_ecc_encode_epki_size(key, &derLen)) { + if (!wp_ecc_encode_epki_size(ctx, key, &derLen)) { ok = 0; } } @@ -2897,8 +2963,8 @@ static int wp_ecc_encode(wp_EccEncDecCtx* ctx, OSSL_CORE_BIO *cBio, #ifdef WOLFSSL_ENCRYPTED_KEYS else if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { private = 1; - if (!wp_ecc_encode_epki(ctx, key, derData, &derLen, pwCb, pwCbArg, - (ctx->encoding == WP_FORMAT_PEM) ? &cipherInfo : NULL)) { + pemType = PKCS8_ENC_PRIVATEKEY_TYPE; + if (!wp_ecc_encode_epki(ctx, key, derData, &derLen, pwCb, pwCbArg)) { ok = 0; } } @@ -2908,7 +2974,7 @@ static int wp_ecc_encode(wp_EccEncDecCtx* ctx, OSSL_CORE_BIO *cBio, keyLen = derLen; } else if (ok && (ctx->encoding == WP_FORMAT_PEM)) { - rc = wc_DerToPemEx(derData, (word32)derLen, NULL, 0, cipherInfo, + rc = wc_DerToPemEx(derData, (word32)derLen, NULL, 0, NULL, pemType); if (rc <= 0) { ok = 0; @@ -2922,7 +2988,7 @@ static int wp_ecc_encode(wp_EccEncDecCtx* ctx, OSSL_CORE_BIO *cBio, } if (ok) { rc = wc_DerToPemEx(derData, (word32)derLen, pemData, (word32)pemLen, - cipherInfo, pemType); + NULL, pemType); if (rc <= 0) { ok = 0; } @@ -2956,7 +3022,6 @@ static int wp_ecc_encode(wp_EccEncDecCtx* ctx, OSSL_CORE_BIO *cBio, OPENSSL_free(derData); OPENSSL_free(pemData); } - OPENSSL_free(cipherInfo); BIO_free(out); WOLFPROV_LEAVE(WP_LOG_COMP_ECC, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; diff --git a/src/wp_ecx_kmgmt.c b/src/wp_ecx_kmgmt.c index d3a60b44..625b19ad 100644 --- a/src/wp_ecx_kmgmt.c +++ b/src/wp_ecx_kmgmt.c @@ -2021,6 +2021,46 @@ static int wp_ecx_dec_send_params(wp_Ecx* ecx, const char* dataType, return ok; } +#ifdef WOLFSSL_ENCRYPTED_KEYS +/** + * Decode an encrypted PKCS#8 DER ECX private key into the ECX key object. + * + * @param [in] ctx ECX encoder/decoder context object. + * @param [in, out] ecx ECX key object. + * @param [in] data DER encoding (decrypted in place). + * @param [in] len Length, in bytes, of DER encoding. + * @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_ecx_decode_enc_pki(wp_EcxEncDecCtx* ctx, wp_Ecx* ecx, + unsigned char* data, word32 len, OSSL_PASSPHRASE_CALLBACK* pwCb, + void* pwCbArg) +{ + int ok = 1; + word32 idx = 0; + + WOLFPROV_ENTER_SILENT(WP_LOG_COMP_KE, WOLFPROV_FUNC_NAME); + + if (!wolfssl_prov_is_running()) { + ok = 0; + } + /* Decrypt the PBES2 EncryptedPrivateKeyInfo in place. */ + if (ok && (!wp_decrypt_key_pkcs8(data, &len, pwCb, pwCbArg))) { + ok = 0; + } + /* Decode the recovered plaintext private key. */ + if (ok && (ctx->decode(data, &idx, (void*)&ecx->key, len) != 0)) { + ok = 0; + } + + WOLFPROV_LEAVE_SILENT(WP_LOG_COMP_KE, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), + ok); + return ok; +} +#endif + /** * Decode the data in the core BIO. * @@ -2102,9 +2142,16 @@ 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) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "decode", rc); - ok = 0; - decoded = 0; +#ifdef WOLFSSL_ENCRYPTED_KEYS + /* 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))) +#endif + { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "decode", rc); + ok = 0; + decoded = 0; + } } } if (ok && (ctx->format == WP_ENC_FORMAT_SPKI)) { @@ -2161,13 +2208,16 @@ static int wp_ecx_encode(wp_EcxEncDecCtx* ctx, OSSL_CORE_BIO *cBio, size_t keyLen = 0; unsigned char derData[160]; size_t derLen = 0; + unsigned char* encData = NULL; + size_t encLen = 0; + unsigned char* srcData; + size_t srcLen; unsigned char* pemData = NULL; size_t pemLen = 0; int pemType = (ctx->format == WP_ENC_FORMAT_SPKI) ? PUBLICKEY_TYPE : PKCS8_PRIVATEKEY_TYPE; int private = (ctx->format == WP_ENC_FORMAT_PKI) || (ctx->format == WP_ENC_FORMAT_EPKI); - byte* cipherInfo = NULL; (void)params; (void)selection; @@ -2185,19 +2235,40 @@ static int wp_ecx_encode(wp_EcxEncDecCtx* ctx, OSSL_CORE_BIO *cBio, derLen = rc; } } + + /* By default the plaintext DER is the source for the output encoding. */ + srcData = derData; + srcLen = derLen; if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { - if (!wp_encrypt_key(ctx->provCtx, ctx->cipherName, derData, &derLen, rc, - pwCb, pwCbArg, &cipherInfo)) { + 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. */ + if (!wp_encrypt_key_pkcs8_size(ctx->provCtx, ctx->cipher, + (word32)derLen, &encLen)) { ok = 0; } + if (ok) { + encData = OPENSSL_malloc(encLen); + if (encData == NULL) { + ok = 0; + } + } + if (ok && (!wp_encrypt_key_pkcs8(ctx->provCtx, ctx->cipher, derData, + (word32)derLen, encData, &encLen, pwCb, pwCbArg))) { + ok = 0; + } + if (ok) { + srcData = encData; + srcLen = encLen; + } } if (ok && (ctx->encoding == WP_FORMAT_DER)) { - keyData = derData; - keyLen = derLen; + keyData = srcData; + keyLen = srcLen; } else if (ok && (ctx->encoding == WP_FORMAT_PEM)) { - rc = wc_DerToPemEx(derData, (word32)derLen, NULL, 0, cipherInfo, pemType); + rc = wc_DerToPemEx(srcData, (word32)srcLen, NULL, 0, NULL, pemType); if (rc <= 0) { ok = 0; } @@ -2209,8 +2280,8 @@ static int wp_ecx_encode(wp_EcxEncDecCtx* ctx, OSSL_CORE_BIO *cBio, } } if (ok) { - rc = wc_DerToPemEx(derData, (word32)derLen, pemData, (word32)pemLen, cipherInfo, - pemType); + rc = wc_DerToPemEx(srcData, (word32)srcLen, pemData, (word32)pemLen, + NULL, pemType); if (rc <= 0) { ok = 0; } @@ -2227,14 +2298,15 @@ static int wp_ecx_encode(wp_EcxEncDecCtx* ctx, OSSL_CORE_BIO *cBio, } } + /* derData holds the plaintext private key material. */ if (private) { - OPENSSL_cleanse(derData, derLen); + OPENSSL_cleanse(derData, sizeof(derData)); OPENSSL_clear_free(pemData, pemLen); } else { OPENSSL_free(pemData); } - OPENSSL_free(cipherInfo); + OPENSSL_clear_free(encData, encLen); WOLFPROV_LEAVE(WP_LOG_COMP_KE, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } diff --git a/src/wp_internal.c b/src/wp_internal.c index ceb2fce6..af7d9826 100644 --- a/src/wp_internal.c +++ b/src/wp_internal.c @@ -32,6 +32,7 @@ #include #include +#include #ifdef HAVE_FIPS #include #endif @@ -904,260 +905,276 @@ int wp_cipher_from_params(const OSSL_PARAM params[], int* cipher, return ok; } -#ifndef WOLFSSL_ENCRYPTED_KEYS -#ifdef WP_HAVE_MD5 -/* - * wolfProvider version of EncryptedInfo. - */ -typedef struct wp_EncryptedInfo { - /* Cipher identifier. */ - int cipherType; - /* Length of IV. */ - word32 ivSz; - /* Length of key. */ - word32 keySz; - /* Name of cipher alglorithm. */ - char name[NAME_SZ]; - /* IV for encryption. */ - byte iv[IV_SZ]; -} wp_EncryptedInfo; - -#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) - static wcchar kEncTypeAesCbc128 = "AES-128-CBC"; -#endif -#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_192) - static wcchar kEncTypeAesCbc192 = "AES-192-CBC"; -#endif -#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_256) - static wcchar kEncTypeAesCbc256 = "AES-256-CBC"; -#endif +/* Salt length used for PBES2 EncryptedPrivateKeyInfo encoding. The same value + * is used for the size query and the encrypt call so the reported and actual + * output sizes agree. */ +#define WP_EPKI_SALT_LEN 16 -static int wp_EncryptedInfoGet(wp_EncryptedInfo* info, const char* cipherInfo) +/* Maximum passphrase length read from the password callback. */ +#define WP_EPKI_PASSWORD_MAX 1024 + +/** + * Get the size of the PBES2 EncryptedPrivateKeyInfo encoding of a PKCS #8 key. + * + * The encrypted structure is larger than the plaintext (it carries the PBES2 + * AlgorithmIdentifier), and the exact size depends on the cipher, so the size + * is obtained from wolfSSL rather than computed. + * + * @param [in] provCtx Provider context (supplies the RNG). + * @param [in] cipher wolfCrypt cipher identifier to encrypt with. + * @param [in] plainLen Length of the plaintext PKCS #8 key in bytes. + * @param [out] outLen Length of the encrypted encoding in bytes. + * @return 1 on success. + * @return 0 on failure. + */ +int wp_encrypt_key_pkcs8_size(WOLFPROV_CTX* provCtx, int cipher, + word32 plainLen, size_t* outLen) { - int ret = 0; +#if defined(HAVE_PKCS8) && !defined(NO_PWDBASED) + int ok = 1; + word32 outSz = 0; + byte fakeData[1] = { 0 }; + byte fakeSalt[WP_EPKI_SALT_LEN] = { 0 }; - if (info == NULL || cipherInfo == NULL) - return BAD_FUNC_ARG; + WOLFPROV_ENTER(WP_LOG_COMP_PROVIDER, "wp_encrypt_key_pkcs8_size"); - /* determine cipher information */ -#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) - if (XSTRCMP(cipherInfo, kEncTypeAesCbc128) == 0) { - info->cipherType = WC_CIPHER_AES_CBC; - info->keySz = AES_128_KEY_SIZE; - if (info->ivSz == 0) info->ivSz = AES_IV_SIZE; - } - else -#endif -#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_192) - if (XSTRCMP(cipherInfo, kEncTypeAesCbc192) == 0) { - info->cipherType = WC_CIPHER_AES_CBC; - info->keySz = AES_192_KEY_SIZE; - if (info->ivSz == 0) info->ivSz = AES_IV_SIZE; + /* A cipher must be selected to produce an encrypted key. */ + if (cipher == 0) { + ok = 0; } - else -#endif -#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_256) - if (XSTRCMP(cipherInfo, kEncTypeAesCbc256) == 0) { - info->cipherType = WC_CIPHER_AES_CBC; - info->keySz = AES_256_KEY_SIZE; - if (info->ivSz == 0) info->ivSz = AES_IV_SIZE; + if (ok) { + /* Passing a NULL output buffer returns the required length. */ + if (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) != LENGTH_ONLY_E) { + ok = 0; + } + else { + *outLen = (size_t)outSz; + } } - else + + WOLFPROV_LEAVE(WP_LOG_COMP_PROVIDER, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), + ok); + return ok; +#else + (void)provCtx; + (void)cipher; + (void)plainLen; + (void)outLen; + return 0; #endif - { - ret = NOT_COMPILED_IN; - } - return ret; } -#define PKCS5_SALT_SZ 8 - -static int wp_BufferKeyEncrypt(wp_EncryptedInfo* info, byte* der, word32 derSz, - const byte* password, int passwordSz, int hashType) +/** + * Encrypt a plaintext PKCS #8 key into a PBES2 EncryptedPrivateKeyInfo. + * + * Derives the key with PBKDF2 and encrypts with the requested cipher, producing + * a standards-compliant EncryptedPrivateKeyInfo structure. The plaintext and + * output buffers must be different (wolfSSL requirement). + * + * @param [in] provCtx Provider context (supplies the RNG). + * @param [in] cipher wolfCrypt cipher identifier to encrypt with. + * @param [in] plain Plaintext PKCS #8 key. + * @param [in] plainLen Length of the plaintext key in bytes. + * @param [out] out Buffer to hold the encrypted encoding. + * @param [in, out] outLen On in, size of buffer; on out, length written. + * @param [in] pwCb Password callback. + * @param [in] pwCbArg Argument to pass to the password callback. + * @return 1 on success. + * @return 0 on failure. + */ +int wp_encrypt_key_pkcs8(WOLFPROV_CTX* provCtx, int cipher, + const unsigned char* plain, word32 plainLen, + unsigned char* out, size_t* outLen, + OSSL_PASSPHRASE_CALLBACK* pwCb, void* pwCbArg) { - int ret = NOT_COMPILED_IN; +#if defined(HAVE_PKCS8) && !defined(NO_PWDBASED) + int ok = 1; + int rc = 0; + word32 outSz = (word32)*outLen; + byte salt[WP_EPKI_SALT_LEN]; #ifdef WOLFSSL_SMALL_STACK - byte* key = NULL; + char* password = NULL; #else - byte key[WC_MAX_SYM_KEY_SIZE]; + char password[WP_EPKI_PASSWORD_MAX]; #endif + size_t passwordSz = WP_EPKI_PASSWORD_MAX; + WC_RNG* rng = wp_provctx_get_rng(provCtx); - (void)derSz; - (void)passwordSz; - (void)hashType; - - if (der == NULL || password == NULL || info == NULL || info->keySz == 0 || - info->ivSz < PKCS5_SALT_SZ) { - return BAD_FUNC_ARG; - } + WOLFPROV_ENTER(WP_LOG_COMP_PROVIDER, "wp_encrypt_key_pkcs8"); #ifdef WOLFSSL_SMALL_STACK - key = (byte*)XMALLOC(WC_MAX_SYM_KEY_SIZE, NULL, DYNAMIC_TYPE_SYMMETRIC_KEY); - if (key == NULL) { - return MEMORY_E; + password = (char*)XMALLOC(WP_EPKI_PASSWORD_MAX, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (password == NULL) { + ok = 0; } -#endif /* WOLFSSL_SMALL_STACK */ +#endif - (void)XMEMSET(key, 0, WC_MAX_SYM_KEY_SIZE); + /* A cipher must be selected and the in/out buffers must differ. */ + if (ok && ((cipher == 0) || (plain == NULL) || (out == NULL) || + (plain == out))) { + ok = 0; + } + /* Get the password from the callback. */ + if (ok && (!pwCb(password, passwordSz, &passwordSz, NULL, pwCbArg))) { + ok = 0; + } + if (ok) { + #ifndef WP_SINGLE_THREADED + wp_provctx_lock_rng(provCtx); + #endif + /* Generate the PBKDF2 salt. */ + rc = wc_RNG_GenerateBlock(rng, salt, sizeof(salt)); + if (rc == 0) { + /* Encrypt into the separate output buffer as PBES2. */ + rc = wc_EncryptPKCS8Key((byte*)plain, plainLen, out, &outSz, + password, (int)passwordSz, WP_PKCS5, WP_PBES2, cipher, + salt, sizeof(salt), WP_PKCS12_ITERATIONS_DEFAULT, rng, NULL); + } + #ifndef WP_SINGLE_THREADED + wp_provctx_unlock_rng(provCtx); + #endif + if (rc <= 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_EncryptPKCS8Key", + rc); + ok = 0; + } + else { + *outLen = (size_t)outSz; + } + } -#ifndef NO_PWDBASED - if ((ret = wc_PBKDF1(key, password, passwordSz, info->iv, PKCS5_SALT_SZ, 1, - info->keySz, hashType)) != 0) { + /* Password is sensitive - force zeroization. */ #ifdef WOLFSSL_SMALL_STACK - XFREE(key, NULL, DYNAMIC_TYPE_SYMMETRIC_KEY); -#endif - return ret; + if (password != NULL) { + OPENSSL_cleanse(password, WP_EPKI_PASSWORD_MAX); + XFREE(password, NULL, DYNAMIC_TYPE_TMP_BUFFER); } +#else + OPENSSL_cleanse(password, sizeof(password)); #endif -#ifndef NO_DES3 - if (info->cipherType == WC_CIPHER_DES) - ret = wc_Des_CbcEncryptWithKey(der, der, derSz, key, info->iv); - if (info->cipherType == WC_CIPHER_DES3) - ret = wc_Des3_CbcEncryptWithKey(der, der, derSz, key, info->iv); -#endif /* NO_DES3 */ -#if !defined(NO_AES) && defined(HAVE_AES_CBC) - if (info->cipherType == WC_CIPHER_AES_CBC) - ret = wc_AesCbcEncryptWithKey(der, der, derSz, key, info->keySz, - info->iv); -#endif /* !NO_AES && HAVE_AES_CBC */ - -#ifdef WOLFSSL_SMALL_STACK - XFREE(key, NULL, DYNAMIC_TYPE_SYMMETRIC_KEY); + WOLFPROV_LEAVE(WP_LOG_COMP_PROVIDER, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), + ok); + return ok; +#else + (void)provCtx; + (void)cipher; + (void)plain; + (void)plainLen; + (void)out; + (void)outLen; + (void)pwCb; + (void)pwCbArg; + return 0; #endif +} - return ret; +#if defined(HAVE_PKCS8) && !defined(NO_PWDBASED) +/* 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 +}; + +/* + * Detect whether a DER blob is a PBES2-encrypted PKCS#8 key by looking for the + * PBKDF2 OID near the start of the encryptionAlgorithm field. Avoids prompting + * for a passphrase on data that is not an encrypted key. + */ +static int wp_is_pbkdf2_encrypted(const unsigned char* data, word32 len) +{ + int found = 0; + word32 i; + + for (i = 0; (i < 40) && (i + sizeof(wp_pbkdf2_oid) <= len); i++) { + if (XMEMCMP(data + i, wp_pbkdf2_oid, sizeof(wp_pbkdf2_oid)) == 0) { + found = 1; + break; + } + } + + return found; } -#endif /* WP_HAVE_MD5 */ -#endif /* WOLFSSL_ENCRYPTED_KEYS */ +#endif /** - * Encrypt the PKCS #8 key. + * Decrypt a PBES2 EncryptedPrivateKeyInfo to plaintext PKCS#8 in place, getting + * the passphrase from the callback. Returns 0 without prompting when the data + * is not a PBES2-encrypted key. * - * Calls password callback and generates a random IV. - * - * @param [in] provCtx Provider context. - * @param [in] cipherName Name of cipher to encrypt with. - * @param [in, out] keyData On in, PKCS #8 encoded key. - * On out, encrypted PKCS #8 encoded key. - * @param [in, out] keyLen On in, length of buffer in bytes. - * On out, length of encrypted key in bytes. - * @param [in] pkcs8Len Length of PKCS #8 key in bytes. - * @param [in] pwCb Password callback. - * @param [in] pwCbArg Argument to pass to password callback. - * @param [out] cipherInfo Information about encryption. + * @param [in, out] data On in, encrypted key; on out, plaintext PKCS#8. + * @param [in, out] len On in, encrypted length; on out, plaintext length. + * @param [in] pwCb Password callback. + * @param [in] pwCbArg Argument to pass to the password callback. * @return 1 on success. - * @return 0 on failure. + * @return 0 on failure or when the data is not an encrypted PKCS#8 key. */ -int wp_encrypt_key(WOLFPROV_CTX* provCtx, const char* cipherName, - unsigned char* keyData, size_t* keyLen, word32 pkcs8Len, - OSSL_PASSPHRASE_CALLBACK *pwCb, void *pwCbArg, byte** cipherInfo) +int wp_decrypt_key_pkcs8(unsigned char* data, word32* len, + OSSL_PASSPHRASE_CALLBACK* pwCb, void* pwCbArg) { -#ifdef WP_HAVE_MD5 +#if defined(HAVE_PKCS8) && !defined(NO_PWDBASED) int ok = 1; int rc; - word32 len = (word32)*keyLen; -#ifdef WOLFSSL_ENCRYPTED_KEYS - EncryptedInfo info[1]; +#ifdef WOLFSSL_SMALL_STACK + char* password = NULL; #else - wp_EncryptedInfo info[1]; + char password[WP_EPKI_PASSWORD_MAX]; #endif - word32 cipherInfoSz; - char password[1024]; - size_t passwordSz = sizeof(password); + size_t passwordSz = WP_EPKI_PASSWORD_MAX; - WOLFPROV_ENTER(WP_LOG_COMP_PROVIDER, "wp_encrypt_key"); + WOLFPROV_ENTER(WP_LOG_COMP_PROVIDER, "wp_decrypt_key_pkcs8"); - /* Get password. */ - if (!pwCb(password, passwordSz, &passwordSz, NULL, pwCbArg)) { + /* Only handle data that looks like a PBES2-encrypted PKCS#8 key. */ + if ((data == NULL) || (!wp_is_pbkdf2_encrypted(data, *len))) { ok = 0; } +#ifdef WOLFSSL_SMALL_STACK if (ok) { - XMEMSET(info, 0, sizeof(info)); - XSTRNCPY(info->name, cipherName, NAME_SZ-1); - info->name[NAME_SZ-1] = '\0'; - - #ifdef WOLFSSL_ENCRYPTED_KEYS - rc = wc_EncryptedInfoGet(info, info->name); - #else - rc = wp_EncryptedInfoGet(info, info->name); - #endif - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "EncryptedInfoGet", rc); + password = (char*)XMALLOC(WP_EPKI_PASSWORD_MAX, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (password == NULL) { ok = 0; } } - if (ok) { - /* Calculate random IV. */ - WC_RNG* rng = wp_provctx_get_rng(provCtx); - - #ifndef WP_SINGLE_THREADED - wp_provctx_lock_rng(provCtx); - #endif - rc = wc_RNG_GenerateBlock(rng, info->iv, info->ivSz); - #ifndef WP_SINGLE_THREADED - wp_provctx_unlock_rng(provCtx); - #endif - if (rc < 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_RNG_GenerateBlock", rc); - ok = 0; - } +#endif + /* Get the password from the callback. */ + if (ok && (!pwCb(password, passwordSz, &passwordSz, NULL, pwCbArg))) { + ok = 0; } if (ok) { - /* Pad with zeros. */ - XMEMSET(keyData + pkcs8Len, 0, len - pkcs8Len); - - /* Encrypt key and padding. */ - #ifdef WOLFSSL_ENCRYPTED_KEYS - rc = wc_BufferKeyEncrypt(info, keyData, len, (byte*)password, - (int)passwordSz, WC_MD5); - #else - rc = wp_BufferKeyEncrypt(info, keyData, len, (byte*)password, - (int)passwordSz, WC_MD5); - #endif - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "BufferKeyEncrypt", rc); + /* Decrypt the key in place. */ + rc = wc_DecryptPKCS8Key(data, *len, password, (int)passwordSz); + if (rc <= 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_DecryptPKCS8Key", + rc); ok = 0; } - } - if (ok && (cipherInfo != NULL)) { - /* cipher name | ',' | hex encoded IV */ - cipherInfoSz = (word32)(XSTRLEN(info->name) + 2 + info->ivSz * 2); - *cipherInfo = (byte*)OPENSSL_malloc(cipherInfoSz); - if (*cipherInfo == NULL) { - ok = 0; - } - } - if (ok && (cipherInfo != NULL)) { - word32 idx = (word32)XSTRLEN(info->name); - XSTRNCPY((char*)*cipherInfo, info->name, cipherInfoSz); - cipherInfoSz -= idx; - XSTRNCAT((char*)*cipherInfo, ",", cipherInfoSz); - cipherInfoSz--; - rc = Base16_Encode(info->iv, info->ivSz, *cipherInfo + idx, - &cipherInfoSz); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "Base16_Encode", rc); - ok = 0; + else { + *len = (word32)rc; } } - if (ok) { - *keyLen = len; - } + /* Password is sensitive - force zeroization. */ +#ifdef WOLFSSL_SMALL_STACK + if (password != NULL) { + OPENSSL_cleanse(password, WP_EPKI_PASSWORD_MAX); + XFREE(password, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } +#else OPENSSL_cleanse(password, sizeof(password)); +#endif - WOLFPROV_LEAVE(WP_LOG_COMP_PROVIDER, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); + WOLFPROV_LEAVE(WP_LOG_COMP_PROVIDER, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), + ok); return ok; #else - (void)provCtx; - (void)cipherName; - (void)keyData; - (void)keyLen; - (void)pkcs8Len; + (void)data; + (void)len; (void)pwCb; (void)pwCbArg; - (void)cipherInfo; return 0; #endif } diff --git a/src/wp_mldsa_kmgmt.c b/src/wp_mldsa_kmgmt.c index dea599a7..2cf2b1d5 100644 --- a/src/wp_mldsa_kmgmt.c +++ b/src/wp_mldsa_kmgmt.c @@ -1332,6 +1332,46 @@ static int wp_mldsa_dec_send_params(wp_MlDsa* mldsa, const char* dataType, return ok; } +#ifdef WOLFSSL_ENCRYPTED_KEYS +/** + * Decode an encrypted PKCS#8 DER ML-DSA private key into the ML-DSA key object. + * + * @param [in] ctx ML-DSA encoder/decoder context object. + * @param [in, out] mldsa ML-DSA key object. + * @param [in] data DER encoding (decrypted in place). + * @param [in] len Length, in bytes, of DER encoding. + * @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_mldsa_decode_enc_pki(wp_MlDsaEncDecCtx* ctx, wp_MlDsa* mldsa, + unsigned char* data, word32 len, OSSL_PASSPHRASE_CALLBACK* pwCb, + void* pwCbArg) +{ + int ok = 1; + word32 idx = 0; + + WOLFPROV_ENTER_SILENT(WP_LOG_COMP_PQC, WOLFPROV_FUNC_NAME); + + if (!wolfssl_prov_is_running()) { + ok = 0; + } + /* Decrypt the PBES2 EncryptedPrivateKeyInfo in place. */ + if (ok && (!wp_decrypt_key_pkcs8(data, &len, pwCb, pwCbArg))) { + ok = 0; + } + /* Decode the recovered plaintext private key. */ + if (ok && (ctx->decode(data, &idx, (void*)&mldsa->key, len) != 0)) { + ok = 0; + } + + WOLFPROV_LEAVE_SILENT(WP_LOG_COMP_PQC, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), + ok); + return ok; +} +#endif + /** * Decode the data in the core BIO. * @@ -1383,9 +1423,17 @@ 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) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "decode", rc); - ok = 0; - decoded = 0; +#ifdef WOLFSSL_ENCRYPTED_KEYS + /* 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, + pwCbArg))) +#endif + { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "decode", rc); + ok = 0; + decoded = 0; + } } } if (ok && (ctx->format == WP_ENC_FORMAT_SPKI)) { @@ -1444,13 +1492,16 @@ static int wp_mldsa_encode(wp_MlDsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, unsigned char* derData = NULL; word32 derAllocLen = 0; size_t derLen = 0; + unsigned char* encData = NULL; + size_t encLen = 0; + unsigned char* srcData; + size_t srcLen; unsigned char* pemData = NULL; size_t pemLen = 0; int pemType = (ctx->format == WP_ENC_FORMAT_SPKI) ? PUBLICKEY_TYPE : PKCS8_PRIVATEKEY_TYPE; int private = (ctx->format == WP_ENC_FORMAT_PKI) || (ctx->format == WP_ENC_FORMAT_EPKI); - byte* cipherInfo = NULL; WOLFPROV_ENTER(WP_LOG_COMP_PQC, "wp_mldsa_encode"); @@ -1470,15 +1521,9 @@ static int wp_mldsa_encode(wp_MlDsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, ok = 0; } else { - derAllocLen = (word32)rc; - /* EPKI encrypts in place: round up to the AES block size so the - * buffer has room for the padded ciphertext. */ - if (ctx->format == WP_ENC_FORMAT_EPKI) { - derAllocLen = ((derAllocLen + 15) / 16) * 16; - } - else { - derAllocLen += WP_MLDSA_DER_SLACK; - } + /* Buffer holds the plaintext PKCS #8 encoding; EPKI encrypts into + * a separate buffer allocated later. */ + derAllocLen = (word32)rc + WP_MLDSA_DER_SLACK; } } if (ok) { @@ -1496,23 +1541,39 @@ static int wp_mldsa_encode(wp_MlDsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, derLen = (size_t)rc; } } + /* By default the plaintext DER is the source for the output encoding. */ + srcData = derData; + srcLen = derLen; if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { - size_t encLen = derAllocLen; - if (!wp_encrypt_key(ctx->provCtx, ctx->cipherName, derData, &encLen, - (word32)derLen, pwCb, pwCbArg, &cipherInfo)) { + 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. */ + if (!wp_encrypt_key_pkcs8_size(ctx->provCtx, ctx->cipher, + (word32)derLen, &encLen)) { ok = 0; } - else { - derLen = encLen; + if (ok) { + encData = (unsigned char*)OPENSSL_malloc(encLen); + if (encData == NULL) { + ok = 0; + } + } + if (ok && (!wp_encrypt_key_pkcs8(ctx->provCtx, ctx->cipher, derData, + (word32)derLen, encData, &encLen, pwCb, pwCbArg))) { + ok = 0; + } + if (ok) { + srcData = encData; + srcLen = encLen; } } if (ok && (ctx->encoding == WP_FORMAT_DER)) { - keyData = derData; - keyLen = derLen; + keyData = srcData; + keyLen = srcLen; } else if (ok && (ctx->encoding == WP_FORMAT_PEM)) { - rc = wc_DerToPemEx(derData, (word32)derLen, NULL, 0, cipherInfo, + rc = wc_DerToPemEx(srcData, (word32)srcLen, NULL, 0, NULL, pemType); if (rc <= 0) { ok = 0; @@ -1525,8 +1586,8 @@ static int wp_mldsa_encode(wp_MlDsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, } } if (ok) { - rc = wc_DerToPemEx(derData, (word32)derLen, pemData, (word32)pemLen, - cipherInfo, pemType); + rc = wc_DerToPemEx(srcData, (word32)srcLen, pemData, (word32)pemLen, + NULL, pemType); if (rc <= 0) { ok = 0; } @@ -1553,7 +1614,7 @@ static int wp_mldsa_encode(wp_MlDsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, OPENSSL_free(derData); OPENSSL_free(pemData); } - OPENSSL_free(cipherInfo); + OPENSSL_clear_free(encData, encLen); WOLFPROV_LEAVE(WP_LOG_COMP_PQC, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } diff --git a/src/wp_rsa_kmgmt.c b/src/wp_rsa_kmgmt.c index becaa404..3ac51aa5 100644 --- a/src/wp_rsa_kmgmt.c +++ b/src/wp_rsa_kmgmt.c @@ -2433,41 +2433,6 @@ static int wp_rsa_decode_pki(wp_Rsa* rsa, unsigned char* data, word32 len) #ifdef WOLFSSL_ENCRYPTED_KEYS -/** PBKDF2 OPID. */ -unsigned char pbkdf2_oid[] = { - 42, 134, 72, 134, 247, 13, 1, 5, 12 -}; -/** Size of PBKDF2 OID. */ -#define PBKDF2_OID_SZ sizeof(pbkdf2_oid) - -/** - * Find the PBKDF2 OID in the key and set type. - * - * @param [in] data DER encoding. - * @param [in] len Length, in bytes, of DER encoding. - * @return 1 on success. - * @return 0 on failure. - */ -static int wp_rsa_find_pbkdf2_oid(unsigned char* data, word32 len) -{ - int ok = 0; - word32 i; - - WOLFPROV_ENTER_SILENT(WP_LOG_COMP_RSA, WOLFPROV_FUNC_NAME); - - for (i = 0; i < 40 && i + PBKDF2_OID_SZ < len; i++) { - /* Find the base OID. */ - if (XMEMCMP(data + i, pbkdf2_oid, PBKDF2_OID_SZ) == 0) { - ok = 1; - break; - } - } - - WOLFPROV_LEAVE_SILENT(WP_LOG_COMP_RSA, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), - ok); - return ok; -} - /** * Decode the encrypted DER encoded RSA private key into the RSA key object. * @@ -2483,41 +2448,21 @@ static int wp_rsa_decode_enc_pki(wp_Rsa* rsa, unsigned char* data, word32 len, OSSL_PASSPHRASE_CALLBACK* pwCb, void* pwCbArg) { int ok = 1; - char password[1024]; - size_t passwordSz = sizeof(password); WOLFPROV_ENTER_SILENT(WP_LOG_COMP_RSA, WOLFPROV_FUNC_NAME); if (!wolfssl_prov_is_running()) { ok = 0; } - - /* Look for the PBKDF2 OID to know we have an encrypted key. */ - if (ok && !wp_rsa_find_pbkdf2_oid(data, len)) { - ok = 0; - } - /* Get password for decryption. */ - if (ok && !pwCb(password, passwordSz, &passwordSz, NULL, pwCbArg)) { + /* Decrypt the PBES2 EncryptedPrivateKeyInfo in place. */ + if (ok && (!wp_decrypt_key_pkcs8(data, &len, pwCb, pwCbArg))) { ok = 0; } if (ok) { - /* Decrypt to encoded private key. */ - int ret = wc_DecryptPKCS8Key(data, len, password, (int)passwordSz); - if (ret <= 0) { - ok = 0; - } - else { - /* Get length of encoded private key. */ - len = (word32)ret; - } - } - if (ok) { - /* Decode private key. */ + /* Decode the recovered plaintext private key. */ ok = wp_rsa_decode_pki(rsa, data, len); } - OPENSSL_cleanse(password, sizeof(password)); - WOLFPROV_LEAVE_SILENT(WP_LOG_COMP_RSA, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; @@ -3224,26 +3169,15 @@ static int wp_rsa_encode_enc_pki_size(const wp_RsaEncDecCtx* ctx, { int ok; size_t len; - word32 outSz; - byte fakeData[1]; - byte fakeSalt[16]; WOLFPROV_ENTER(WP_LOG_COMP_RSA, "wp_rsa_encode_enc_pki_size"); - /* Get encode private key length. */ + /* Get the plaintext PKCS #8 encoding length. */ ok = wp_rsa_encode_pki_size(rsa, &len, RSA_ALGO_ID(ctx)); if (ok) { - /* Get encrypted encode private key. */ - if (wc_EncryptPKCS8Key(fakeData, (word32)len, NULL, &outSz, "", 0, - WP_PKCS5, WP_PBES2, ctx->cipher, fakeSalt, sizeof(fakeSalt), - WP_PKCS12_ITERATIONS_DEFAULT, wp_provctx_get_rng(ctx->provCtx), - NULL) != LENGTH_ONLY_E) { - ok = 0; - } - else { - /* Return the length calculated. */ - *keyLen = (size_t)outSz; - } + /* Get the size of the PBES2 EncryptedPrivateKeyInfo encoding. */ + ok = wp_encrypt_key_pkcs8_size(ctx->provCtx, ctx->cipher, (word32)len, + keyLen); } WOLFPROV_LEAVE(WP_LOG_COMP_RSA, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); @@ -3251,7 +3185,7 @@ static int wp_rsa_encode_enc_pki_size(const wp_RsaEncDecCtx* ctx, } /** - * Encode the RSA key in a Encrypted Private Key format. + * Encode the RSA key in a PBES2 EncryptedPrivateKeyInfo format. * * @param [in] ctx RSA encoder/decoder context object. * @param [in] rsa RSA key object. @@ -3269,51 +3203,27 @@ static int wp_rsa_encode_enc_pki(const wp_RsaEncDecCtx* ctx, const wp_Rsa* rsa, { int ok = 1; size_t len; - word32 outSz = (word32)*keyLen; - byte salt[WP_MAX_SALT_SIZE]; - int saltLen = 16; - char password[1024]; - size_t passwordSz = sizeof(password); byte* encodedKey = NULL; WOLFPROV_ENTER(WP_LOG_COMP_RSA, "wp_rsa_encode_enc_pki"); - /* TODO: support salt length of 8 for DES3. */ - - /* Encode key. */ + /* Determine the plaintext PKCS #8 length. */ ok = wp_rsa_encode_pki_size(rsa, &len, RSA_ALGO_ID(ctx)); if (ok) { - /* Allocate buffer for encrypted key to be placed into. */ + /* Allocate the plaintext buffer - must differ from the output. */ encodedKey = XMALLOC(len, NULL, DYNAMIC_TYPE_RSA_BUFFER); if (encodedKey == NULL) { ok = 0; } } if (ok) { - /* Encode key. */ + /* Encode the plaintext PKCS #8 key. */ ok = wp_rsa_encode_pki(rsa, encodedKey, &len, RSA_ALGO_ID(ctx)); } - /* Generate salt. */ - if (ok && (wc_RNG_GenerateBlock(wp_provctx_get_rng(ctx->provCtx), salt, - saltLen) != 0)) { - ok = 0; - } - /* Get password. */ - if (ok && !pwCb(password, passwordSz, &passwordSz, NULL, pwCbArg)) { - ok = 0; - } if (ok) { - /* Encrypt encoded key - in and out buffers must be different. */ - if (wc_EncryptPKCS8Key(encodedKey, (word32)len, keyData, &outSz, - password, (word32)passwordSz, WP_PKCS5, WP_PBES2, ctx->cipher, - salt, saltLen, WP_PKCS12_ITERATIONS_DEFAULT, - wp_provctx_get_rng(ctx->provCtx), NULL) <= 0) { - ok = 0; - } - else { - /* Return actual size of encrypted encoded private key. */ - *keyLen = (size_t)outSz; - } + /* Encrypt as a PBES2 EncryptedPrivateKeyInfo. */ + ok = wp_encrypt_key_pkcs8(ctx->provCtx, ctx->cipher, encodedKey, + (word32)len, keyData, keyLen, pwCb, pwCbArg); } /* encodedKey holds the plaintext PKCS#8 private key before encryption. */ @@ -3322,67 +3232,6 @@ static int wp_rsa_encode_enc_pki(const wp_RsaEncDecCtx* ctx, const wp_Rsa* rsa, } XFREE(encodedKey, NULL, DYNAMIC_TYPE_RSA_BUFFER); - OPENSSL_cleanse(password, sizeof(password)); - - WOLFPROV_LEAVE(WP_LOG_COMP_RSA, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); - return ok; -} - -/** - * Get the Encrypted PKCS#8 encoding size for the key. - * - * @param [in] rsa RSA key object. - * @param [out] keyLen Length of encoding in bytes. - * @return 1 on success. - * @return 0 on failure. - */ -static int wp_rsa_encode_epki_size(const wp_RsaEncDecCtx* ctx, - const wp_Rsa* rsa, size_t* keyLen) -{ - int ok; - size_t len; - - WOLFPROV_ENTER(WP_LOG_COMP_RSA, "wp_rsa_encode_epki_size"); - - ok = wp_rsa_encode_pki_size(rsa, &len, RSA_ALGO_ID(ctx)); - if (ok) { - *keyLen = ((len + 15) / 16) * 16; - } - - WOLFPROV_LEAVE(WP_LOG_COMP_RSA, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); - return ok; -} - -/** - * Encode the RSA key in an Encrypted PKCS#8 format. - * - * @param [in] ctx RSA encoder/decoder context object. - * @param [in] rsa RSA 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. - * @param [out] cipherInfo Information about encryption. - * @return 1 on success. - * @return 0 on failure. - */ -static int wp_rsa_encode_epki(const wp_RsaEncDecCtx* ctx, const wp_Rsa* rsa, - unsigned char* keyData, size_t* keyLen, OSSL_PASSPHRASE_CALLBACK* pwCb, - void* pwCbArg, byte** cipherInfo) -{ - int ok = 1; - size_t len = *keyLen; - - WOLFPROV_ENTER(WP_LOG_COMP_RSA, "wp_rsa_encode_epki"); - - /* Encode key. */ - ok = wp_rsa_encode_pki(rsa, keyData, &len, RSA_ALGO_ID(ctx)); - if (ok && (!wp_encrypt_key(ctx->provCtx, ctx->cipherName, keyData, keyLen, - (word32)len, pwCb, pwCbArg, cipherInfo))) { - ok = 0; - } - WOLFPROV_LEAVE(WP_LOG_COMP_RSA, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } @@ -3422,7 +3271,6 @@ static int wp_rsa_encode(wp_RsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, size_t pemLen = 0; int pemType = PKCS8_PRIVATEKEY_TYPE; int private = 0; - byte* cipherInfo = NULL; (void)params; (void)selection; @@ -3451,7 +3299,7 @@ static int wp_rsa_encode(wp_RsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, } #ifdef WOLFSSL_ENCRYPTED_KEYS else if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { - if (!wp_rsa_encode_epki_size(ctx, key, &derLen)) { + if (!wp_rsa_encode_enc_pki_size(ctx, key, &derLen)) { ok = 0; } } @@ -3499,8 +3347,8 @@ static int wp_rsa_encode(wp_RsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, #ifdef WOLFSSL_ENCRYPTED_KEYS else if (ok && (ctx->format == WP_ENC_FORMAT_EPKI)) { private = 1; - if (!wp_rsa_encode_epki(ctx, key, derData, &derLen, pwCb, pwCbArg, - (ctx->encoding == WP_FORMAT_PEM) ? &cipherInfo : NULL)) { + pemType = PKCS8_ENC_PRIVATEKEY_TYPE; + if (!wp_rsa_encode_enc_pki(ctx, key, derData, &derLen, pwCb, pwCbArg)) { ok = 0; } } @@ -3525,7 +3373,7 @@ static int wp_rsa_encode(wp_RsaEncDecCtx* ctx, OSSL_CORE_BIO* cBio, keyLen = derLen; } else if (ok && (ctx->encoding == WP_FORMAT_PEM)) { - rc = wc_DerToPemEx(derData, (word32)derLen, NULL, 0, cipherInfo, + rc = wc_DerToPemEx(derData, (word32)derLen, NULL, 0, NULL, pemType); if (rc <= 0) { ok = 0; diff --git a/test/test_dh.c b/test/test_dh.c index 11ee1896..48e50dbf 100644 --- a/test/test_dh.c +++ b/test/test_dh.c @@ -360,6 +360,43 @@ int test_dh_pkey(void *data) return err; } +#if defined(WOLFSSL_DH_EXTRA) && defined(WP_HAVE_EPKI_TEST) +int test_dh_encode_epki(void *data) +{ + int err = 0; + const unsigned char* p = dh_der; + EVP_PKEY* pkey = NULL; + + (void)data; + + /* Load a DH key into a wolfProvider-backed EVP_PKEY. */ + pkey = d2i_PrivateKey_ex(EVP_PKEY_DH, NULL, &p, sizeof(dh_der), wpLibCtx, + NULL); + err = (pkey == NULL); + + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo DER: wolfProvider -> OpenSSL"); + err = test_epki_encode_decode(pkey, "DER", osslLibCtx); + } + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo DER: wolfProvider -> wolfProvider"); + err = test_epki_encode_decode(pkey, "DER", wpLibCtx); + } + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo PEM: wolfProvider -> OpenSSL"); + err = test_epki_encode_decode(pkey, "PEM", osslLibCtx); + } + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo PEM: wolfProvider -> wolfProvider"); + err = test_epki_encode_decode(pkey, "PEM", wpLibCtx); + } + + EVP_PKEY_free(pkey); + + return err; +} +#endif /* WOLFSSL_DH_EXTRA && WP_HAVE_EPKI_TEST */ + int test_dh_invalid_kdf_strings(void *data) { int err = 0; diff --git a/test/test_ecc.c b/test/test_ecc.c index 2783963b..518b9420 100644 --- a/test/test_ecc.c +++ b/test/test_ecc.c @@ -859,6 +859,43 @@ static int test_ecdh(const unsigned char *privKey, size_t len, } #ifdef WP_HAVE_EC_P256 +#ifdef WP_HAVE_EPKI_TEST +int test_ecc_encode_epki(void *data) +{ + int err = 0; + const unsigned char* p = ecc_key_der_256; + EVP_PKEY* pkey = NULL; + + (void)data; + + /* Load a P-256 key into a wolfProvider-backed EVP_PKEY. */ + pkey = d2i_PrivateKey_ex(EVP_PKEY_EC, NULL, &p, sizeof(ecc_key_der_256), + wpLibCtx, NULL); + err = (pkey == NULL); + + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo DER: wolfProvider -> OpenSSL"); + err = test_epki_encode_decode(pkey, "DER", osslLibCtx); + } + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo DER: wolfProvider -> wolfProvider"); + err = test_epki_encode_decode(pkey, "DER", wpLibCtx); + } + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo PEM: wolfProvider -> OpenSSL"); + err = test_epki_encode_decode(pkey, "PEM", osslLibCtx); + } + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo PEM: wolfProvider -> wolfProvider"); + err = test_epki_encode_decode(pkey, "PEM", wpLibCtx); + } + + EVP_PKEY_free(pkey); + + return err; +} +#endif /* WP_HAVE_EPKI_TEST */ + int test_ecdh_invalid_kdf_strings(void *data) { int err = 0; diff --git a/test/test_ecx.c b/test/test_ecx.c index 6c6ccabf..8a0f13e0 100644 --- a/test/test_ecx.c +++ b/test/test_ecx.c @@ -135,6 +135,38 @@ static int sign_verify(unsigned char* sig, size_t sigLen, return err; } +#if defined(WP_HAVE_ED25519) && defined(WP_HAVE_EPKI_TEST) +int test_ecx_encode_epki(void *data) +{ + int err = 0; + const unsigned char* p = ed25519_key_der; + EVP_PKEY* pkey = NULL; + + (void)data; + + /* Load an Ed25519 key into a wolfProvider-backed EVP_PKEY. */ + pkey = d2i_PrivateKey_ex(EVP_PKEY_ED25519, NULL, &p, sizeof(ed25519_key_der), + 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. */ + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo DER: wolfProvider -> wolfProvider"); + err = test_epki_encode_decode(pkey, "DER", wpLibCtx); + } + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo PEM: wolfProvider -> wolfProvider"); + err = test_epki_encode_decode(pkey, "PEM", wpLibCtx); + } + + EVP_PKEY_free(pkey); + + return err; +} +#endif /* WP_HAVE_ED25519 && WP_HAVE_EPKI_TEST */ + int test_ecx_sign_verify(void *data) { int err = 0; diff --git a/test/test_mldsa.c b/test/test_mldsa.c index f0a677f0..3e415d9d 100644 --- a/test/test_mldsa.c +++ b/test/test_mldsa.c @@ -1220,4 +1220,34 @@ int test_mldsa_pubonly_sign_fails(void* data) return err; } +#ifdef WP_HAVE_EPKI_TEST +int test_mldsa_encode_epki(void* data) +{ + int err = 0; + EVP_PKEY* pkey = NULL; + + (void)data; + + /* Generate an ML-DSA-44 key via wolfProvider. */ + err = mldsa_keygen("ML-DSA-44", &pkey); + + /* wolfProvider self round-trip (DER and PEM): the encoder output is decoded + * back by wolfProvider and must match. Stock-OpenSSL interop for ML-DSA + * encrypted keys depends on separate ML-DSA/PQC work and is not asserted + * here. */ + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo DER: wolfProvider -> wolfProvider"); + err = test_epki_encode_decode(pkey, "DER", wpLibCtx); + } + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo PEM: wolfProvider -> wolfProvider"); + err = test_epki_encode_decode(pkey, "PEM", wpLibCtx); + } + + EVP_PKEY_free(pkey); + + return err; +} +#endif /* WP_HAVE_EPKI_TEST */ + #endif /* WP_HAVE_MLDSA */ diff --git a/test/test_pkey.c b/test/test_pkey.c index e4ee6575..15a27291 100644 --- a/test/test_pkey.c +++ b/test/test_pkey.c @@ -19,6 +19,9 @@ */ #include "unit.h" +#include +#include +#include int test_digest_sign(EVP_PKEY *pkey, OSSL_LIB_CTX* libCtx, unsigned char *data, size_t len, const char *md, const EVP_MD *mgf1Md, unsigned char *sig, @@ -295,3 +298,97 @@ int test_pkey_dec(EVP_PKEY *pkey, OSSL_LIB_CTX* libCtx, unsigned char *msg, return err; } +/* + * Encode a wolfProvider key as a PBES2 EncryptedPrivateKeyInfo, decode it back + * with decLibCtx and check it matches, and assert a wrong passphrase fails. + * Use a stock-OpenSSL decLibCtx to also check interop. + */ +int test_epki_encode_decode(EVP_PKEY* pkey, const char* fmt, + OSSL_LIB_CTX* decLibCtx) +{ + 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); + + /* Encode as EncryptedPrivateKeyInfo using wolfProvider. */ + ectx = OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_KEYPAIR, fmt, + "EncryptedPrivateKeyInfo", "provider=libwolfprov"); + 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) { + /* Save the encoded length; OSSL_DECODER_from_data consumes it. */ + encLen = dataLen; + } + + /* Decode with the requested library context and the correct passphrase. */ + 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) { + err = EVP_PKEY_eq(pkey, pkey2) != 1; + } + + /* Negative case: a wrong passphrase must not yield a key. */ + 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) { + /* Decode is expected to fail; success with a recovered key is wrong. */ + if ((OSSL_DECODER_from_data(bctx, &pp, &dataLen) == 1) && + (badKey != NULL)) { + err = 1; + } + /* The failed decrypt leaves an expected error on the queue. */ + 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; +} + diff --git a/test/test_rsa.c b/test/test_rsa.c index ae7eb59a..d65f6077 100644 --- a/test/test_rsa.c +++ b/test/test_rsa.c @@ -24,6 +24,8 @@ #include #include +#include +#include #ifdef WP_HAVE_RSA @@ -2141,6 +2143,48 @@ int test_rsa_encode_pkcs8(void* data) return err; } +#ifdef WP_HAVE_EPKI_TEST +int test_rsa_encode_epki(void* data) +{ + int err = 0; + const unsigned char* p = rsa_key_der_2048_pkcs8; + PKCS8_PRIV_KEY_INFO* p8 = NULL; + EVP_PKEY* pkey = NULL; + + (void)data; + + /* Load the key into a wolfProvider-backed EVP_PKEY. */ + p8 = d2i_PKCS8_PRIV_KEY_INFO(NULL, &p, sizeof(rsa_key_der_2048_pkcs8)); + err = (p8 == NULL); + if (err == 0) { + pkey = EVP_PKCS82PKEY_ex(p8, wpLibCtx, NULL); + err = (pkey == NULL); + } + + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo DER: wolfProvider -> OpenSSL"); + err = test_epki_encode_decode(pkey, "DER", osslLibCtx); + } + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo DER: wolfProvider -> wolfProvider"); + err = test_epki_encode_decode(pkey, "DER", wpLibCtx); + } + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo PEM: wolfProvider -> OpenSSL"); + err = test_epki_encode_decode(pkey, "PEM", osslLibCtx); + } + if (err == 0) { + PRINT_MSG("EncryptedPrivateKeyInfo PEM: wolfProvider -> wolfProvider"); + err = test_epki_encode_decode(pkey, "PEM", wpLibCtx); + } + + EVP_PKEY_free(pkey); + PKCS8_PRIV_KEY_INFO_free(p8); + + return err; +} +#endif /* WP_HAVE_EPKI_TEST */ + static int test_rsa_null_sign_init_ex(OSSL_LIB_CTX *libCtx) { int err = 0; diff --git a/test/unit.c b/test/unit.c index a4035e10..960927be 100644 --- a/test/unit.c +++ b/test/unit.c @@ -308,6 +308,9 @@ 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) + TEST_DECL(test_dh_encode_epki, NULL), +#endif TEST_DECL(test_dh_decode, NULL), TEST_DECL(test_dh_krb5_keygen, NULL), TEST_DECL(test_dh_pad, NULL), @@ -341,6 +344,9 @@ TEST_CASE test_case[] = { TEST_DECL(test_rsa_fromdata_oversize, NULL), TEST_DECL(test_rsa_decode_pkcs8, NULL), TEST_DECL(test_rsa_encode_pkcs8, NULL), +#ifdef WP_HAVE_EPKI_TEST + TEST_DECL(test_rsa_encode_epki, NULL), +#endif TEST_DECL(test_rsa_null_init, NULL), TEST_DECL(test_rsa_param_prefix_match, NULL), TEST_DECL(test_rsa_kem_prefix_match, NULL), @@ -378,6 +384,9 @@ TEST_CASE test_case[] = { #endif #endif #ifdef WP_HAVE_EC_P256 + #ifdef WP_HAVE_EPKI_TEST + TEST_DECL(test_ecc_encode_epki, NULL), + #endif #ifdef WP_HAVE_ECKEYGEN TEST_DECL(test_eckeygen_p256, NULL), #endif @@ -475,6 +484,9 @@ TEST_CASE test_case[] = { #endif #if defined(WP_HAVE_ED25519) || defined(WP_HAVE_ED448) +#if defined(WP_HAVE_ED25519) && defined(WP_HAVE_EPKI_TEST) + TEST_DECL(test_ecx_encode_epki, NULL), +#endif TEST_DECL(test_ecx_sign_verify, NULL), TEST_DECL(test_ecx_sign_verify_raw_priv, NULL), TEST_DECL(test_ecx_sign_verify_raw_pub, NULL), @@ -516,6 +528,9 @@ TEST_CASE test_case[] = { #endif #ifdef WP_HAVE_MLDSA + #ifdef WP_HAVE_EPKI_TEST + TEST_DECL(test_mldsa_encode_epki, NULL), + #endif TEST_DECL(test_mldsa_keygen, NULL), TEST_DECL(test_mldsa_import_export_roundtrip, NULL), TEST_DECL(test_mldsa_sign_verify, NULL), diff --git a/test/unit.h b/test/unit.h index 6d6f780d..b6d670ed 100644 --- a/test/unit.h +++ b/test/unit.h @@ -48,6 +48,13 @@ #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) + #define WP_HAVE_EPKI_TEST +#endif + #ifdef TEST_MULTITHREADED #define PRINT_MSG(str) #define PRINT_ERR_MSG(str) @@ -283,6 +290,8 @@ 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, + 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); @@ -303,6 +312,9 @@ int test_rsa_fromdata(void* data); int test_rsa_fromdata_oversize(void* data); int test_rsa_decode_pkcs8(void* data); int test_rsa_encode_pkcs8(void* data); +#ifdef WP_HAVE_EPKI_TEST +int test_rsa_encode_epki(void* data); +#endif int test_rsa_null_init(void* data); int test_rsa_param_prefix_match(void* data); int test_rsa_kem_prefix_match(void* data); @@ -314,6 +326,9 @@ int test_rsa_kem(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) +int test_dh_encode_epki(void *data); +#endif int test_dh_decode(void *data); int test_dh_get_params(void *data); int test_dh_krb5_keygen(void *data); @@ -403,6 +418,9 @@ int test_ecdh_p224(void *data); #endif /* WP_HAVE_EC_P224 */ #ifdef WP_HAVE_EC_P256 int test_ecdh_invalid_kdf_strings(void *data); +#ifdef WP_HAVE_EPKI_TEST +int test_ecc_encode_epki(void *data); +#endif int test_ecdh_p256(void *data); #if defined(HAVE_X963_KDF) && defined(WP_HAVE_SHA256) int test_ecdh_x963_kdf(void *data); @@ -472,6 +490,9 @@ int test_pbkdf2(void *data); #endif /* WP_HAVE_PBE */ #if defined(WP_HAVE_ED25519) || defined(WP_HAVE_ED448) +#if defined(WP_HAVE_ED25519) && defined(WP_HAVE_EPKI_TEST) +int test_ecx_encode_epki(void *data); +#endif int test_ecx_sign_verify(void *data); int test_ecx_sign_verify_raw_priv(void *data); int test_ecx_sign_verify_raw_pub(void *data); @@ -514,6 +535,9 @@ int test_mlx_encap_decap(void *data); #endif #ifdef WP_HAVE_MLDSA +#ifdef WP_HAVE_EPKI_TEST +int test_mldsa_encode_epki(void *data); +#endif int test_mldsa_keygen(void *data); int test_mldsa_import_export_roundtrip(void *data); int test_mldsa_sign_verify(void *data);