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/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..04efff3b --- /dev/null +++ b/scripts/cmd_test/pkcs8-cmd-test.sh @@ -0,0 +1,105 @@ +#!/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 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 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" +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 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 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 745edf93..abebc1f9 100644 --- a/src/wp_dec_pem2der.c +++ b/src/wp_dec_pem2der.c @@ -320,17 +320,26 @@ 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; +#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 { ok = 0; } @@ -466,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 5e2b0e5d..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); @@ -2359,7 +2372,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 +2489,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 { @@ -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); @@ -2637,169 +2646,97 @@ 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. + * The caller must hold dh->keyMutex. * - * @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 */ - 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; } } - - ret = wc_DhPrivKeyToDer((DhKey*)&dh->key, NULL, &len); - if (ret != LENGTH_ONLY_E) { - ok = 0; - } - if (ok) { - *keyLen = len; - } - WOLFPROV_LEAVE(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } /** - * Encode the DH key in a PKCS#8 format. + * Get the PKCS#8 encoding size for the key. * - * @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] 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(const wp_Dh *dh, unsigned char* keyData, - size_t* keyLen) +static int wp_dh_encode_pki_size(const wp_Dh *dh, size_t* keyLen) { int ok = 1; int ret; - word32 len = (word32)*keyLen; + word32 len; - WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_encode_pki"); + WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_encode_pki_size"); - ret = wc_DhPrivKeyToDer((DhKey*)&dh->key, keyData, &len); - if (ret <= 0) { - ok = 0; + 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; - /* wolfSSL calculating it wrong. */ - if (keyData[1] == 0x81) { - keyData[2] = len - 3; - } } WOLFPROV_LEAVE(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } -#ifdef WOLFSSL_ENCRYPTED_KEYS /** - * Get the Encrypted PKCS#8 encoding size for the key. + * Encode the DH key in a PKCS#8 format. * - * @param [in] ctx DH encoder/decoder context object. - * @param [in] dh DH key object. - * @param [out] keyLen Length of encoding in bytes. + * @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. * @return 1 on success. * @return 0 on failure. */ -static int wp_dh_encode_epki_size(const wp_DhEncDecCtx* ctx, const wp_Dh *dh, +static int wp_dh_encode_pki(const wp_Dh *dh, unsigned char* keyData, size_t* keyLen) { int ok = 1; int ret; - word32 len; + word32 len = (word32)*keyLen; - WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_encode_epki_size"); + WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_encode_pki"); - /* Get the plaintext PKCS #8 length. */ - ret = wc_DhPrivKeyToDer((DhKey*)&dh->key, NULL, &len); - if (ret != LENGTH_ONLY_E) { + ret = wc_DhPrivKeyToDer((DhKey*)&dh->key, keyData, &len); + if (ret <= 0) { ok = 0; } if (ok) { - /* Get the size of the PBES2 EncryptedPrivateKeyInfo encoding. */ - ok = wp_encrypt_key_pkcs8_size(ctx->provCtx, ctx->cipher, len, keyLen); + *keyLen = len; } 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) { - /* 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 /** @@ -2834,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; } @@ -2861,22 +2823,33 @@ 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) { + encrypted = 1; + if (!wp_dh_encode_pki_size(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)) { + 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; } @@ -2896,19 +2869,72 @@ 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_pki(key, derData, &derLen)) { + 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; - 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; @@ -2946,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 4d6e1b57..f566f0fa 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,17 @@ 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)) { + WOLFPROV_ERROR_MSG(WP_LOG_COMP_ECC, + "encrypted traditional EC encoding is not supported"); + 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 +2968,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 +3028,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..513b7349 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,11 +901,14 @@ 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. * - * 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. @@ -923,9 +927,22 @@ 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; } - if (ok) { + 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 { +#ifdef WP_HAVE_PKCS8_ENC size_t i; for (i = 0; i < WP_CIPHER_NAMES_LEN; i++) { @@ -939,9 +956,24 @@ int wp_cipher_from_params(const OSSL_PARAM params[], int* cipher, break; } } + /* 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) { + *cipherName = NULL; + } ok = 0; } +#else + /* This build cannot encrypt private keys. */ + *cipher = 0; + if (cipherName != NULL) { + *cipherName = NULL; + } + ok = 0; +#endif } } @@ -974,19 +1006,32 @@ 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; +#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 + /* 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; } + else if (ok) { + rngLocked = 1; + } +#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 @@ -1001,6 +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 + if (rngLocked) { + wp_provctx_unlock_rng(provCtx); + rngLocked = 0; + } +#endif + if (ok) { if (rc != LENGTH_ONLY_E) { ok = 0; } @@ -1044,10 +1097,14 @@ 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; + 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; @@ -1055,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"); @@ -1068,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; @@ -1080,10 +1143,15 @@ int wp_encrypt_key_pkcs8(WOLFPROV_CTX* provCtx, int cipher, if (ok && (passwordSz > WP_EPKI_PASSWORD_MAX)) { ok = 0; } +#ifndef WP_SINGLE_THREADED + if (ok && (wp_provctx_lock_rng(provCtx) != 1)) { + ok = 0; + } + else if (ok) { + rngLocked = 1; + } +#endif 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) { @@ -1101,16 +1169,21 @@ 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; } else { - *outLen = (size_t)outSz; + *outLen = (size_t)rc; } } @@ -1140,7 +1213,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 @@ -1182,7 +1255,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 @@ -1505,4 +1578,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..a9fe71b1 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,24 @@ 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)) { + WOLFPROV_ERROR_MSG(WP_LOG_COMP_RSA, + "encrypted traditional RSA encoding is not supported"); + 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 +3582,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 +3617,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 +3629,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; @@ -4602,4 +4613,3 @@ const OSSL_DISPATCH wp_rsa_text_encoder_functions[] = { }; #endif /* WP_HAVE_RSA */ - 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..03a0d8bf 100644 --- a/test/test_ecc.c +++ b/test/test_ecc.c @@ -21,7 +21,9 @@ #include "unit.h" #include +#include #include +#include #include #include @@ -945,6 +947,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 +958,32 @@ 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_KEYPAIR, + "DER", "type-specific", "provider=libwolfprov"); + 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); + if (err == 0) { PRINT_MSG("EncryptedPrivateKeyInfo DER: wolfProvider -> OpenSSL"); err = test_epki_encode_decode(pkey, "DER", "provider=libwolfprov", @@ -992,6 +1023,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..94025425 100644 --- a/test/test_pkey.c +++ b/test/test_pkey.c @@ -298,6 +298,169 @@ 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_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; + const char* pass = "wolfprov-test-pass"; + 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); + 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); + 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; + } + 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, XSTRLEN(badPass)) != 1; + } + if (err == 0) { + if (OSSL_DECODER_from_data(bctx, &pp, &dataLen) == 1) { + PRINT_ERR_MSG("Wrong passphrase was accepted"); + 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, @@ -315,7 +478,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, @@ -349,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. */ @@ -367,7 +540,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. */ @@ -388,4 +561,3 @@ int test_epki_encode_decode(EVP_PKEY* pkey, const char* fmt, return err; } - diff --git a/test/test_rsa.c b/test/test_rsa.c index bfa818ef..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", @@ -2427,6 +2457,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);