diff --git a/src/pk.c b/src/pk.c index bfc039e5d0..1dc4e987eb 100644 --- a/src/pk.c +++ b/src/pk.c @@ -7373,6 +7373,22 @@ int pkcs8_encode(WOLFSSL_EVP_PKEY* pkey, byte* key, word32* keySz) curveOid = NULL; oidSz = 0; } +#endif +#if defined(HAVE_ED25519) + else if (pkey->type == WC_EVP_PKEY_ED25519) { + /* The cached DER is already a PKCS#8 PrivateKeyInfo (set when the + * key was generated or decoded), so return it as-is (same as the + * DH special case above). */ + if (keySz == NULL) + return BAD_FUNC_ARG; + + *keySz = (word32)pkey->pkey_sz; + if (key == NULL) + return LENGTH_ONLY_E; + + XMEMCPY(key, pkey->pkey.ptr, (size_t)pkey->pkey_sz); + return pkey->pkey_sz; + } #endif else { ret = NOT_COMPILED_IN; diff --git a/src/x509.c b/src/x509.c index fd87b1da4a..43bb7d01eb 100644 --- a/src/x509.c +++ b/src/x509.c @@ -6481,6 +6481,11 @@ WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509) x509->pubKeyOID == ML_DSA_87k) { key->type = WC_EVP_PKEY_DILITHIUM; } + #endif + #if defined(HAVE_ED25519) + else if (x509->pubKeyOID == ED25519k) { + key->type = WC_EVP_PKEY_ED25519; + } #endif else { key->type = WC_EVP_PKEY_EC; @@ -6572,6 +6577,37 @@ WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509) } } #endif /* NO_DSA */ + + /* decode Ed25519 key */ + #if defined(HAVE_ED25519) + if (key->type == WC_EVP_PKEY_ED25519) { + key->ed25519 = (ed25519_key*)XMALLOC(sizeof(ed25519_key), + x509->heap, DYNAMIC_TYPE_ED25519); + if (key->ed25519 == NULL) { + wolfSSL_EVP_PKEY_free(key); + return NULL; + } + if (wc_ed25519_init_ex(key->ed25519, x509->heap, + INVALID_DEVID) != 0) { + XFREE(key->ed25519, x509->heap, DYNAMIC_TYPE_ED25519); + key->ed25519 = NULL; + wolfSSL_EVP_PKEY_free(key); + return NULL; + } + key->ownEd25519 = 1; + + /* The X.509 public key buffer holds the raw Ed25519 key + * (CopyDecodedToX509 / StoreKey store the BIT STRING + * contents), so import it directly. */ + if (wc_ed25519_import_public( + (const unsigned char*)key->pkey.ptr, + (word32)key->pkey_sz, key->ed25519) != 0) { + WOLFSSL_MSG("wc_ed25519_import_public failed"); + wolfSSL_EVP_PKEY_free(key); + return NULL; + } + } + #endif /* HAVE_ED25519 */ } } return key; @@ -12225,6 +12261,14 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509) int hashType; int sigType = WOLFSSL_FAILURE; + #if defined(HAVE_ED25519) + /* Ed25519 carries its own hash, so md is unused (and may be NULL). + * Resolve it before touching md. */ + if (pkey->type == WC_EVP_PKEY_ED25519) { + return ED25519k; + } + #endif + /* Convert key type and hash algorithm to a signature algorithm */ if (wolfSSL_EVP_get_hashinfo(md, &hashType, NULL) == WC_NO_ERR_TRACE(WOLFSSL_FAILURE)) @@ -12337,6 +12381,9 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509) #ifndef NO_DSA DsaKey* dsa = NULL; #endif + #if defined(HAVE_ED25519) + ed25519_key* ed25519 = NULL; + #endif #if defined(HAVE_FALCON) falcon_key* falcon = NULL; #endif @@ -12472,6 +12519,36 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509) key = (void*)dsa; } #endif + #if defined(HAVE_ED25519) + if (x509->pubKeyOID == ED25519k) { + ed25519 = (ed25519_key*)XMALLOC(sizeof(ed25519_key), NULL, + DYNAMIC_TYPE_ED25519); + if (ed25519 == NULL) { + WOLFSSL_MSG("Failed to allocate memory for ed25519_key"); + XFREE(cert, NULL, DYNAMIC_TYPE_CERT); + return WOLFSSL_FAILURE; + } + + type = ED25519_TYPE; + ret = wc_ed25519_init(ed25519); + if (ret != 0) { + XFREE(ed25519, NULL, DYNAMIC_TYPE_ED25519); + XFREE(cert, NULL, DYNAMIC_TYPE_CERT); + return ret; + } + /* The X.509 public key buffer holds the raw Ed25519 key. */ + ret = wc_ed25519_import_public(x509->pubKey.buffer, + x509->pubKey.length, ed25519); + if (ret != 0) { + WOLFSSL_ERROR_VERBOSE(ret); + wc_ed25519_free(ed25519); + XFREE(ed25519, NULL, DYNAMIC_TYPE_ED25519); + XFREE(cert, NULL, DYNAMIC_TYPE_CERT); + return ret; + } + key = (void*)ed25519; + } + #endif #if defined(HAVE_FALCON) if ((x509->pubKeyOID == FALCON_LEVEL1k) || (x509->pubKeyOID == FALCON_LEVEL5k)) { @@ -12723,6 +12800,12 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509) XFREE(ecc, NULL, DYNAMIC_TYPE_ECC); } #endif + #if defined(HAVE_ED25519) + if (x509->pubKeyOID == ED25519k) { + wc_ed25519_free(ed25519); + XFREE(ed25519, NULL, DYNAMIC_TYPE_ED25519); + } + #endif #ifndef NO_DSA if (x509->pubKeyOID == DSAk) { wc_FreeDsaKey(dsa); @@ -12807,6 +12890,12 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509) key = pkey->ecc->internal; } #endif + #if defined(HAVE_ED25519) + if (pkey->type == WC_EVP_PKEY_ED25519) { + type = ED25519_TYPE; + key = pkey->ed25519; + } + #endif /* Sign the certificate (request) body. */ ret = wc_InitRng(&rng); @@ -12895,7 +12984,14 @@ int wolfSSL_X509_sign(WOLFSSL_X509* x509, WOLFSSL_EVP_PKEY* pkey, WOLFSSL_ENTER("wolfSSL_X509_sign"); - if (x509 == NULL || pkey == NULL || md == NULL) { + /* Ed25519 signs with a NULL digest (the key has a built-in hash); every + * other key type requires an explicit md. */ + if (x509 == NULL || pkey == NULL || + (md == NULL + #if defined(HAVE_ED25519) + && pkey->type != WC_EVP_PKEY_ED25519 + #endif + )) { ret = WOLFSSL_FAILURE; goto out; } @@ -16408,6 +16504,30 @@ int wolfSSL_X509_set_pubkey(WOLFSSL_X509 *cert, WOLFSSL_EVP_PKEY *pkey) cert->pubKeyOID = ECDSAk; } break; +#endif +#if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_EXPORT) + case WC_EVP_PKEY_ED25519: + { + word32 rawLen = ED25519_PUB_KEY_SIZE; + + if (pkey->ed25519 == NULL) + return WOLFSSL_FAILURE; + + /* Store the RAW public key: wolfSSL keeps an X.509 Ed25519 + * public key as the bare key bytes (see StoreKey / + * CopyDecodedToX509), not a SubjectPublicKeyInfo. */ + p = (byte*)XMALLOC(rawLen, cert->heap, DYNAMIC_TYPE_PUBLIC_KEY); + if (p == NULL) + return WOLFSSL_FAILURE; + + if (wc_ed25519_export_public(pkey->ed25519, p, &rawLen) != 0) { + XFREE(p, cert->heap, DYNAMIC_TYPE_PUBLIC_KEY); + return WOLFSSL_FAILURE; + } + derSz = (int)rawLen; + cert->pubKeyOID = ED25519k; + } + break; #endif default: return WOLFSSL_FAILURE; diff --git a/tests/api.c b/tests/api.c index d34a5afef8..84a274a5d0 100644 --- a/tests/api.c +++ b/tests/api.c @@ -10074,6 +10074,117 @@ static int test_wolfSSL_PKCS8(void) return EXPECT_RESULT(); } +/* Exercise Ed25519 through the OpenSSL-compatibility EVP/X509 surface the + * way an application does: generate a key, serialise the public + * (SubjectPublicKeyInfo) and private (PKCS#8) parts, build and sign an + * in-memory self-signed certificate with a NULL digest, read the public key + * back out, and load the key + cert into an SSL_CTX. */ +static int test_wolfSSL_EVP_PKEY_ED25519_openssl(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(HAVE_ED25519) && \ + defined(HAVE_ED25519_KEY_EXPORT) && defined(HAVE_ED25519_KEY_IMPORT) && \ + defined(WOLFSSL_CERT_GEN) && !defined(NO_CERTS) + WOLFSSL_EVP_PKEY_CTX* ctx = NULL; + WOLFSSL_EVP_PKEY* pkey = NULL; + WOLFSSL_EVP_PKEY* certPub = NULL; + WOLFSSL_X509* x509 = NULL; + WOLFSSL_X509_NAME* name = NULL; + WOLFSSL_ASN1_TIME* notBefore = NULL; + WOLFSSL_ASN1_TIME* notAfter = NULL; + unsigned char* spki = NULL; + unsigned char* spki2 = NULL; + int spkiSz = 0; + int spki2Sz = 0; + time_t t = 0; + + /* (1) Generate an Ed25519 key purely through the EVP API. */ + ExpectNotNull(ctx = wolfSSL_EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL)); + ExpectIntEQ(wolfSSL_EVP_PKEY_keygen_init(ctx), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_EVP_PKEY_keygen(ctx, &pkey), WOLFSSL_SUCCESS); + ExpectNotNull(pkey); + + /* (2) Encode the public key as SubjectPublicKeyInfo. */ + ExpectIntGT((spkiSz = wolfSSL_i2d_PUBKEY(pkey, &spki)), 0); + + /* (3) Encode the private key as PKCS#8 PrivateKeyInfo and (7) decode it + * back. These compat helpers (EVP_PKEY2PKCS8, i2d_PKCS8_PKEY, + * d2i_AutoPrivateKey) are only built with OPENSSL_ALL. */ +#if defined(OPENSSL_ALL) && !defined(NO_AES) + { + WOLFSSL_PKCS8_PRIV_KEY_INFO* p8 = NULL; + WOLFSSL_EVP_PKEY* decPriv = NULL; + unsigned char* p8der = NULL; + const unsigned char* tmp = NULL; + int p8Sz = 0; + + ExpectNotNull(p8 = wolfSSL_EVP_PKEY2PKCS8(pkey)); + ExpectIntGT((p8Sz = wolfSSL_i2d_PKCS8_PKEY(p8, &p8der)), 0); + tmp = p8der; + ExpectNotNull(decPriv = wolfSSL_d2i_AutoPrivateKey(NULL, &tmp, p8Sz)); + + XFREE(p8der, HEAP_HINT, DYNAMIC_TYPE_OPENSSL); + wolfSSL_EVP_PKEY_free(decPriv); + wolfSSL_EVP_PKEY_free((WOLFSSL_EVP_PKEY*)p8); + } +#endif + + /* (4)(5) Build an in-memory self-signed cert; Ed25519 signs with a NULL + * digest (it carries its own hash). */ + ExpectNotNull(x509 = wolfSSL_X509_new()); + ExpectIntEQ(wolfSSL_X509_set_version(x509, 2), WOLFSSL_SUCCESS); + ExpectNotNull(name = wolfSSL_X509_NAME_new()); + ExpectIntEQ(wolfSSL_X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, + (const byte*)"ed25519 test", -1, -1, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_subject_name(x509, name), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_issuer_name(x509, name), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_pubkey(x509, pkey), WOLFSSL_SUCCESS); + t = XTIME(NULL); + ExpectNotNull(notBefore = wolfSSL_ASN1_TIME_adj(NULL, t, 0, 0)); + ExpectNotNull(notAfter = wolfSSL_ASN1_TIME_adj(NULL, t, 365, 0)); + ExpectTrue(wolfSSL_X509_set_notBefore(x509, notBefore)); + ExpectTrue(wolfSSL_X509_set_notAfter(x509, notAfter)); + ExpectIntGT(wolfSSL_X509_sign(x509, pkey, NULL), 0); + + /* (8) The certificate's public key round-trips to the same SPKI. */ + ExpectNotNull(certPub = wolfSSL_X509_get_pubkey(x509)); + ExpectIntGT((spki2Sz = wolfSSL_i2d_PUBKEY(certPub, &spki2)), 0); + ExpectIntEQ(spki2Sz, spkiSz); + if ((spki != NULL) && (spki2 != NULL) && (spkiSz == spki2Sz)) { + ExpectIntEQ(XMEMCMP(spki, spki2, (size_t)spkiSz), 0); + } + + /* (6) Load the generated key and cert into an SSL_CTX. */ +#if !defined(NO_TLS) && \ + (!defined(NO_WOLFSSL_CLIENT) || !defined(NO_WOLFSSL_SERVER)) + { + WOLFSSL_CTX* sslCtx = NULL; +#ifndef NO_WOLFSSL_SERVER + ExpectNotNull(sslCtx = wolfSSL_CTX_new(wolfSSLv23_server_method())); +#else + ExpectNotNull(sslCtx = wolfSSL_CTX_new(wolfSSLv23_client_method())); +#endif + ExpectIntEQ(wolfSSL_CTX_use_certificate(sslCtx, x509), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey(sslCtx, pkey), + WOLFSSL_SUCCESS); + wolfSSL_CTX_free(sslCtx); + } +#endif + + XFREE(spki, HEAP_HINT, DYNAMIC_TYPE_OPENSSL); + XFREE(spki2, HEAP_HINT, DYNAMIC_TYPE_OPENSSL); + wolfSSL_ASN1_TIME_free(notBefore); + wolfSSL_ASN1_TIME_free(notAfter); + wolfSSL_X509_NAME_free(name); + wolfSSL_X509_free(x509); + wolfSSL_EVP_PKEY_free(certPub); + wolfSSL_EVP_PKEY_free(pkey); + wolfSSL_EVP_PKEY_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + static int test_wolfSSL_PKCS8_ED25519(void) { EXPECT_DECLS; @@ -35068,6 +35179,7 @@ TEST_CASE testCases[] = { /* PKCS8 testing */ TEST_DECL(test_wolfSSL_no_password_cb), TEST_DECL(test_wolfSSL_PKCS8), + TEST_DECL(test_wolfSSL_EVP_PKEY_ED25519_openssl), TEST_DECL(test_wolfSSL_PKCS8_ED25519), TEST_DECL(test_wolfSSL_PKCS8_ED448), diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index 5001288338..6278dc70ee 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -3820,6 +3820,9 @@ int wolfSSL_EVP_PKEY_keygen(WOLFSSL_EVP_PKEY_CTX *ctx, #endif #ifdef HAVE_CURVE448 ctx->pkey->type != WC_EVP_PKEY_X448 && + #endif + #if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_EXPORT) + ctx->pkey->type != WC_EVP_PKEY_ED25519 && #endif ctx->pkey->type != WC_EVP_PKEY_DH)) { WOLFSSL_MSG("Key not set or key type not supported"); @@ -3932,6 +3935,52 @@ int wolfSSL_EVP_PKEY_keygen(WOLFSSL_EVP_PKEY_CTX *ctx, ret = WOLFSSL_SUCCESS; } break; +#endif +#if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_EXPORT) + case WC_EVP_PKEY_ED25519: + if (pkey->ed25519 == NULL) { + pkey->ed25519 = (ed25519_key*)XMALLOC(sizeof(ed25519_key), + pkey->heap, DYNAMIC_TYPE_ED25519); + if (pkey->ed25519 == NULL) { + ret = MEMORY_E; + break; + } + if (wc_ed25519_init_ex(pkey->ed25519, pkey->heap, + INVALID_DEVID) != 0) { + XFREE(pkey->ed25519, pkey->heap, DYNAMIC_TYPE_ED25519); + pkey->ed25519 = NULL; + break; + } + pkey->ownEd25519 = 1; + } + /* Reuse the RNG already initialized on the EVP_PKEY. */ + if (wc_ed25519_make_key(&pkey->rng, ED25519_KEY_SIZE, + pkey->ed25519) == 0) { + /* Cache the PKCS#8 PrivateKeyInfo DER so the EVP/SSL paths + * (use_PrivateKey, EVP_PKEY2PKCS8) can load and serialize the + * key, mirroring the state the decode path produces. */ + int edDerSz = wc_Ed25519PrivateKeyToDer(pkey->ed25519, NULL, 0); + if (edDerSz > 0) { + byte* edDer = (byte*)XMALLOC((size_t)edDerSz, pkey->heap, + DYNAMIC_TYPE_OPENSSL); + if (edDer != NULL) { + if (wc_Ed25519PrivateKeyToDer(pkey->ed25519, edDer, + (word32)edDerSz) == edDerSz) { + if (pkey->pkey.ptr != NULL) { + XFREE(pkey->pkey.ptr, pkey->heap, + DYNAMIC_TYPE_OPENSSL); + } + pkey->pkey.ptr = (char*)edDer; + pkey->pkey_sz = edDerSz; + ret = WOLFSSL_SUCCESS; + } + else { + XFREE(edDer, pkey->heap, DYNAMIC_TYPE_OPENSSL); + } + } + } + } + break; #endif default: break; diff --git a/wolfcrypt/src/evp_pk.c b/wolfcrypt/src/evp_pk.c index 777f012e3c..04b1e52133 100644 --- a/wolfcrypt/src/evp_pk.c +++ b/wolfcrypt/src/evp_pk.c @@ -283,6 +283,17 @@ static int d2iTryEd25519Key(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, return WOLFSSL_FATAL_ERROR; } + /* A PKCS#8 v1 PrivateKeyInfo carries only the private seed, so the + * decoded key has no public part. Derive it (it is deterministic from + * the seed) so the resulting EVP_PKEY is complete and callers can later + * export/embed the public key. */ + if (priv && !edKey->pubKeySet) { + if (wc_ed25519_make_public(edKey, edKey->p, ED25519_PUB_KEY_SIZE) + == 0) { + edKey->pubKeySet = 1; + } + } + /* Create an EVP PKEY object holding the input DER bytes. If the caller * already populated the EVP PKEY with the input bytes (pkey.ptr set), * skip the allocate/copy. */ @@ -1601,6 +1612,16 @@ WOLFSSL_EVP_PKEY* wolfSSL_d2i_AutoPrivateKey(WOLFSSL_EVP_PKEY** pkey, /* Take off PKCS#8 wrapper if found. */ if ((len = ToTraditionalInline_ex(der, &idx, keyLen, &algId)) >= 0) { + #if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_IMPORT) + if (algId == ED25519k) { + /* Ed25519's inner key is an OCTET STRING, not a SEQUENCE, so the + * RSA/ECC element-count heuristic below cannot classify it. + * Decode the full PKCS#8 PrivateKeyInfo directly (keeps the + * cached DER complete so the key can be re-loaded later). */ + return wolfSSL_d2i_PrivateKey(WC_EVP_PKEY_ED25519, pkey, pp, + length); + } + #endif der += idx; keyLen = (word32)len; } @@ -2387,6 +2408,54 @@ static int wolfssl_i_i2d_ecpublickey(const WOLFSSL_EVP_PKEY* key, * @return WOLFSSL_FATAL_ERROR when key type not supported. * @return WOLFSSL_FATAL_ERROR when dynamic memory allocation fails. */ +#if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_EXPORT) +/* Encode an Ed25519 public key as DER SubjectPublicKeyInfo. Follows the + * i2d output convention: der == NULL returns the size only; *der == NULL + * allocates the buffer (caller frees); otherwise writes into *der and + * advances it. Returns the DER size or WOLFSSL_FATAL_ERROR. */ +static int wolfssl_i_i2d_ed25519_pubkey(const ed25519_key* key, + unsigned char **der) +{ + int derSz; + unsigned char* buf; + + if (key == NULL) { + return WOLFSSL_FATAL_ERROR; + } + + /* withAlg = 1 -> wrap the raw key in a SubjectPublicKeyInfo. */ + derSz = wc_Ed25519PublicKeyToDer((ed25519_key*)key, NULL, 0, 1); + if (derSz <= 0) { + return WOLFSSL_FATAL_ERROR; + } + if (der == NULL) { + return derSz; + } + + buf = (unsigned char*)XMALLOC((size_t)derSz, NULL, DYNAMIC_TYPE_PUBLIC_KEY); + if (buf == NULL) { + return WOLFSSL_FATAL_ERROR; + } + if (wc_Ed25519PublicKeyToDer((ed25519_key*)key, buf, (word32)derSz, 1) + != derSz) { + XFREE(buf, NULL, DYNAMIC_TYPE_PUBLIC_KEY); + return WOLFSSL_FATAL_ERROR; + } + + if (*der == NULL) { + /* Hand the buffer to the caller. */ + *der = buf; + } + else { + XMEMCPY(*der, buf, (size_t)derSz); + *der += derSz; + XFREE(buf, NULL, DYNAMIC_TYPE_PUBLIC_KEY); + } + + return derSz; +} +#endif /* HAVE_ED25519 && HAVE_ED25519_KEY_EXPORT */ + int wolfSSL_i2d_PublicKey(const WOLFSSL_EVP_PKEY *key, unsigned char **der) { int ret; @@ -2405,6 +2474,10 @@ int wolfSSL_i2d_PublicKey(const WOLFSSL_EVP_PKEY *key, unsigned char **der) #ifdef HAVE_ECC case WC_EVP_PKEY_EC: return wolfssl_i_i2d_ecpublickey(key, key->ecc, der); + #endif + #if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_EXPORT) + case WC_EVP_PKEY_ED25519: + return wolfssl_i_i2d_ed25519_pubkey(key->ed25519, der); #endif default: ret = WOLFSSL_FATAL_ERROR;