Skip to content

Commit 219f0d2

Browse files
committed
Ed25519 support in the OpenSSL compatibility layer
wolfCrypt has full Ed25519 (keygen, sign/verify, ASN.1 import/export) and the TLS 1.3 stack already authenticates with Ed25519 certificates, but the OpenSSL-compatibility surface was missing the dispatch for several common operations, so an application driving Ed25519 purely through the OpenSSL API (EVP_PKEY_keygen, i2d_PUBKEY, X509_sign of an in-memory self-signed cert, X509_verify, then loading it into an SSL_CTX) could not use it. Fill those gaps, each mirroring the adjacent RSA/ECC/X25519 case: - EVP_PKEY_keygen (wolfcrypt/src/evp.c): generate an Ed25519 key and cache the PKCS#8 PrivateKeyInfo DER, like the EC/RSA cases; if the DER cannot be cached the freshly generated key is freed so a failed keygen never leaves a partial key behind. - wolfSSL_i2d_PublicKey / i2d_PUBKEY (wolfcrypt/src/evp_pk.c): encode an Ed25519 SubjectPublicKeyInfo. i2d_PublicKey intentionally emits the full SPKI here (rather than the bare key) because i2d_PUBKEY aliases it and the PEM_write_PUBKEY / d2i_PUBKEY round-trip relies on it. - pkcs8_encode (src/pk.c): return the already-PKCS#8 cached DER for Ed25519 (as the DH case does) after checking it is present, so i2d_PKCS8_PRIV_KEY_INFO works and a public-only key (e.g. one obtained from X509_get_pubkey, which caches only the raw public key) is rejected rather than emitted as a bogus private key. - d2iTryEd25519Key (wolfcrypt/src/evp_pk.c): derive the public key after decoding a PKCS#8 private key (v1 carries only the seed) so the decoded EVP_PKEY is complete. - d2i_AutoPrivateKey (wolfcrypt/src/evp_pk.c): detect Ed25519 by its algorithm id and decode the full PKCS#8 (its inner key is an OCTET STRING, which the RSA/ECC sequence-counting heuristic cannot classify). - X509_sign / X509_resign_cert / sigTypeFromPKEY / wolfssl_x509_make_der (src/x509.c): sign a certificate with an Ed25519 key (NULL digest), resolving the signature type as CTC_ED25519 and building the SubjectPublicKey from an ed25519_key. - X509_verify (src/x509.c): verify a certificate's signature with an Ed25519 EVP_PKEY, exporting the raw public key for the check (the cached DER is a PKCS#8 private blob, not the public key the verify path wants). - X509_set_pubkey / X509_get_pubkey (src/x509.c): set and retrieve an Ed25519 public key, keeping the X.509 public-key buffer as the raw key bytes to match how DecodeCert/StoreKey store it. Ed25519 object lifetime goes through the existing wolfSSL_ED25519_new / wolfSSL_ED25519_free helpers. Their guards (wolfssl/openssl/ed25519.h, src/pk.c) and the ssl.c include of openssl/ed25519.h are widened to also cover OPENSSL_EXTRA_X509_SMALL, since the X509 paths above build in that configuration too. The individual operations are gated on HAVE_ED25519_KEY_IMPORT / HAVE_ED25519_KEY_EXPORT / HAVE_ED25519_MAKE_KEY so a build that disables one of those sub-features still compiles. tests/api.c gains test_wolfSSL_EVP_PKEY_ED25519_openssl, which exercises the whole sequence: keygen, i2d_PUBKEY, the raw-key constructors (EVP_PKEY_new_raw_public_key / EVP_PKEY_new_raw_private_key), the PKCS#8 round-trip (EVP_PKEY2PKCS8, i2d_PKCS8_PKEY, d2i_AutoPrivateKey) including a check that the decoded key re-exports the same SPKI, a negative check that PKCS#8 export of a public-only key fails cleanly, a self-signed X509_sign with a NULL digest followed by X509_verify of the result, an X509_get_pubkey round-trip, and loading the key+cert into an SSL_CTX. Requires --enable-ed25519 --enable-certgen (key import/export and key generation are on by default with --enable-ed25519). Signed-off-by: Antonio Quartulli <antonio@mandelbit.com>
1 parent dd6da70 commit 219f0d2

7 files changed

Lines changed: 477 additions & 7 deletions

File tree

src/pk.c

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5518,8 +5518,8 @@ int wolfSSL_ED25519_verify(const unsigned char *msg, unsigned int msgSz,
55185518

55195519
#endif /* OPENSSL_EXTRA && HAVE_ED25519 */
55205520

5521-
#if (defined(OPENSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL)) && \
5522-
defined(HAVE_ED25519)
5521+
#if (defined(OPENSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL) || \
5522+
defined(OPENSSL_EXTRA_X509_SMALL)) && defined(HAVE_ED25519)
55235523
/* Allocate and initialize a new ed25519_key.
55245524
*
55255525
* @param [in] heap Heap hint for memory allocation.
@@ -5570,7 +5570,8 @@ void wolfSSL_ED25519_free(ed25519_key* key)
55705570
#endif
55715571
}
55725572
}
5573-
#endif /* (OPENSSL_EXTRA || WOLFSSL_WPAS_SMALL) && HAVE_ED25519 */
5573+
#endif /* (OPENSSL_EXTRA || WOLFSSL_WPAS_SMALL || OPENSSL_EXTRA_X509_SMALL) &&
5574+
* HAVE_ED25519 */
55745575

55755576
/*******************************************************************************
55765577
* END OF ED25519 API
@@ -7373,6 +7374,27 @@ int pkcs8_encode(WOLFSSL_EVP_PKEY* pkey, byte* key, word32* keySz)
73737374
curveOid = NULL;
73747375
oidSz = 0;
73757376
}
7377+
#endif
7378+
#if defined(HAVE_ED25519)
7379+
else if (pkey->type == WC_EVP_PKEY_ED25519) {
7380+
/* The cached DER is already a PKCS#8 PrivateKeyInfo (set when the
7381+
* key was generated or decoded), so return it as-is (same as the
7382+
* DH special case above). */
7383+
if (keySz == NULL)
7384+
return BAD_FUNC_ARG;
7385+
/* The cached DER must be present; e.g. an Ed25519 EVP_PKEY built
7386+
* from wolfSSL_X509_get_pubkey() holds only a raw public key, not a
7387+
* PKCS#8 private key, so reject rather than emit bogus output. */
7388+
if (pkey->pkey.ptr == NULL || pkey->pkey_sz <= 0)
7389+
return BAD_FUNC_ARG;
7390+
7391+
*keySz = (word32)pkey->pkey_sz;
7392+
if (key == NULL)
7393+
return LENGTH_ONLY_E;
7394+
7395+
XMEMCPY(key, pkey->pkey.ptr, (size_t)pkey->pkey_sz);
7396+
return pkey->pkey_sz;
7397+
}
73767398
#endif
73777399
else {
73787400
ret = NOT_COMPILED_IN;

src/ssl.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@
6363
|| defined(OPENSSL_EXTRA_X509_SMALL) \
6464
|| defined(HAVE_WEBSERVER) || defined(WOLFSSL_KEY_GEN))
6565
#include <wolfssl/openssl/evp.h>
66+
/* ed25519 compat helpers (wolfSSL_ED25519_new/free) are used by the X509
67+
* Ed25519 paths, which also build under OPENSSL_EXTRA_X509_SMALL. */
68+
#include <wolfssl/openssl/ed25519.h>
6669
/* openssl headers end, wolfssl internal headers next */
6770
#endif
6871

src/x509.c

Lines changed: 139 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6481,6 +6481,11 @@ WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509)
64816481
x509->pubKeyOID == ML_DSA_87k) {
64826482
key->type = WC_EVP_PKEY_DILITHIUM;
64836483
}
6484+
#endif
6485+
#if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_IMPORT)
6486+
else if (x509->pubKeyOID == ED25519k) {
6487+
key->type = WC_EVP_PKEY_ED25519;
6488+
}
64846489
#endif
64856490
else {
64866491
key->type = WC_EVP_PKEY_EC;
@@ -6572,6 +6577,29 @@ WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509)
65726577
}
65736578
}
65746579
#endif /* NO_DSA */
6580+
6581+
/* decode Ed25519 key */
6582+
#if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_IMPORT)
6583+
if (key->type == WC_EVP_PKEY_ED25519) {
6584+
key->ed25519 = wolfSSL_ED25519_new(x509->heap, INVALID_DEVID);
6585+
if (key->ed25519 == NULL) {
6586+
wolfSSL_EVP_PKEY_free(key);
6587+
return NULL;
6588+
}
6589+
key->ownEd25519 = 1;
6590+
6591+
/* The X.509 public key buffer holds the raw Ed25519 key
6592+
* (CopyDecodedToX509 / StoreKey store the BIT STRING
6593+
* contents), so import it directly. */
6594+
if (wc_ed25519_import_public(
6595+
(const unsigned char*)key->pkey.ptr,
6596+
(word32)key->pkey_sz, key->ed25519) != 0) {
6597+
WOLFSSL_MSG("wc_ed25519_import_public failed");
6598+
wolfSSL_EVP_PKEY_free(key);
6599+
return NULL;
6600+
}
6601+
}
6602+
#endif /* HAVE_ED25519 */
65756603
}
65766604
}
65776605
return key;
@@ -9010,6 +9038,12 @@ static int verifyX509orX509REQ(WOLFSSL_X509* x509, WOLFSSL_EVP_PKEY* pkey,
90109038
const byte* der;
90119039
int derSz = 0;
90129040
int type;
9041+
const byte* pubKey;
9042+
int pubKeySz;
9043+
#if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_EXPORT)
9044+
byte edPubKey[ED25519_PUB_KEY_SIZE];
9045+
word32 edPubKeySz = (word32)sizeof(edPubKey);
9046+
#endif
90139047

90149048
(void)req;
90159049

@@ -9023,6 +9057,10 @@ static int verifyX509orX509REQ(WOLFSSL_X509* x509, WOLFSSL_EVP_PKEY* pkey,
90239057
return WOLFSSL_FATAL_ERROR;
90249058
}
90259059

9060+
/* Most key types verify against the cached public-key DER. */
9061+
pubKey = (const byte*)pkey->pkey.ptr;
9062+
pubKeySz = pkey->pkey_sz;
9063+
90269064
switch (pkey->type) {
90279065
case WC_EVP_PKEY_RSA:
90289066
type = RSAk;
@@ -9036,6 +9074,23 @@ static int verifyX509orX509REQ(WOLFSSL_X509* x509, WOLFSSL_EVP_PKEY* pkey,
90369074
type = DSAk;
90379075
break;
90389076

9077+
#if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_EXPORT)
9078+
case WC_EVP_PKEY_ED25519:
9079+
/* The signature check needs the raw public key; for Ed25519
9080+
* pkey->pkey.ptr holds a PKCS#8 private blob (or nothing), so
9081+
* export the public key from the ed25519_key instead. */
9082+
if (pkey->ed25519 == NULL ||
9083+
wc_ed25519_export_public(pkey->ed25519, edPubKey,
9084+
&edPubKeySz) != 0) {
9085+
WOLFSSL_MSG("Unable to export Ed25519 public key");
9086+
return WOLFSSL_FATAL_ERROR;
9087+
}
9088+
pubKey = edPubKey;
9089+
pubKeySz = (int)edPubKeySz;
9090+
type = ED25519k;
9091+
break;
9092+
#endif
9093+
90399094
default:
90409095
WOLFSSL_MSG("Unknown pkey key type");
90419096
return WOLFSSL_FATAL_ERROR;
@@ -9044,11 +9099,11 @@ static int verifyX509orX509REQ(WOLFSSL_X509* x509, WOLFSSL_EVP_PKEY* pkey,
90449099
#ifdef WOLFSSL_CERT_REQ
90459100
if (req)
90469101
ret = CheckCSRSignaturePubKey(der, (word32)derSz, x509->heap,
9047-
(unsigned char*)pkey->pkey.ptr, pkey->pkey_sz, type);
9102+
(unsigned char*)pubKey, pubKeySz, type);
90489103
else
90499104
#endif
90509105
ret = CheckCertSignaturePubKey(der, (word32)derSz, x509->heap,
9051-
(unsigned char*)pkey->pkey.ptr, pkey->pkey_sz, type);
9106+
(unsigned char*)pubKey, pubKeySz, type);
90529107
if (ret == 0) {
90539108
return WOLFSSL_SUCCESS;
90549109
}
@@ -12225,6 +12280,14 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509)
1222512280
int hashType;
1222612281
int sigType = WOLFSSL_FAILURE;
1222712282

12283+
#if defined(HAVE_ED25519)
12284+
/* Ed25519 carries its own hash, so md is unused (and may be NULL).
12285+
* Resolve it before touching md. */
12286+
if (pkey->type == WC_EVP_PKEY_ED25519) {
12287+
return CTC_ED25519;
12288+
}
12289+
#endif
12290+
1222812291
/* Convert key type and hash algorithm to a signature algorithm */
1222912292
if (wolfSSL_EVP_get_hashinfo(md, &hashType, NULL)
1223012293
== WC_NO_ERR_TRACE(WOLFSSL_FAILURE))
@@ -12337,6 +12400,9 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509)
1233712400
#ifndef NO_DSA
1233812401
DsaKey* dsa = NULL;
1233912402
#endif
12403+
#if defined(HAVE_ED25519)
12404+
ed25519_key* ed25519 = NULL;
12405+
#endif
1234012406
#if defined(HAVE_FALCON)
1234112407
falcon_key* falcon = NULL;
1234212408
#endif
@@ -12472,6 +12538,28 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509)
1247212538
key = (void*)dsa;
1247312539
}
1247412540
#endif
12541+
#if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_IMPORT)
12542+
if (x509->pubKeyOID == ED25519k) {
12543+
ed25519 = wolfSSL_ED25519_new(NULL, INVALID_DEVID);
12544+
if (ed25519 == NULL) {
12545+
WOLFSSL_MSG("Failed to allocate memory for ed25519_key");
12546+
XFREE(cert, NULL, DYNAMIC_TYPE_CERT);
12547+
return WOLFSSL_FAILURE;
12548+
}
12549+
12550+
type = ED25519_TYPE;
12551+
/* The X.509 public key buffer holds the raw Ed25519 key. */
12552+
ret = wc_ed25519_import_public(x509->pubKey.buffer,
12553+
x509->pubKey.length, ed25519);
12554+
if (ret != 0) {
12555+
WOLFSSL_ERROR_VERBOSE(ret);
12556+
wolfSSL_ED25519_free(ed25519);
12557+
XFREE(cert, NULL, DYNAMIC_TYPE_CERT);
12558+
return ret;
12559+
}
12560+
key = (void*)ed25519;
12561+
}
12562+
#endif
1247512563
#if defined(HAVE_FALCON)
1247612564
if ((x509->pubKeyOID == FALCON_LEVEL1k) ||
1247712565
(x509->pubKeyOID == FALCON_LEVEL5k)) {
@@ -12723,6 +12811,11 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509)
1272312811
XFREE(ecc, NULL, DYNAMIC_TYPE_ECC);
1272412812
}
1272512813
#endif
12814+
#if defined(HAVE_ED25519)
12815+
if (x509->pubKeyOID == ED25519k) {
12816+
wolfSSL_ED25519_free(ed25519);
12817+
}
12818+
#endif
1272612819
#ifndef NO_DSA
1272712820
if (x509->pubKeyOID == DSAk) {
1272812821
wc_FreeDsaKey(dsa);
@@ -12807,6 +12900,12 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509)
1280712900
key = pkey->ecc->internal;
1280812901
}
1280912902
#endif
12903+
#if defined(HAVE_ED25519)
12904+
if (pkey->type == WC_EVP_PKEY_ED25519) {
12905+
type = ED25519_TYPE;
12906+
key = pkey->ed25519;
12907+
}
12908+
#endif
1281012909

1281112910
/* Sign the certificate (request) body. */
1281212911
ret = wc_InitRng(&rng);
@@ -12895,11 +12994,24 @@ int wolfSSL_X509_sign(WOLFSSL_X509* x509, WOLFSSL_EVP_PKEY* pkey,
1289512994

1289612995
WOLFSSL_ENTER("wolfSSL_X509_sign");
1289712996

12898-
if (x509 == NULL || pkey == NULL || md == NULL) {
12997+
if (x509 == NULL || pkey == NULL) {
1289912998
ret = WOLFSSL_FAILURE;
1290012999
goto out;
1290113000
}
1290213001

13002+
/* Most key types require an explicit digest. Ed25519 is the exception:
13003+
* it signs with a NULL digest (the key has a built-in hash). Kept as a
13004+
* dedicated block since other algorithms may grow similar edge cases. */
13005+
if (md == NULL) {
13006+
#if defined(HAVE_ED25519)
13007+
if (pkey->type != WC_EVP_PKEY_ED25519)
13008+
#endif
13009+
{
13010+
ret = WOLFSSL_FAILURE;
13011+
goto out;
13012+
}
13013+
}
13014+
1290313015
x509->sigOID = wolfSSL_sigTypeFromPKEY((WOLFSSL_EVP_MD*)md, pkey);
1290413016
if ((ret = wolfssl_x509_make_der(x509, 0, der, &derSz, 0)) !=
1290513017
WOLFSSL_SUCCESS) {
@@ -16408,6 +16520,30 @@ int wolfSSL_X509_set_pubkey(WOLFSSL_X509 *cert, WOLFSSL_EVP_PKEY *pkey)
1640816520
cert->pubKeyOID = ECDSAk;
1640916521
}
1641016522
break;
16523+
#endif
16524+
#if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_EXPORT)
16525+
case WC_EVP_PKEY_ED25519:
16526+
{
16527+
word32 rawLen = ED25519_PUB_KEY_SIZE;
16528+
16529+
if (pkey->ed25519 == NULL)
16530+
return WOLFSSL_FAILURE;
16531+
16532+
/* Store the RAW public key: wolfSSL keeps an X.509 Ed25519
16533+
* public key as the bare key bytes (see StoreKey /
16534+
* CopyDecodedToX509), not a SubjectPublicKeyInfo. */
16535+
p = (byte*)XMALLOC(rawLen, cert->heap, DYNAMIC_TYPE_PUBLIC_KEY);
16536+
if (p == NULL)
16537+
return WOLFSSL_FAILURE;
16538+
16539+
if (wc_ed25519_export_public(pkey->ed25519, p, &rawLen) != 0) {
16540+
XFREE(p, cert->heap, DYNAMIC_TYPE_PUBLIC_KEY);
16541+
return WOLFSSL_FAILURE;
16542+
}
16543+
derSz = (int)rawLen;
16544+
cert->pubKeyOID = ED25519k;
16545+
}
16546+
break;
1641116547
#endif
1641216548
default:
1641316549
return WOLFSSL_FAILURE;

0 commit comments

Comments
 (0)