From d385ae9c296999d7c57fb4ce8a6d62672f847e4d Mon Sep 17 00:00:00 2001 From: Lealem Amedie Date: Mon, 6 Apr 2026 12:32:25 -0600 Subject: [PATCH] add Ed25519 and Ed448 support to the EVP_PKEY layer - Add WC_EVP_PKEY_ED25519 / WC_EVP_PKEY_ED448 type constants and matching EVP_PKEY_ED25519 / EVP_PKEY_ED448 OpenSSL aliases. - Extend WOLFSSL_EVP_PKEY with ed25519/ed448 fields and ownership bits, and free them in wolfSSL_EVP_PKEY_free(). - Add d2i probe functions that accept both SubjectPublicKeyInfo / PKCS#8 PrivateKeyInfo encodings and raw 32/57-byte key material, and hook them into the d2i_evp_pkey_try() chain. - Map the Ed25519/Ed448 signature OIDs in the relevant lookups and teach the PEM key-format dispatch and SSL_CTX_use_PrivateKey switch about the new types. --- src/pk.c | 20 ++++ src/ssl.c | 16 +++ src/ssl_load.c | 12 ++ wolfcrypt/src/evp.c | 26 ++++ wolfcrypt/src/evp_pk.c | 262 ++++++++++++++++++++++++++++++++++++++++- wolfssl/openssl/evp.h | 6 + wolfssl/ssl.h | 12 ++ 7 files changed, 353 insertions(+), 1 deletion(-) diff --git a/src/pk.c b/src/pk.c index 1aaf40d065..4544baaf10 100644 --- a/src/pk.c +++ b/src/pk.c @@ -6262,6 +6262,16 @@ WOLFSSL_EVP_PKEY* wolfSSL_PEM_read_bio_PrivateKey(WOLFSSL_BIO* bio, case DHk: type = WC_EVP_PKEY_DH; break; + #ifdef HAVE_ED25519 + case ED25519k: + type = WC_EVP_PKEY_ED25519; + break; + #endif + #ifdef HAVE_ED448 + case ED448k: + type = WC_EVP_PKEY_ED448; + break; + #endif default: type = WOLFSSL_FATAL_ERROR; break; @@ -6409,6 +6419,16 @@ WOLFSSL_EVP_PKEY* wolfSSL_PEM_read_PrivateKey(XFILE fp, WOLFSSL_EVP_PKEY **key, case DHk: type = WC_EVP_PKEY_DH; break; + #ifdef HAVE_ED25519 + case ED25519k: + type = WC_EVP_PKEY_ED25519; + break; + #endif + #ifdef HAVE_ED448 + case ED448k: + type = WC_EVP_PKEY_ED448; + break; + #endif default: type = WOLFSSL_FATAL_ERROR; break; diff --git a/src/ssl.c b/src/ssl.c index ede7863b2f..47b13a8513 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -17747,6 +17747,14 @@ word32 nid2oid(int nid, int grp) return CTC_SHA3_512wECDSA; #endif #endif /* HAVE_ECC */ + #ifdef HAVE_ED25519 + case WC_NID_ED25519: + return CTC_ED25519; + #endif /* HAVE_ED25519 */ + #ifdef HAVE_ED448 + case WC_NID_ED448: + return CTC_ED448; + #endif /* HAVE_ED448 */ } break; @@ -18131,6 +18139,14 @@ int oid2nid(word32 oid, int grp) return WC_NID_ecdsa_with_SHA3_512; #endif #endif /* HAVE_ECC */ + #ifdef HAVE_ED25519 + case CTC_ED25519: + return WC_NID_ED25519; + #endif /* HAVE_ED25519 */ + #ifdef HAVE_ED448 + case CTC_ED448: + return WC_NID_ED448; + #endif /* HAVE_ED448 */ } break; diff --git a/src/ssl_load.c b/src/ssl_load.c index 0a0fb9e467..d77ccb31d6 100644 --- a/src/ssl_load.c +++ b/src/ssl_load.c @@ -5256,6 +5256,18 @@ int wolfSSL_CTX_use_PrivateKey(WOLFSSL_CTX *ctx, WOLFSSL_EVP_PKEY *pkey) WOLFSSL_MSG("populating ECC key"); ret = ECC_populate_EVP_PKEY(pkey, pkey->ecc); break; + #endif + #ifdef HAVE_ED25519 + case WC_EVP_PKEY_ED25519: + /* DER is already stored in pkey->pkey.ptr by d2i_evp_pkey. */ + WOLFSSL_MSG("populating Ed25519 key"); + break; + #endif + #ifdef HAVE_ED448 + case WC_EVP_PKEY_ED448: + /* DER is already stored in pkey->pkey.ptr by d2i_evp_pkey. */ + WOLFSSL_MSG("populating Ed448 key"); + break; #endif default: ret = 0; diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index b802958a9e..f8f0a264b2 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -41,6 +41,12 @@ #include #include #include +#ifdef HAVE_ED25519 +#include +#endif +#ifdef HAVE_ED448 +#include +#endif static const struct s_ent { const enum wc_HashType macType; @@ -11679,6 +11685,26 @@ void wolfSSL_EVP_PKEY_free(WOLFSSL_EVP_PKEY* key) break; #endif /* ! NO_DH ... */ + #ifdef HAVE_ED25519 + case WC_EVP_PKEY_ED25519: + if (key->ed25519 != NULL && key->ownEd25519 == 1) { + wc_ed25519_free(key->ed25519); + XFREE(key->ed25519, key->heap, DYNAMIC_TYPE_ED25519); + key->ed25519 = NULL; + } + break; + #endif /* HAVE_ED25519 */ + + #ifdef HAVE_ED448 + case WC_EVP_PKEY_ED448: + if (key->ed448 != NULL && key->ownEd448 == 1) { + wc_ed448_free(key->ed448); + XFREE(key->ed448, key->heap, DYNAMIC_TYPE_ED448); + key->ed448 = NULL; + } + break; + #endif /* HAVE_ED448 */ + #ifdef HAVE_HKDF case WC_EVP_PKEY_HKDF: XFREE(key->hkdfSalt, NULL, DYNAMIC_TYPE_SALT); diff --git a/wolfcrypt/src/evp_pk.c b/wolfcrypt/src/evp_pk.c index 51fffc6be8..1a2a14729a 100644 --- a/wolfcrypt/src/evp_pk.c +++ b/wolfcrypt/src/evp_pk.c @@ -231,6 +231,157 @@ static int d2iTryEccKey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, } #endif /* HAVE_ECC && OPENSSL_EXTRA */ +#ifdef HAVE_ED25519 +/** + * Try to make an Ed25519 EVP PKEY from data. + * + * @param [in, out] out On in, an EVP PKEY or NULL. + * On out, an EVP PKEY or NULL. + * @param [in] mem Memory containing key data. + * @param [in] memSz Size of key data in bytes. + * @param [in] priv 1 means private key, 0 means public key. + * @return 1 on success. + * @return 0 otherwise. + */ +static int d2iTryEd25519Key(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, + long memSz, int priv) +{ + ed25519_key* edKey = NULL; + word32 keyIdx = 0; + int isEdKey; + int ret = 1; + + edKey = (ed25519_key*)XMALLOC(sizeof(ed25519_key), NULL, + DYNAMIC_TYPE_ED25519); + if (edKey == NULL) { + return 0; + } + if (wc_ed25519_init(edKey) != 0) { + XFREE(edKey, NULL, DYNAMIC_TYPE_ED25519); + return 0; + } + + /* Try decoding data as an Ed25519 private/public key. The input may be + * either a DER-encoded SubjectPublicKeyInfo / PKCS#8 PrivateKeyInfo + * (the normal d2i_PUBKEY input) or a raw key (as passed from + * CopyDecodedToX509 where dCert->publicKey is the raw bytes from the + * SPKI BIT STRING). Try the structured form first, then fall back to + * the raw import. */ + if (priv) { + isEdKey = (wc_Ed25519PrivateKeyDecode(mem, &keyIdx, edKey, + (word32)memSz) == 0); + if (!isEdKey && memSz == ED25519_KEY_SIZE) { + keyIdx = (word32)memSz; + isEdKey = (wc_ed25519_import_private_only(mem, (word32)memSz, + edKey) == 0); + } + } + else { + isEdKey = (wc_Ed25519PublicKeyDecode(mem, &keyIdx, edKey, + (word32)memSz) == 0); + if (!isEdKey && memSz == ED25519_PUB_KEY_SIZE) { + keyIdx = (word32)memSz; + isEdKey = (wc_ed25519_import_public(mem, (word32)memSz, edKey) + == 0); + } + } + + if (!isEdKey) { + wc_ed25519_free(edKey); + XFREE(edKey, NULL, DYNAMIC_TYPE_ED25519); + return WOLFSSL_FATAL_ERROR; + } + + /* Create an EVP PKEY object holding the DER bytes. */ + ret = d2i_make_pkey(out, mem, keyIdx, priv, WC_EVP_PKEY_ED25519); + if (ret == 1) { + (*out)->ownEd25519 = 1; + (*out)->ed25519 = edKey; + } + else { + wc_ed25519_free(edKey); + XFREE(edKey, NULL, DYNAMIC_TYPE_ED25519); + } + + return ret; +} +#endif /* HAVE_ED25519 */ + +#ifdef HAVE_ED448 +/** + * Try to make an Ed448 EVP PKEY from data. + * + * @param [in, out] out On in, an EVP PKEY or NULL. + * On out, an EVP PKEY or NULL. + * @param [in] mem Memory containing key data. + * @param [in] memSz Size of key data in bytes. + * @param [in] priv 1 means private key, 0 means public key. + * @return 1 on success. + * @return 0 otherwise. + */ +static int d2iTryEd448Key(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, + long memSz, int priv) +{ + ed448_key* edKey = NULL; + word32 keyIdx = 0; + int isEdKey; + int ret = 1; + + edKey = (ed448_key*)XMALLOC(sizeof(ed448_key), NULL, DYNAMIC_TYPE_ED448); + if (edKey == NULL) { + return 0; + } + if (wc_ed448_init(edKey) != 0) { + XFREE(edKey, NULL, DYNAMIC_TYPE_ED448); + return 0; + } + + /* Try decoding data as an Ed448 private/public key. The input may be + * either a DER-encoded SubjectPublicKeyInfo / PKCS#8 PrivateKeyInfo + * (the normal d2i_PUBKEY input) or a raw key (as passed from + * CopyDecodedToX509 where dCert->publicKey is the raw bytes from the + * SPKI BIT STRING). Try the structured form first, then fall back to + * the raw import. */ + if (priv) { + isEdKey = (wc_Ed448PrivateKeyDecode(mem, &keyIdx, edKey, + (word32)memSz) == 0); + if (!isEdKey && memSz == ED448_KEY_SIZE) { + keyIdx = (word32)memSz; + isEdKey = (wc_ed448_import_private_only(mem, (word32)memSz, + edKey) == 0); + } + } + else { + isEdKey = (wc_Ed448PublicKeyDecode(mem, &keyIdx, edKey, + (word32)memSz) == 0); + if (!isEdKey && memSz == ED448_PUB_KEY_SIZE) { + keyIdx = (word32)memSz; + isEdKey = (wc_ed448_import_public(mem, (word32)memSz, edKey) + == 0); + } + } + + if (!isEdKey) { + wc_ed448_free(edKey); + XFREE(edKey, NULL, DYNAMIC_TYPE_ED448); + return WOLFSSL_FATAL_ERROR; + } + + /* Create an EVP PKEY object holding the DER bytes. */ + ret = d2i_make_pkey(out, mem, keyIdx, priv, WC_EVP_PKEY_ED448); + if (ret == 1) { + (*out)->ownEd448 = 1; + (*out)->ed448 = edKey; + } + else { + wc_ed448_free(edKey); + XFREE(edKey, NULL, DYNAMIC_TYPE_ED448); + } + + return ret; +} +#endif /* HAVE_ED448 */ + #if !defined(NO_DSA) /** * Try to make a DSA EVP PKEY from data. @@ -686,6 +837,18 @@ static WOLFSSL_EVP_PKEY* d2i_evp_pkey_try(WOLFSSL_EVP_PKEY** out, #endif /* !HAVE_FIPS || HAVE_FIPS_VERSION > 2 */ #endif /* !NO_DH && OPENSSL_EXTRA && WOLFSSL_DH_EXTRA */ +#ifdef HAVE_ED25519 + if (d2iTryEd25519Key(&pkey, *in, inSz, priv) >= 0) { + ; + } + else +#endif /* HAVE_ED25519 */ +#ifdef HAVE_ED448 + if (d2iTryEd448Key(&pkey, *in, inSz, priv) >= 0) { + ; + } + else +#endif /* HAVE_ED448 */ #ifdef HAVE_FALCON if (d2iTryFalconKey(&pkey, *in, inSz, priv) >= 0) { ; @@ -917,7 +1080,14 @@ static WOLFSSL_EVP_PKEY* d2i_evp_pkey(int type, WOLFSSL_EVP_PKEY** out, ) || (type == WC_EVP_PKEY_EC && algId != ECDSAk) || (type == WC_EVP_PKEY_DSA && algId != DSAk) || - (type == WC_EVP_PKEY_DH && algId != DHk)) { + (type == WC_EVP_PKEY_DH && algId != DHk) + #ifdef HAVE_ED25519 + || (type == WC_EVP_PKEY_ED25519 && algId != ED25519k) + #endif + #ifdef HAVE_ED448 + || (type == WC_EVP_PKEY_ED448 && algId != ED448k) + #endif + ) { WOLFSSL_MSG("PKCS8 does not match EVP key type"); return NULL; } @@ -1022,6 +1192,96 @@ static WOLFSSL_EVP_PKEY* d2i_evp_pkey(int type, WOLFSSL_EVP_PKEY** out, #endif /* !HAVE_FIPS || HAVE_FIPS_VERSION > 2 */ #endif /* HAVE_DH */ #endif /* WOLFSSL_QT || OPENSSL_ALL || WOLFSSL_OPENSSH */ +#ifdef HAVE_ED25519 + case WC_EVP_PKEY_ED25519: + { + ed25519_key* edKey; + word32 keyIdx = 0; + int isEdKey; + + edKey = (ed25519_key*)XMALLOC(sizeof(ed25519_key), NULL, + DYNAMIC_TYPE_ED25519); + if (edKey == NULL) { + wolfSSL_EVP_PKEY_free(local); + return NULL; + } + if (wc_ed25519_init(edKey) != 0) { + XFREE(edKey, NULL, DYNAMIC_TYPE_ED25519); + wolfSSL_EVP_PKEY_free(local); + return NULL; + } + if (priv) { + isEdKey = (wc_Ed25519PrivateKeyDecode(p, &keyIdx, edKey, + (word32)local->pkey_sz) == 0); + if (!isEdKey && local->pkey_sz == ED25519_KEY_SIZE) { + isEdKey = (wc_ed25519_import_private_only(p, + (word32)local->pkey_sz, edKey) == 0); + } + } + else { + isEdKey = (wc_Ed25519PublicKeyDecode(p, &keyIdx, edKey, + (word32)local->pkey_sz) == 0); + if (!isEdKey && local->pkey_sz == ED25519_PUB_KEY_SIZE) { + isEdKey = (wc_ed25519_import_public(p, + (word32)local->pkey_sz, edKey) == 0); + } + } + if (!isEdKey) { + wc_ed25519_free(edKey); + XFREE(edKey, NULL, DYNAMIC_TYPE_ED25519); + wolfSSL_EVP_PKEY_free(local); + return NULL; + } + local->ownEd25519 = 1; + local->ed25519 = edKey; + break; + } +#endif /* HAVE_ED25519 */ +#ifdef HAVE_ED448 + case WC_EVP_PKEY_ED448: + { + ed448_key* edKey; + word32 keyIdx = 0; + int isEdKey; + + edKey = (ed448_key*)XMALLOC(sizeof(ed448_key), NULL, + DYNAMIC_TYPE_ED448); + if (edKey == NULL) { + wolfSSL_EVP_PKEY_free(local); + return NULL; + } + if (wc_ed448_init(edKey) != 0) { + XFREE(edKey, NULL, DYNAMIC_TYPE_ED448); + wolfSSL_EVP_PKEY_free(local); + return NULL; + } + if (priv) { + isEdKey = (wc_Ed448PrivateKeyDecode(p, &keyIdx, edKey, + (word32)local->pkey_sz) == 0); + if (!isEdKey && local->pkey_sz == ED448_KEY_SIZE) { + isEdKey = (wc_ed448_import_private_only(p, + (word32)local->pkey_sz, edKey) == 0); + } + } + else { + isEdKey = (wc_Ed448PublicKeyDecode(p, &keyIdx, edKey, + (word32)local->pkey_sz) == 0); + if (!isEdKey && local->pkey_sz == ED448_PUB_KEY_SIZE) { + isEdKey = (wc_ed448_import_public(p, + (word32)local->pkey_sz, edKey) == 0); + } + } + if (!isEdKey) { + wc_ed448_free(edKey); + XFREE(edKey, NULL, DYNAMIC_TYPE_ED448); + wolfSSL_EVP_PKEY_free(local); + return NULL; + } + local->ownEd448 = 1; + local->ed448 = edKey; + break; + } +#endif /* HAVE_ED448 */ default: WOLFSSL_MSG("Unsupported key type"); wolfSSL_EVP_PKEY_free(local); diff --git a/wolfssl/openssl/evp.h b/wolfssl/openssl/evp.h index f4ee44cd00..b8dfcb340c 100644 --- a/wolfssl/openssl/evp.h +++ b/wolfssl/openssl/evp.h @@ -450,6 +450,12 @@ enum { WC_EVP_PKEY_HKDF = WC_NID_hkdf, WC_EVP_PKEY_FALCON = 300, /* Randomly picked value. */ WC_EVP_PKEY_DILITHIUM = 301, /* Randomly picked value. */ +#ifdef HAVE_ED25519 + WC_EVP_PKEY_ED25519 = WC_NID_ED25519, +#endif +#ifdef HAVE_ED448 + WC_EVP_PKEY_ED448 = WC_NID_ED448, +#endif WC_AES_128_CFB1_TYPE = 24, WC_AES_192_CFB1_TYPE = 25, WC_AES_256_CFB1_TYPE = 26, diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index df5e272365..81160a57dc 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -602,6 +602,12 @@ struct WOLFSSL_EVP_PKEY { #ifndef NO_DH WOLFSSL_DH* dh; #endif + #ifdef HAVE_ED25519 + struct ed25519_key* ed25519; + #endif + #ifdef HAVE_ED448 + struct ed448_key* ed448; + #endif WC_RNG rng; #ifdef HAVE_HKDF const WOLFSSL_EVP_MD* hkdfMd; @@ -627,6 +633,12 @@ struct WOLFSSL_EVP_PKEY { WC_BITFIELD ownEcc:1; /* if struct owns ECC and should free it */ WC_BITFIELD ownDsa:1; /* if struct owns DSA and should free it */ WC_BITFIELD ownRsa:1; /* if struct owns RSA and should free it */ +#ifdef HAVE_ED25519 + WC_BITFIELD ownEd25519:1; /* if struct owns Ed25519 and should free it */ +#endif +#ifdef HAVE_ED448 + WC_BITFIELD ownEd448:1; /* if struct owns Ed448 and should free it */ +#endif };