From 1f14324d5fdbe704475a58fabef662e2c31108d7 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 3 Jun 2026 21:58:05 +0000 Subject: [PATCH 1/3] Add EVP_PKEY encoded public key get/set compatibility functions Implement wolfSSL_EVP_PKEY_set1_encoded_public_key and wolfSSL_EVP_PKEY_get1_encoded_public_key in the OpenSSL compatibility layer. Both the new OpenSSL 3.0 names and the deprecated EVP_PKEY_{set1,get1}_tls_encodedpoint names map to these single implementations. Supported key types: - EC: uncompressed octet point (0x04 || X || Y), reusing i2o_ECPublicKey / o2i_ECPublicKey. set1 also syncs the internal wolfCrypt key (SetECKeyInternal) so the key is usable by EVP_PKEY_derive, and refreshes the cached DER. - X25519 / X448: raw little-endian public key (RFC 7748). Adds test_wolfSSL_EVP_PKEY_encoded_public_key covering NULL handling, EC encode/decode round-trip and ECDH agreement, X25519/X448 round-trips, and deprecated-name parity. --- tests/api/test_evp_pkey.c | 217 +++++++++++++++++++++++++++++++++ tests/api/test_evp_pkey.h | 4 +- wolfcrypt/src/evp.c | 248 ++++++++++++++++++++++++++++++++++++++ wolfssl/openssl/evp.h | 10 ++ 4 files changed, 478 insertions(+), 1 deletion(-) diff --git a/tests/api/test_evp_pkey.c b/tests/api/test_evp_pkey.c index 6b15f38d962..6653d84029b 100644 --- a/tests/api/test_evp_pkey.c +++ b/tests/api/test_evp_pkey.c @@ -2861,3 +2861,220 @@ int test_wolfSSL_EVP_PKEY_x448(void) return EXPECT_RESULT(); } +int test_wolfSSL_EVP_PKEY_encoded_public_key(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && (defined(HAVE_ECC) || defined(HAVE_CURVE25519) || \ + defined(HAVE_CURVE448)) + /* Type-independent bad-argument handling. The deprecated tls_encodedpoint + * names are macro aliases of these, so exercising one covers both. */ + { + unsigned char* p = NULL; + ExpectIntEQ((int)EVP_PKEY_get1_encoded_public_key(NULL, &p), 0); + ExpectIntEQ(EVP_PKEY_set1_encoded_public_key(NULL, + (const unsigned char*)"abc", 3), 0); + } + +#ifdef HAVE_ECC + { + EC_KEY* ec1 = NULL; + EC_KEY* ec2 = NULL; + EVP_PKEY* pkey1 = NULL; + EVP_PKEY* pkey2 = NULL; + unsigned char* enc = NULL; + unsigned char* enc2 = NULL; + unsigned char* encTls = NULL; + size_t encLen = 0; + size_t encLen2 = 0; + size_t encLenTls = 0; + + /* EVP_PKEY holding a generated P-256 key. */ + ExpectNotNull(ec1 = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + ExpectIntEQ(EC_KEY_generate_key(ec1), 1); + ExpectNotNull(pkey1 = EVP_PKEY_new()); + ExpectIntEQ(EVP_PKEY_set1_EC_KEY(pkey1, ec1), 1); + + /* Bad arguments with a valid key. */ + ExpectIntEQ((int)EVP_PKEY_get1_encoded_public_key(pkey1, NULL), 0); + ExpectIntEQ(EVP_PKEY_set1_encoded_public_key(pkey1, NULL, 10), 0); + ExpectIntEQ(EVP_PKEY_set1_encoded_public_key(pkey1, enc, 0), 0); + + /* get1 returns the uncompressed point: 0x04 || X || Y == 65 bytes. */ + ExpectIntEQ((int)(encLen = + EVP_PKEY_get1_encoded_public_key(pkey1, &enc)), 65); + ExpectNotNull(enc); + if (enc != NULL) { + ExpectIntEQ(enc[0], 0x04); + } + + /* Deprecated alias must produce identical output. */ + ExpectIntEQ((int)(encLenTls = + EVP_PKEY_get1_tls_encodedpoint(pkey1, &encTls)), (int)encLen); + ExpectBufEQ(encTls, enc, encLen); + + /* set1 into a second key with the same curve, then round-trip get1. */ + ExpectNotNull(ec2 = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + ExpectIntEQ(EC_KEY_generate_key(ec2), 1); + ExpectNotNull(pkey2 = EVP_PKEY_new()); + ExpectIntEQ(EVP_PKEY_set1_EC_KEY(pkey2, ec2), 1); + ExpectIntEQ(EVP_PKEY_set1_encoded_public_key(pkey2, enc, encLen), 1); + ExpectIntEQ((int)(encLen2 = + EVP_PKEY_get1_encoded_public_key(pkey2, &enc2)), (int)encLen); + ExpectBufEQ(enc2, enc, encLen); + + OPENSSL_free(enc); + OPENSSL_free(enc2); + OPENSSL_free(encTls); + EC_KEY_free(ec1); + EC_KEY_free(ec2); + EVP_PKEY_free(pkey1); + EVP_PKEY_free(pkey2); + } + + /* set1 must produce a peer key usable for ECDH, i.e. the internal wolfCrypt + * key (consumed by EVP_PKEY_derive) is synced, not just the wire bytes. */ + { + EC_KEY* aKey = NULL; + EC_KEY* bKey = NULL; + EC_KEY* pKey = NULL; + EVP_PKEY* alice = NULL; + EVP_PKEY* bob = NULL; + EVP_PKEY* peer = NULL; + EVP_PKEY_CTX* ctx = NULL; + unsigned char* bobEnc = NULL; + size_t bobEncLen = 0; + unsigned char secretRef[80]; + unsigned char secret[80]; + size_t refLen = sizeof(secretRef); + size_t secLen = sizeof(secret); + + ExpectNotNull(aKey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + ExpectIntEQ(EC_KEY_generate_key(aKey), 1); + ExpectNotNull(alice = EVP_PKEY_new()); + ExpectIntEQ(EVP_PKEY_set1_EC_KEY(alice, aKey), 1); + + ExpectNotNull(bKey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + ExpectIntEQ(EC_KEY_generate_key(bKey), 1); + ExpectNotNull(bob = EVP_PKEY_new()); + ExpectIntEQ(EVP_PKEY_set1_EC_KEY(bob, bKey), 1); + + /* Reference shared secret: alice + the real bob. */ + ExpectNotNull(ctx = EVP_PKEY_CTX_new(alice, NULL)); + ExpectIntEQ(EVP_PKEY_derive_init(ctx), 1); + ExpectIntEQ(EVP_PKEY_derive_set_peer(ctx, bob), 1); + ExpectIntEQ(EVP_PKEY_derive(ctx, secretRef, &refLen), 1); + EVP_PKEY_CTX_free(ctx); + ctx = NULL; + + /* Load bob's public point into a fresh peer key (using the deprecated + * set name to also cover that alias). */ + ExpectIntGT((int)(bobEncLen = + EVP_PKEY_get1_encoded_public_key(bob, &bobEnc)), 0); + ExpectNotNull(pKey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + ExpectIntEQ(EC_KEY_generate_key(pKey), 1); + ExpectNotNull(peer = EVP_PKEY_new()); + ExpectIntEQ(EVP_PKEY_set1_EC_KEY(peer, pKey), 1); + ExpectIntEQ(EVP_PKEY_set1_tls_encodedpoint(peer, bobEnc, bobEncLen), 1); + + /* Secret with the set1-loaded peer must match the reference. */ + ExpectNotNull(ctx = EVP_PKEY_CTX_new(alice, NULL)); + ExpectIntEQ(EVP_PKEY_derive_init(ctx), 1); + ExpectIntEQ(EVP_PKEY_derive_set_peer(ctx, peer), 1); + ExpectIntEQ(EVP_PKEY_derive(ctx, secret, &secLen), 1); + EVP_PKEY_CTX_free(ctx); + ctx = NULL; + + ExpectIntEQ((int)secLen, (int)refLen); + ExpectBufEQ(secret, secretRef, refLen); + + OPENSSL_free(bobEnc); + EC_KEY_free(aKey); + EC_KEY_free(bKey); + EC_KEY_free(pKey); + EVP_PKEY_free(alice); + EVP_PKEY_free(bob); + EVP_PKEY_free(peer); + } +#endif /* HAVE_ECC */ + +#ifdef HAVE_CURVE25519 + { + EVP_PKEY_CTX* genCtx = NULL; + EVP_PKEY* pkey = NULL; + EVP_PKEY* peer = NULL; + unsigned char* enc = NULL; + unsigned char* enc2 = NULL; + unsigned char* encTls = NULL; + size_t encLen = 0; + size_t encLen2 = 0; + size_t encLenTls = 0; + + ExpectNotNull(genCtx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL)); + ExpectIntEQ(EVP_PKEY_keygen_init(genCtx), 1); + ExpectIntEQ(EVP_PKEY_keygen(genCtx, &pkey), 1); + ExpectIntEQ(EVP_PKEY_keygen(genCtx, &peer), 1); + EVP_PKEY_CTX_free(genCtx); + genCtx = NULL; + + /* Raw X25519 public key is 32 bytes. */ + ExpectIntEQ((int)(encLen = + EVP_PKEY_get1_encoded_public_key(pkey, &enc)), 32); + ExpectNotNull(enc); + + /* Deprecated alias parity. */ + ExpectIntEQ((int)(encLenTls = + EVP_PKEY_get1_tls_encodedpoint(pkey, &encTls)), (int)encLen); + ExpectBufEQ(encTls, enc, encLen); + + /* set1 into the peer key and round-trip. */ + ExpectIntEQ(EVP_PKEY_set1_encoded_public_key(peer, enc, encLen), 1); + ExpectIntEQ((int)(encLen2 = + EVP_PKEY_get1_encoded_public_key(peer, &enc2)), (int)encLen); + ExpectBufEQ(enc2, enc, encLen); + + OPENSSL_free(enc); + OPENSSL_free(enc2); + OPENSSL_free(encTls); + EVP_PKEY_free(pkey); + EVP_PKEY_free(peer); + } +#endif /* HAVE_CURVE25519 */ + +#ifdef HAVE_CURVE448 + { + EVP_PKEY_CTX* genCtx = NULL; + EVP_PKEY* pkey = NULL; + EVP_PKEY* peer = NULL; + unsigned char* enc = NULL; + unsigned char* enc2 = NULL; + size_t encLen = 0; + size_t encLen2 = 0; + + ExpectNotNull(genCtx = EVP_PKEY_CTX_new_id(EVP_PKEY_X448, NULL)); + ExpectIntEQ(EVP_PKEY_keygen_init(genCtx), 1); + ExpectIntEQ(EVP_PKEY_keygen(genCtx, &pkey), 1); + ExpectIntEQ(EVP_PKEY_keygen(genCtx, &peer), 1); + EVP_PKEY_CTX_free(genCtx); + genCtx = NULL; + + /* Raw X448 public key is 56 bytes. */ + ExpectIntEQ((int)(encLen = + EVP_PKEY_get1_encoded_public_key(pkey, &enc)), 56); + ExpectNotNull(enc); + + /* set1 into the peer key and round-trip. */ + ExpectIntEQ(EVP_PKEY_set1_encoded_public_key(peer, enc, encLen), 1); + ExpectIntEQ((int)(encLen2 = + EVP_PKEY_get1_encoded_public_key(peer, &enc2)), (int)encLen); + ExpectBufEQ(enc2, enc, encLen); + + OPENSSL_free(enc); + OPENSSL_free(enc2); + EVP_PKEY_free(pkey); + EVP_PKEY_free(peer); + } +#endif /* HAVE_CURVE448 */ +#endif /* OPENSSL_EXTRA && (HAVE_ECC || HAVE_CURVE25519 || HAVE_CURVE448) */ + return EXPECT_RESULT(); +} + diff --git a/tests/api/test_evp_pkey.h b/tests/api/test_evp_pkey.h index 97388dfd7a8..60647ac0490 100644 --- a/tests/api/test_evp_pkey.h +++ b/tests/api/test_evp_pkey.h @@ -68,6 +68,7 @@ int test_wolfSSL_CTX_use_PrivateKey_ed25519(void); int test_wolfSSL_EVP_PKEY_ed448(void); int test_wolfSSL_EVP_PKEY_x25519(void); int test_wolfSSL_EVP_PKEY_x448(void); +int test_wolfSSL_EVP_PKEY_encoded_public_key(void); #define TEST_EVP_PKEY_DECLS \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_CTX_new_id), \ @@ -114,6 +115,7 @@ int test_wolfSSL_EVP_PKEY_x448(void); TEST_DECL_GROUP("evp_pkey", test_wolfSSL_CTX_use_PrivateKey_ed25519), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_ed448), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_x25519), \ - TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_x448) + TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_x448), \ + TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_encoded_public_key) #endif /* WOLFCRYPT_TEST_EVP_PKEY_H */ diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index c7e1e4c8856..f4c7365cb1f 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -9854,6 +9854,254 @@ int wolfSSL_EVP_PKEY_assign_EC_KEY(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_EC_KEY* key) } #endif /* HAVE_ECC */ +#if defined(OPENSSL_EXTRA) && (defined(HAVE_ECC) || defined(HAVE_CURVE25519) || \ + defined(HAVE_CURVE448)) +/* Get the public key from a key as an encoded point / raw octet string. + * + * This is the implementation behind both EVP_PKEY_get1_encoded_public_key() + * and the deprecated (but functionally identical) EVP_PKEY_get1_tls_encodedpoint(). + * + * For EC keys the encoding is the uncompressed octet point (0x04 || X || Y). + * For X25519/X448 keys it is the raw little-endian public key (RFC 7748). + * + * The buffer returned through ppub is allocated here and must be released by + * the caller with OPENSSL_free(). + * + * @param [in] pkey Key to encode the public part of. + * @param [out] ppub Reference into which the allocated buffer is returned. + * @return Length of the encoding in bytes on success. + * @return 0 on error. + */ +size_t wolfSSL_EVP_PKEY_get1_encoded_public_key(WOLFSSL_EVP_PKEY* pkey, + unsigned char** ppub) +{ + size_t ret = 0; + + WOLFSSL_ENTER("wolfSSL_EVP_PKEY_get1_encoded_public_key"); + + if ((pkey == NULL) || (ppub == NULL)) { + WOLFSSL_MSG("Bad parameter"); + return 0; + } + + switch (pkey->type) { +#ifdef HAVE_ECC + case WC_EVP_PKEY_EC: { + int len; + WOLFSSL_EC_KEY* ec = wolfSSL_EVP_PKEY_get1_EC_KEY(pkey); + if (ec == NULL) { + WOLFSSL_MSG("No EC key in EVP_PKEY"); + break; + } + /* Passing a pointer to NULL has i2o allocate the buffer for us. */ + *ppub = NULL; + len = wolfSSL_i2o_ECPublicKey(ec, ppub); + wolfSSL_EC_KEY_free(ec); + if (len > 0) { + ret = (size_t)len; + } + break; + } +#endif /* HAVE_ECC */ +#ifdef HAVE_CURVE25519 + case WC_EVP_PKEY_X25519: { + word32 len = CURVE25519_PUB_KEY_SIZE; + byte* buf; + + if (pkey->curve25519 == NULL) { + WOLFSSL_MSG("No X25519 key in EVP_PKEY"); + break; + } + buf = (byte*)XMALLOC(len, NULL, DYNAMIC_TYPE_OPENSSL); + if (buf == NULL) { + WOLFSSL_MSG("malloc failed"); + break; + } + /* TLS wire format is little-endian (RFC 7748). */ + if (wc_curve25519_export_public_ex(pkey->curve25519, buf, &len, + EC25519_LITTLE_ENDIAN) != 0) { + WOLFSSL_MSG("wc_curve25519_export_public_ex error"); + XFREE(buf, NULL, DYNAMIC_TYPE_OPENSSL); + break; + } + *ppub = buf; + ret = (size_t)len; + break; + } +#endif /* HAVE_CURVE25519 */ +#ifdef HAVE_CURVE448 + case WC_EVP_PKEY_X448: { + word32 len = CURVE448_PUB_KEY_SIZE; + byte* buf; + + if (pkey->curve448 == NULL) { + WOLFSSL_MSG("No X448 key in EVP_PKEY"); + break; + } + buf = (byte*)XMALLOC(len, NULL, DYNAMIC_TYPE_OPENSSL); + if (buf == NULL) { + WOLFSSL_MSG("malloc failed"); + break; + } + /* TLS wire format is little-endian (RFC 7748). */ + if (wc_curve448_export_public_ex(pkey->curve448, buf, &len, + EC448_LITTLE_ENDIAN) != 0) { + WOLFSSL_MSG("wc_curve448_export_public_ex error"); + XFREE(buf, NULL, DYNAMIC_TYPE_OPENSSL); + break; + } + *ppub = buf; + ret = (size_t)len; + break; + } +#endif /* HAVE_CURVE448 */ + default: + WOLFSSL_MSG("Unsupported key type"); + break; + } + + return ret; +} + +/* Set the public key in a key from an encoded point / raw octet string. + * + * This is the implementation behind both EVP_PKEY_set1_encoded_public_key() + * and the deprecated (but functionally identical) EVP_PKEY_set1_tls_encodedpoint(). + * + * For EC keys the EVP_PKEY must already hold the curve parameters; pub is + * decoded as an octet point. For X25519/X448 keys pkey->type must already be + * set; pub is the raw little-endian public key (RFC 7748). + * + * @param [in, out] pkey Key to set the public part of. + * @param [in] pub Encoded public key. + * @param [in] publen Length of encoded public key in bytes. + * @return WOLFSSL_SUCCESS on success. + * @return WOLFSSL_FAILURE on error. + */ +int wolfSSL_EVP_PKEY_set1_encoded_public_key(WOLFSSL_EVP_PKEY* pkey, + const unsigned char* pub, size_t publen) +{ + int ret = WOLFSSL_FAILURE; + + WOLFSSL_ENTER("wolfSSL_EVP_PKEY_set1_encoded_public_key"); + + if ((pkey == NULL) || (pub == NULL) || (publen == 0) || + (publen > INT_MAX)) { + WOLFSSL_MSG("Bad parameter"); + return WOLFSSL_FAILURE; + } + + switch (pkey->type) { +#ifdef HAVE_ECC + case WC_EVP_PKEY_EC: { + const unsigned char* in = pub; + /* Need the EC key (with its group) to decode the point into. */ + WOLFSSL_EC_KEY* ec = wolfSSL_EVP_PKEY_get1_EC_KEY(pkey); + if (ec == NULL) { + WOLFSSL_MSG("No EC parameters in EVP_PKEY"); + break; + } + /* Decode the octet point into the external public key. */ + if (wolfSSL_o2i_ECPublicKey(&ec, &in, (long)publen) == NULL) { + WOLFSSL_MSG("wolfSSL_o2i_ECPublicKey error"); + wolfSSL_EC_KEY_free(ec); + break; + } + /* Push the new public point into the internal wolfCrypt key so + * that consumers such as EVP_PKEY_derive() can use it. */ + ec->inSet = 0; + if (SetECKeyInternal(ec) != WOLFSSL_SUCCESS) { + WOLFSSL_MSG("SetECKeyInternal error"); + wolfSSL_EC_KEY_free(ec); + break; + } + /* Refresh the cached DER representation in the EVP_PKEY. */ + ret = ECC_populate_EVP_PKEY(pkey, ec); + wolfSSL_EC_KEY_free(ec); + break; + } +#endif /* HAVE_ECC */ +#ifdef HAVE_CURVE25519 + case WC_EVP_PKEY_X25519: { + curve25519_key* key; + + /* Replace any existing key with a fresh one from the bytes. */ + if ((pkey->curve25519 != NULL) && (pkey->ownCurve25519 == 1)) { + wc_curve25519_free(pkey->curve25519); + XFREE(pkey->curve25519, pkey->heap, DYNAMIC_TYPE_CURVE25519); + } + pkey->curve25519 = NULL; + pkey->ownCurve25519 = 0; + + key = (curve25519_key*)XMALLOC(sizeof(curve25519_key), pkey->heap, + DYNAMIC_TYPE_CURVE25519); + if (key == NULL) { + WOLFSSL_MSG("malloc failed"); + break; + } + if (wc_curve25519_init_ex(key, pkey->heap, INVALID_DEVID) != 0) { + XFREE(key, pkey->heap, DYNAMIC_TYPE_CURVE25519); + break; + } + /* Raw X25519 keys are little-endian (RFC 7748). */ + if (wc_curve25519_import_public_ex(pub, (word32)publen, key, + EC25519_LITTLE_ENDIAN) != 0) { + WOLFSSL_MSG("wc_curve25519_import_public_ex error"); + wc_curve25519_free(key); + XFREE(key, pkey->heap, DYNAMIC_TYPE_CURVE25519); + break; + } + pkey->curve25519 = key; + pkey->ownCurve25519 = 1; + ret = WOLFSSL_SUCCESS; + break; + } +#endif /* HAVE_CURVE25519 */ +#ifdef HAVE_CURVE448 + case WC_EVP_PKEY_X448: { + curve448_key* key; + + /* Replace any existing key with a fresh one from the bytes. */ + if ((pkey->curve448 != NULL) && (pkey->ownCurve448 == 1)) { + wc_curve448_free(pkey->curve448); + XFREE(pkey->curve448, pkey->heap, DYNAMIC_TYPE_CURVE448); + } + pkey->curve448 = NULL; + pkey->ownCurve448 = 0; + + key = (curve448_key*)XMALLOC(sizeof(curve448_key), pkey->heap, + DYNAMIC_TYPE_CURVE448); + if (key == NULL) { + WOLFSSL_MSG("malloc failed"); + break; + } + if (wc_curve448_init(key) != 0) { + XFREE(key, pkey->heap, DYNAMIC_TYPE_CURVE448); + break; + } + /* Raw X448 keys are little-endian (RFC 7748). */ + if (wc_curve448_import_public_ex(pub, (word32)publen, key, + EC448_LITTLE_ENDIAN) != 0) { + WOLFSSL_MSG("wc_curve448_import_public_ex error"); + wc_curve448_free(key); + XFREE(key, pkey->heap, DYNAMIC_TYPE_CURVE448); + break; + } + pkey->curve448 = key; + pkey->ownCurve448 = 1; + ret = WOLFSSL_SUCCESS; + break; + } +#endif /* HAVE_CURVE448 */ + default: + WOLFSSL_MSG("Unsupported key type"); + break; + } + + return ret; +} +#endif /* OPENSSL_EXTRA && (HAVE_ECC || HAVE_CURVE25519 || HAVE_CURVE448) */ + #ifndef NO_WOLFSSL_STUB const WOLFSSL_EVP_MD* wolfSSL_EVP_ripemd160(void) { diff --git a/wolfssl/openssl/evp.h b/wolfssl/openssl/evp.h index bc873695bf0..74e8a8ba126 100644 --- a/wolfssl/openssl/evp.h +++ b/wolfssl/openssl/evp.h @@ -958,6 +958,10 @@ WOLFSSL_API int wolfSSL_EVP_PKEY_set1_RSA(WOLFSSL_EVP_PKEY *pkey, WOLFSSL_RSA *k WOLFSSL_API int wolfSSL_EVP_PKEY_set1_DSA(WOLFSSL_EVP_PKEY *pkey, WOLFSSL_DSA *key); WOLFSSL_API int wolfSSL_EVP_PKEY_set1_DH(WOLFSSL_EVP_PKEY *pkey, WOLFSSL_DH *key); WOLFSSL_API int wolfSSL_EVP_PKEY_set1_EC_KEY(WOLFSSL_EVP_PKEY *pkey, WOLFSSL_EC_KEY *key); +WOLFSSL_API int wolfSSL_EVP_PKEY_set1_encoded_public_key(WOLFSSL_EVP_PKEY *pkey, + const unsigned char *pub, size_t publen); +WOLFSSL_API size_t wolfSSL_EVP_PKEY_get1_encoded_public_key(WOLFSSL_EVP_PKEY *pkey, + unsigned char **ppub); WOLFSSL_API int wolfSSL_EVP_PKEY_assign(WOLFSSL_EVP_PKEY *pkey, int type, void *key); WOLFSSL_API const unsigned char* wolfSSL_EVP_PKEY_get0_hmac(const WOLFSSL_EVP_PKEY* pkey, @@ -1392,6 +1396,12 @@ WOLFSSL_API int wolfSSL_EVP_SignInit_ex(WOLFSSL_EVP_MD_CTX* ctx, #define EVP_PKEY_get0_DH wolfSSL_EVP_PKEY_get0_DH #define EVP_PKEY_get1_DH wolfSSL_EVP_PKEY_get1_DH #define EVP_PKEY_get0_EC_KEY wolfSSL_EVP_PKEY_get0_EC_KEY +/* New (OpenSSL 3.0+) names and the deprecated tls_encodedpoint names map to the + * same implementations. */ +#define EVP_PKEY_set1_encoded_public_key wolfSSL_EVP_PKEY_set1_encoded_public_key +#define EVP_PKEY_get1_encoded_public_key wolfSSL_EVP_PKEY_get1_encoded_public_key +#define EVP_PKEY_set1_tls_encodedpoint wolfSSL_EVP_PKEY_set1_encoded_public_key +#define EVP_PKEY_get1_tls_encodedpoint wolfSSL_EVP_PKEY_get1_encoded_public_key #define EVP_PKEY_get0_hmac wolfSSL_EVP_PKEY_get0_hmac #define EVP_PKEY_new_mac_key wolfSSL_EVP_PKEY_new_mac_key #define EVP_PKEY_new_CMAC_key wolfSSL_EVP_PKEY_new_CMAC_key From 9452c5d37e5e645f17b2b6502338e8815a7e2df6 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 3 Jun 2026 21:58:05 +0000 Subject: [PATCH 2/3] Make X25519/X448 set1_encoded_public_key failure-atomic Build the replacement curve25519/curve448 key in a temporary and only free the existing key once the import succeeds, so a failed set1 leaves the original EVP_PKEY intact (matching the EC branch). Add a regression test that a wrong-length set1 leaves the existing key usable. --- tests/api/test_evp_pkey.c | 11 +++++++++++ wolfcrypt/src/evp.c | 32 ++++++++++++++++---------------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/tests/api/test_evp_pkey.c b/tests/api/test_evp_pkey.c index 6653d84029b..8fab4be4e7a 100644 --- a/tests/api/test_evp_pkey.c +++ b/tests/api/test_evp_pkey.c @@ -3032,6 +3032,17 @@ int test_wolfSSL_EVP_PKEY_encoded_public_key(void) EVP_PKEY_get1_encoded_public_key(peer, &enc2)), (int)encLen); ExpectBufEQ(enc2, enc, encLen); + /* A failed set1 (wrong length) must leave the existing key intact. */ + ExpectIntEQ(EVP_PKEY_set1_encoded_public_key(peer, enc, encLen - 1), 0); + { + unsigned char* enc3 = NULL; + size_t encLen3 = 0; + ExpectIntEQ((int)(encLen3 = + EVP_PKEY_get1_encoded_public_key(peer, &enc3)), (int)encLen); + ExpectBufEQ(enc3, enc, encLen); + OPENSSL_free(enc3); + } + OPENSSL_free(enc); OPENSSL_free(enc2); OPENSSL_free(encTls); diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index f4c7365cb1f..541c53aa10a 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -10025,14 +10025,9 @@ int wolfSSL_EVP_PKEY_set1_encoded_public_key(WOLFSSL_EVP_PKEY* pkey, case WC_EVP_PKEY_X25519: { curve25519_key* key; - /* Replace any existing key with a fresh one from the bytes. */ - if ((pkey->curve25519 != NULL) && (pkey->ownCurve25519 == 1)) { - wc_curve25519_free(pkey->curve25519); - XFREE(pkey->curve25519, pkey->heap, DYNAMIC_TYPE_CURVE25519); - } - pkey->curve25519 = NULL; - pkey->ownCurve25519 = 0; - + /* Build the replacement in a temporary first; only commit (and + * free the old key) once the import has succeeded, so a failure + * leaves the original EVP_PKEY intact. */ key = (curve25519_key*)XMALLOC(sizeof(curve25519_key), pkey->heap, DYNAMIC_TYPE_CURVE25519); if (key == NULL) { @@ -10051,6 +10046,11 @@ int wolfSSL_EVP_PKEY_set1_encoded_public_key(WOLFSSL_EVP_PKEY* pkey, XFREE(key, pkey->heap, DYNAMIC_TYPE_CURVE25519); break; } + /* Import succeeded - replace any existing key. */ + if ((pkey->curve25519 != NULL) && (pkey->ownCurve25519 == 1)) { + wc_curve25519_free(pkey->curve25519); + XFREE(pkey->curve25519, pkey->heap, DYNAMIC_TYPE_CURVE25519); + } pkey->curve25519 = key; pkey->ownCurve25519 = 1; ret = WOLFSSL_SUCCESS; @@ -10061,14 +10061,9 @@ int wolfSSL_EVP_PKEY_set1_encoded_public_key(WOLFSSL_EVP_PKEY* pkey, case WC_EVP_PKEY_X448: { curve448_key* key; - /* Replace any existing key with a fresh one from the bytes. */ - if ((pkey->curve448 != NULL) && (pkey->ownCurve448 == 1)) { - wc_curve448_free(pkey->curve448); - XFREE(pkey->curve448, pkey->heap, DYNAMIC_TYPE_CURVE448); - } - pkey->curve448 = NULL; - pkey->ownCurve448 = 0; - + /* Build the replacement in a temporary first; only commit (and + * free the old key) once the import has succeeded, so a failure + * leaves the original EVP_PKEY intact. */ key = (curve448_key*)XMALLOC(sizeof(curve448_key), pkey->heap, DYNAMIC_TYPE_CURVE448); if (key == NULL) { @@ -10087,6 +10082,11 @@ int wolfSSL_EVP_PKEY_set1_encoded_public_key(WOLFSSL_EVP_PKEY* pkey, XFREE(key, pkey->heap, DYNAMIC_TYPE_CURVE448); break; } + /* Import succeeded - replace any existing key. */ + if ((pkey->curve448 != NULL) && (pkey->ownCurve448 == 1)) { + wc_curve448_free(pkey->curve448); + XFREE(pkey->curve448, pkey->heap, DYNAMIC_TYPE_CURVE448); + } pkey->curve448 = key; pkey->ownCurve448 = 1; ret = WOLFSSL_SUCCESS; From abeea3e23fd73eec895b102f73d839acca6f25ae Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 3 Jun 2026 21:58:05 +0000 Subject: [PATCH 3/3] Add X448 failure-atomicity regression test for set1_encoded_public_key Mirror the X25519 check: a wrong-length set1 must leave the existing X448 key intact and usable. --- tests/api/test_evp_pkey.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/api/test_evp_pkey.c b/tests/api/test_evp_pkey.c index 8fab4be4e7a..f24c0817db5 100644 --- a/tests/api/test_evp_pkey.c +++ b/tests/api/test_evp_pkey.c @@ -3079,6 +3079,17 @@ int test_wolfSSL_EVP_PKEY_encoded_public_key(void) EVP_PKEY_get1_encoded_public_key(peer, &enc2)), (int)encLen); ExpectBufEQ(enc2, enc, encLen); + /* A failed set1 (wrong length) must leave the existing key intact. */ + ExpectIntEQ(EVP_PKEY_set1_encoded_public_key(peer, enc, encLen - 1), 0); + { + unsigned char* enc3 = NULL; + size_t encLen3 = 0; + ExpectIntEQ((int)(encLen3 = + EVP_PKEY_get1_encoded_public_key(peer, &enc3)), (int)encLen); + ExpectBufEQ(enc3, enc, encLen); + OPENSSL_free(enc3); + } + OPENSSL_free(enc); OPENSSL_free(enc2); EVP_PKEY_free(pkey);