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);