From 2f438199cc7d049d8c61409af4fc4bdb0d546e40 Mon Sep 17 00:00:00 2001 From: Emma Stensland Date: Wed, 22 Jul 2026 16:34:28 -0600 Subject: [PATCH] added check for private and public keys decoded --- .github/workflows/scp-test.yml | 4 +- .github/workflows/sshd-test.yml | 13 +- src/internal.c | 110 +++++- tests/auth.c | 249 ++++++++++++- tests/auth.h | 2 + tests/unit.c | 603 ++++++++++++++++++++++++++++++++ wolfssh/internal.h | 17 +- 7 files changed, 965 insertions(+), 33 deletions(-) diff --git a/.github/workflows/scp-test.yml b/.github/workflows/scp-test.yml index 89a57745a..f421afe49 100644 --- a/.github/workflows/scp-test.yml +++ b/.github/workflows/scp-test.yml @@ -42,7 +42,7 @@ jobs: id: cache-wolfssl with: path: build-dir/ - key: wolfssh-sshd-wolfssl-${{ matrix.wolfssl }}-${{ matrix.os }} + key: wolfssh-sshd-wolfssl-${{ matrix.wolfssl }}-${{ matrix.os }}-v3 lookup-only: true - name: Checkout, build, and install wolfssl @@ -73,7 +73,7 @@ jobs: uses: actions/cache@v5 with: path: build-dir/ - key: wolfssh-sshd-wolfssl-${{ matrix.wolfssl }}-${{ matrix.os }} + key: wolfssh-sshd-wolfssl-${{ matrix.wolfssl }}-${{ matrix.os }}-v3 fail-on-cache-miss: true - uses: actions/checkout@v6 diff --git a/.github/workflows/sshd-test.yml b/.github/workflows/sshd-test.yml index a59f6d280..aa39a5cab 100644 --- a/.github/workflows/sshd-test.yml +++ b/.github/workflows/sshd-test.yml @@ -33,6 +33,12 @@ jobs: matrix: os: [ ubuntu-latest ] wolfssl: ${{ fromJson(needs.create_matrix.outputs['versions']) }} + mldsa: [ 'yes', 'no' ] + include: + - mldsa: 'yes' + mldsa_flag: '--enable-mldsa' + - mldsa: 'no' + mldsa_flag: '' name: Build wolfssl runs-on: ${{ matrix.os }} timeout-minutes: 4 @@ -42,7 +48,7 @@ jobs: id: cache-wolfssl with: path: build-dir/ - key: wolfssh-sshd-wolfssl-${{ matrix.wolfssl }}-${{ matrix.os }} + key: wolfssh-sshd-wolfssl-${{ matrix.wolfssl }}-${{ matrix.os }}-mldsa-${{ matrix.mldsa }}-v3 lookup-only: true - name: Checkout, build, and install wolfssl @@ -52,7 +58,7 @@ jobs: repository: wolfssl/wolfssl ref: ${{ matrix.wolfssl }} path: wolfssl - configure: --enable-all + configure: --enable-all ${{ matrix.mldsa_flag }} check: false install: true @@ -65,6 +71,7 @@ jobs: matrix: os: [ ubuntu-latest ] wolfssl: ${{ fromJson(needs.create_matrix.outputs['versions']) }} + mldsa: [ 'yes', 'no' ] name: Build and test wolfsshd runs-on: ${{ matrix.os }} timeout-minutes: 10 @@ -73,7 +80,7 @@ jobs: uses: actions/cache@v5 with: path: build-dir/ - key: wolfssh-sshd-wolfssl-${{ matrix.wolfssl }}-${{ matrix.os }} + key: wolfssh-sshd-wolfssl-${{ matrix.wolfssl }}-${{ matrix.os }}-mldsa-${{ matrix.mldsa }}-v3 fail-on-cache-miss: true - uses: actions/checkout@v6 diff --git a/src/internal.c b/src/internal.c index a5fa0ee3b..d4b308ae0 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1690,12 +1690,18 @@ void wolfSSH_KEY_clean(WS_KeySignature* key) * fails try to load it as if ECDSA. Both public and private keys can be * decoded. For RSA keys, the key format is described as "ssh-rsa". * + * Private-only ML-DSA keys are rejected (WS_CRYPTO_FAILED) as public keys + * cannot be derived. ECDSA derives and validates the public key here. + * Ed25519 allows missing public keys if HAVE_ED25519_MAKE_KEY is defined + * (derived later at KEX); otherwise rejected like ML-DSA. + * * @param in key to identify * @param inSz size of key * @param isPrivate indicates private or public key * @param heap heap to use for memory allocation * @param pkey optionally return populated WS_KeySignature - * @return keyId as int, WS_MEMORY_E, WS_UNIMPLEMENTED_E + * @return keyId as int, WS_MEMORY_E, WS_UNIMPLEMENTED_E, + * WS_CRYPTO_FAILED */ int IdentifyAsn1Key(const byte* in, word32 inSz, int isPrivate, void* heap, WS_KeySignature **pkey) @@ -1704,6 +1710,9 @@ int IdentifyAsn1Key(const byte* in, word32 inSz, int isPrivate, void* heap, word32 idx; int ret; int dynType = isPrivate ? DYNTYPE_PRIVKEY : DYNTYPE_PUBKEY; + /* Set to WS_CRYPTO_FAILED if ML-DSA key lacks derivable public key. + * Prevents Ed25519 fallback decode. */ + int noPubKeyRet = 0; #ifndef WOLFSSH_NO_MLDSA byte mlDsaLevel = 0; int mlDsaInit = 0; @@ -1765,6 +1774,8 @@ int IdentifyAsn1Key(const byte* in, word32 inSz, int isPrivate, void* heap, /* If decode was successful, this is an ECDSA key. */ if (ret == 0) { + int curveSupported = 1; + switch (wc_ecc_get_curve_id(key->ks.ecc.key.idx)) { case ECC_SECP256R1: key->keyId = ID_ECDSA_SHA2_NISTP256; @@ -1776,8 +1787,29 @@ int IdentifyAsn1Key(const byte* in, word32 inSz, int isPrivate, void* heap, key->keyId = ID_ECDSA_SHA2_NISTP521; break; default: - /* Not a supported curve, so free the key */ - wc_ecc_free(&key->ks.ecc.key); + curveSupported = 0; + break; + } + + /* SEC1 allows omitted public point; derive it now so + * an undecodable key is rejected at load time, not + * KEX. */ + if (curveSupported && isPrivate && + key->ks.ecc.key.type == ECC_PRIVATEKEY_ONLY) { + if (wc_ecc_make_pub(&key->ks.ecc.key, NULL) != 0) { + WLOG(WS_LOG_ERROR, + "ECDSA priv-only key rejected; no " + "derivable pubkey"); + curveSupported = 0; + ret = WS_CRYPTO_FAILED; + noPubKeyRet = ret; + key->keyId = ID_UNKNOWN; + } + } + + if (!curveSupported) { + /* Not a supported curve, so free the key */ + wc_ecc_free(&key->ks.ecc.key); } } else { wc_ecc_free(&key->ks.ecc.key); @@ -1786,7 +1818,7 @@ int IdentifyAsn1Key(const byte* in, word32 inSz, int isPrivate, void* heap, } #endif /* WOLFSSH_NO_ECDSA */ #ifndef WOLFSSH_NO_MLDSA - if (key->keyId == ID_UNKNOWN) { + if (key->keyId == ID_UNKNOWN && noPubKeyRet == 0) { idx = 0; mlDsaLevel = 0; mlDsaInit = 0; @@ -1796,6 +1828,17 @@ int IdentifyAsn1Key(const byte* in, word32 inSz, int isPrivate, void* heap, if (isPrivate) { ret = wc_MlDsaKey_PrivateKeyDecode(&key->ks.mldsa.key, in, inSz, &idx); + if (ret == 0) { + /* Priv-only decode can succeed with no derivable + * public key; reject here instead of at first + * handshake. */ + if (!key->ks.mldsa.key.pubKeySet) { + WLOG(WS_LOG_ERROR, + "ML-DSA priv-only key rejected; no derivable pubkey"); + ret = WS_CRYPTO_FAILED; + noPubKeyRet = ret; + } + } } else { /* PublicKeyDecode auto-detects level from SPKI OID. */ @@ -1857,7 +1900,9 @@ int IdentifyAsn1Key(const byte* in, word32 inSz, int isPrivate, void* heap, } #endif /* WOLFSSH_NO_MLDSA */ #if !defined(WOLFSSH_NO_ED25519) - if (key->keyId == ID_UNKNOWN) { + /* noPubKeyRet == 0 check: don't reinterpret rejected ML-DSA bytes + * as Ed25519. */ + if (key->keyId == ID_UNKNOWN && noPubKeyRet == 0) { idx = 0; ret = wc_ed25519_init_ex(&key->ks.ed25519.key, heap, INVALID_DEVID); @@ -1865,6 +1910,23 @@ int IdentifyAsn1Key(const byte* in, word32 inSz, int isPrivate, void* heap, if (isPrivate) { ret = wc_Ed25519PrivateKeyDecode(in, &idx, &key->ks.ed25519.key, inSz); + if (ret == 0) { + /* No embedded pubkey. SendKexGetSigningKey derives + * it via wc_ed25519_make_public() if available; + * otherwise reject now instead of failing at KEX. */ + if (!key->ks.ed25519.key.pubKeySet) { +#ifdef HAVE_ED25519_MAKE_KEY + WLOG(WS_LOG_WARN, + "Ed25519 priv-only key; pubkey derives at KEX"); +#else + WLOG(WS_LOG_ERROR, + "Ed25519 priv-only key rejected; no " + "derivable pubkey"); + ret = WS_CRYPTO_FAILED; + noPubKeyRet = ret; +#endif /* HAVE_ED25519_MAKE_KEY */ + } + } } else { ret = wc_Ed25519PublicKeyDecode(in, &idx, @@ -1882,7 +1944,8 @@ int IdentifyAsn1Key(const byte* in, word32 inSz, int isPrivate, void* heap, #endif /* WOLFSSH_NO_ED25519 */ if (key->keyId == ID_UNKNOWN) { - ret = WS_UNIMPLEMENTED_E; + /* Prefer specific rejection reason over generic fallback. */ + ret = (noPubKeyRet != 0) ? noPubKeyRet : WS_UNIMPLEMENTED_E; } else { if (pkey != NULL) @@ -13125,6 +13188,22 @@ static int SendKexGetSigningKey(WOLFSSH* ssh, ret = wc_ecc_export_x963(&sigKeyBlock_ptr->sk.ecc.key, sigKeyBlock_ptr->sk.ecc.q, &sigKeyBlock_ptr->sk.ecc.qSz); + if (ret != 0 && sigKeyBlock_ptr->sk.ecc.key.type == + ECC_PRIVATEKEY_ONLY) { + /* Priv-only SEC1 DER: ECDSA public key is derivable + * from private scalar. wc_ecc_make_pub() is + * unconditional and caches result. Retry export. */ + sigKeyBlock_ptr->sk.ecc.qSz = + (word32)sizeof(sigKeyBlock_ptr->sk.ecc.q); + ret = wc_ecc_make_pub(&sigKeyBlock_ptr->sk.ecc.key, + NULL); + if (ret == 0) { + ret = wc_ecc_export_x963( + &sigKeyBlock_ptr->sk.ecc.key, + sigKeyBlock_ptr->sk.ecc.q, + &sigKeyBlock_ptr->sk.ecc.qSz); + } + } PRIVATE_KEY_LOCK(); } /* Hash in the length of the public key block. */ @@ -13182,10 +13261,27 @@ static int SendKexGetSigningKey(WOLFSSH* ssh, if (ret == 0) ret = wc_Ed25519PrivateKeyDecode(ssh->ctx->privateKey[keyIdx].key, &scratch, &sigKeyBlock_ptr->sk.ed.key, ssh->ctx->privateKey[keyIdx].keySz); - if (ret == 0) + if (ret == 0) { ret = wc_ed25519_export_public(&sigKeyBlock_ptr->sk.ed.key, sigKeyBlock_ptr->sk.ed.q, &sigKeyBlock_ptr->sk.ed.qSz ); +#ifdef HAVE_ED25519_MAKE_KEY + if (ret != 0 && !sigKeyBlock_ptr->sk.ed.key.pubKeySet) { + /* Priv-only DER: Ed25519 public key is deterministically + * derivable from private seed. */ + sigKeyBlock_ptr->sk.ed.qSz = ED25519_PUB_KEY_SIZE; + ret = wc_ed25519_make_public(&sigKeyBlock_ptr->sk.ed.key, + sigKeyBlock_ptr->sk.ed.q, ED25519_PUB_KEY_SIZE); + if (ret == 0) { + /* trusted=1: q derived from same private scalar; + * untrusted path re-verification redundant. */ + ret = wc_ed25519_import_public_ex( + sigKeyBlock_ptr->sk.ed.q, ED25519_PUB_KEY_SIZE, + &sigKeyBlock_ptr->sk.ed.key, 1); + } + } +#endif /* HAVE_ED25519_MAKE_KEY */ + } /* Hash in the length of the public key block. */ if (ret == 0) { diff --git a/tests/auth.c b/tests/auth.c index 83b1dd4c6..2129dbbe3 100644 --- a/tests/auth.c +++ b/tests/auth.c @@ -36,6 +36,12 @@ #if !defined(NO_SHA256) #include #endif +#if (!defined(WOLFSSH_NO_ED25519) && defined(HAVE_ED25519) && \ + defined(HAVE_ED25519_MAKE_KEY) && defined(HAVE_ED25519_KEY_EXPORT)) || \ + (!defined(WOLFSSH_NO_ECDSA) && !defined(WOLFSSH_NO_RSA) && \ + defined(HAVE_ECC_KEY_EXPORT)) + #include +#endif #ifdef NO_FILESYSTEM #include #endif @@ -526,17 +532,25 @@ static THREAD_RETURN WOLFSSH_THREAD pubkey_server_thread(void* args) wolfSSH_SetUserAuthCtx(ssh, serverArgs->pubkeyServerCtx); - /* Load the server's host key. If ECDSA is available, let ECC_PATH pick - * the enabled curve-specific key file; otherwise fall back to RSA. */ + /* Load host key: caller-supplied key takes priority, else ECDSA, then RSA. */ + if (serverArgs->hostKeyBuf != NULL) { + if (wolfSSH_CTX_UsePrivateKey_buffer(ctx, serverArgs->hostKeyBuf, + serverArgs->hostKeyBufSz, WOLFSSH_FORMAT_ASN1) < 0) { + serverArgs->return_code = WS_BAD_FILE_E; + goto cleanup; + } + } + else { #ifndef WOLFSSH_NO_ECDSA - bufSz = (word32)load_key(1, buf, sizeof(buf)); + bufSz = (word32)load_key(1, buf, sizeof(buf)); #else - bufSz = (word32)load_key(0, buf, sizeof(buf)); + bufSz = (word32)load_key(0, buf, sizeof(buf)); #endif - if (bufSz == 0 || wolfSSH_CTX_UsePrivateKey_buffer(ctx, buf, bufSz, - WOLFSSH_FORMAT_ASN1) < 0) { - serverArgs->return_code = WS_BAD_FILE_E; - goto cleanup; + if (bufSz == 0 || wolfSSH_CTX_UsePrivateKey_buffer(ctx, buf, bufSz, + WOLFSSH_FORMAT_ASN1) < 0) { + serverArgs->return_code = WS_BAD_FILE_E; + goto cleanup; + } } clientFd = accept(listenFd, (struct sockaddr*)&clientAddr, &clientAddrSz); @@ -574,13 +588,17 @@ static int AcceptAnyServerHostKey(const byte* pubKey, word32 pubKeySz, } /* Run one pubkey auth attempt. - * sCtx - server context (authorised key hash) - * cCtx - client context (key material to present) - * expect - expected return value from both wolfSSH_connect() and - * wolfSSH_accept(): WS_SUCCESS for a valid-key test, - * WS_FATAL_ERROR for a reject test */ -static int run_pubkey_test(PubkeyServerCtx* sCtx, PubkeyClientCtx* cCtx, - int expect) + * sCtx - server context (authorised key hash) + * cCtx - client context (key material to present) + * expect - expected return value from both wolfSSH_connect() and + * wolfSSH_accept(): WS_SUCCESS for a valid-key test, + * WS_FATAL_ERROR for a reject test + * hostKeyBuf - server host key DER; NULL uses the default fixture key + * via load_key() (what every existing caller wants) + * hostKeyBufSz - size of hostKeyBuf; ignored when hostKeyBuf is NULL */ +static int run_pubkey_test_ex(PubkeyServerCtx* sCtx, PubkeyClientCtx* cCtx, + int expect, const byte* hostKeyBuf, + word32 hostKeyBufSz) { thread_args serverArgs; tcp_ready ready; @@ -593,11 +611,14 @@ static int run_pubkey_test(PubkeyServerCtx* sCtx, PubkeyClientCtx* cCtx, int ret; int clientErr = WS_SUCCESS; + WMEMSET(&serverArgs, 0, sizeof(serverArgs)); serverArgs.signal = &ready; serverArgs.pubkeyServerCtx = sCtx; serverArgs.userAuth = serverPubkeyUserAuth; serverArgs.caCert = sCtx->caCert; serverArgs.caCertSz = sCtx->caCertSz; + serverArgs.hostKeyBuf = hostKeyBuf; + serverArgs.hostKeyBufSz = hostKeyBufSz; InitTcpReady(serverArgs.signal); ThreadStart(pubkey_server_thread, (void*)&serverArgs, &serThread); @@ -644,6 +665,13 @@ static int run_pubkey_test(PubkeyServerCtx* sCtx, PubkeyClientCtx* cCtx, return WS_SUCCESS; } +/* Existing callers all want the default fixture host key. */ +static int run_pubkey_test(PubkeyServerCtx* sCtx, PubkeyClientCtx* cCtx, + int expect) +{ + return run_pubkey_test_ex(sCtx, cCtx, expect, NULL, 0); +} + #ifndef WOLFSSH_NO_RSA static void test_pubkey_auth_rsa(void) { @@ -1338,6 +1366,183 @@ static void test_pubkey_auth_wrong_key(void) } #endif /* !WOLFSSH_NO_RSA && !WOLFSSH_NO_ECC */ +#if !defined(WOLFSSH_NO_MLDSA) && !defined(WOLFSSH_NO_MLDSA44) && \ + defined(WOLFSSL_MLDSA_PRIVATE_KEY) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) +/* Confirms a private-only ML-DSA host key is rejected through the real + * load path (wolfSSH_CTX_UsePrivateKey_buffer), not just IdentifyAsn1Key + * called directly as in the unit test. */ +static void test_pubkey_load_mldsa_privonly_hostkey(void) +{ + WOLFSSH_CTX* ctx; + MlDsaKey mlKey; + WC_RNG mlRng; + byte* mlDer; + int mlDerSz; + + printf("Testing ML-DSA private-only host key load rejection\n"); + + WMEMSET(&mlKey, 0, sizeof(mlKey)); + AssertIntEQ(wc_MlDsaKey_Init(&mlKey, NULL, INVALID_DEVID), 0); + AssertIntEQ(wc_MlDsaKey_SetParams(&mlKey, WC_ML_DSA_44), 0); + AssertIntEQ(wc_InitRng(&mlRng), 0); + AssertIntEQ(wc_MlDsaKey_MakeKey(&mlKey, &mlRng), 0); + wc_FreeRng(&mlRng); + + mlDer = (byte*)WMALLOC(WC_MLDSA_44_PRV_KEY_DER_SIZE, NULL, 0); + AssertNotNull(mlDer); + mlDerSz = wc_MlDsaKey_PrivateKeyToDer(&mlKey, mlDer, + WC_MLDSA_44_PRV_KEY_DER_SIZE); + wc_MlDsaKey_Free(&mlKey); + AssertIntGT(mlDerSz, 0); + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + AssertNotNull(ctx); + AssertIntEQ(wolfSSH_CTX_UsePrivateKey_buffer(ctx, mlDer, (word32)mlDerSz, + WOLFSSH_FORMAT_ASN1), WS_CRYPTO_FAILED); + wolfSSH_CTX_free(ctx); + + WFREE(mlDer, NULL, 0); +} +#endif /* !WOLFSSH_NO_MLDSA && !WOLFSSH_NO_MLDSA44 && WOLFSSL_MLDSA_PRIVATE_KEY + * && !WOLFSSL_MLDSA_NO_ASN1 && !WOLFSSL_MLDSA_NO_MAKE_KEY */ + +#if !defined(WOLFSSH_NO_ED25519) && defined(HAVE_ED25519) && \ + defined(HAVE_ED25519_MAKE_KEY) && defined(HAVE_ED25519_KEY_EXPORT) && \ + !defined(WOLFSSH_NO_ECDSA) +/* End-to-end regression test for ID_ED25519 derive-fallback in + * SendKexGetSigningKey. Tests a real handshake using a private-only + * Ed25519 host key to ensure wolfSSH correctly derives the public key. */ +static void test_pubkey_auth_ed25519_privonly_hostkey(void) +{ + PubkeyServerCtx sCtx = {0}; + PubkeyClientCtx cCtx; + byte pubKeyBuf[256]; + byte* p = pubKeyBuf; + word32 pubKeySz = sizeof(pubKeyBuf); + const byte* pubKeyType = NULL; + word32 pubKeyTypeSz = 0; + byte privKeyBuf[256]; + byte* privKeyPtr = privKeyBuf; + word32 privKeySz = sizeof(privKeyBuf); + const byte* privKeyType = NULL; + word32 privKeyTypeSz = 0; + ed25519_key edKey; + WC_RNG edRng; + byte hostKeyDer[128]; + int hostKeyDerSz; + + printf("Testing Ed25519 private-only host key at KEX (issue: derive " + "fallback)\n"); + + WMEMSET(&edKey, 0, sizeof(edKey)); + AssertIntEQ(wc_ed25519_init(&edKey), 0); + AssertIntEQ(wc_InitRng(&edRng), 0); + AssertIntEQ(wc_ed25519_make_key(&edRng, ED25519_KEY_SIZE, &edKey), 0); + wc_FreeRng(&edRng); + hostKeyDerSz = wc_Ed25519PrivateKeyToDer(&edKey, hostKeyDer, + sizeof(hostKeyDer)); + wc_ed25519_free(&edKey); + AssertIntGT(hostKeyDerSz, 0); + + AssertIntEQ(wolfSSH_ReadKey_buffer((const byte*)hanselPublicEcc, + (word32)WSTRLEN(hanselPublicEcc), WOLFSSH_FORMAT_SSH, + &p, &pubKeySz, &pubKeyType, &pubKeyTypeSz, NULL), WS_SUCCESS); + + AssertIntEQ(wc_Sha256Hash(pubKeyBuf, pubKeySz, sCtx.hash), 0); + + AssertIntEQ(wolfSSH_ReadKey_buffer(hanselPrivateEcc, hanselPrivateEccSz, + WOLFSSH_FORMAT_ASN1, + &privKeyPtr, &privKeySz, &privKeyType, &privKeyTypeSz, NULL), + WS_SUCCESS); + + cCtx.publicKeyType = pubKeyType; + cCtx.publicKeyTypeSz = pubKeyTypeSz; + cCtx.publicKey = pubKeyBuf; + cCtx.publicKeySz = pubKeySz; + cCtx.privateKey = privKeyBuf; + cCtx.privateKeySz = privKeySz; + + run_pubkey_test_ex(&sCtx, &cCtx, WS_SUCCESS, hostKeyDer, + (word32)hostKeyDerSz); +} +#endif /* !WOLFSSH_NO_ED25519 && HAVE_ED25519_MAKE_KEY && !WOLFSSH_NO_ECDSA */ + +#if !defined(WOLFSSH_NO_ECDSA) && !defined(WOLFSSH_NO_RSA) && \ + defined(HAVE_ECC_KEY_EXPORT) +/* ECDSA counterpart to test_pubkey_auth_ed25519_privonly_hostkey: a real + * handshake with a private-only SEC1 host key, exercising the + * wc_ecc_make_pub() fallback in SendKexGetSigningKey. */ +static void test_pubkey_auth_ecdsa_privonly_hostkey(void) +{ + PubkeyServerCtx sCtx = {0}; + PubkeyClientCtx cCtx; + byte pubKeyBuf[512]; + byte* p = pubKeyBuf; + word32 pubKeySz = sizeof(pubKeyBuf); + const byte* pubKeyType = NULL; + word32 pubKeyTypeSz = 0; + byte privKeyBuf[1300]; + byte* privKeyPtr = privKeyBuf; + word32 privKeySz = sizeof(privKeyBuf); + const byte* privKeyType = NULL; + word32 privKeyTypeSz = 0; + ecc_key hostKey; + WC_RNG hostRng; + byte* hostKeyDer; + int hostKeyDerSz; +#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256 + const int hostKeySz = 32; +#elif !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP384) + const int hostKeySz = 48; +#elif !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP521) + const int hostKeySz = 66; +#else + #error "Enable nistp256, nistp384, nistp521, or disable ECDSA." +#endif + + printf("Testing ECDSA private-only host key at KEX (wc_ecc_make_pub " + "fallback)\n"); + + WMEMSET(&hostKey, 0, sizeof(hostKey)); + AssertIntEQ(wc_ecc_init(&hostKey), 0); + AssertIntEQ(wc_InitRng(&hostRng), 0); + AssertIntEQ(wc_ecc_make_key(&hostRng, hostKeySz, &hostKey), 0); + wc_FreeRng(&hostRng); + hostKeyDerSz = wc_EccKeyDerSize(&hostKey, 0); + AssertIntGT(hostKeyDerSz, 0); + hostKeyDer = (byte*)WMALLOC((word32)hostKeyDerSz, NULL, 0); + AssertNotNull(hostKeyDer); + hostKeyDerSz = wc_EccPrivateKeyToDer(&hostKey, hostKeyDer, + (word32)hostKeyDerSz); + wc_ecc_free(&hostKey); + AssertIntGT(hostKeyDerSz, 0); + + AssertIntEQ(wolfSSH_ReadKey_buffer((const byte*)hanselPublicRsa, + (word32)WSTRLEN(hanselPublicRsa), WOLFSSH_FORMAT_SSH, + &p, &pubKeySz, &pubKeyType, &pubKeyTypeSz, NULL), WS_SUCCESS); + + AssertIntEQ(wc_Sha256Hash(pubKeyBuf, pubKeySz, sCtx.hash), 0); + + AssertIntEQ(wolfSSH_ReadKey_buffer(hanselPrivateRsa, hanselPrivateRsaSz, + WOLFSSH_FORMAT_ASN1, + &privKeyPtr, &privKeySz, &privKeyType, &privKeyTypeSz, NULL), + WS_SUCCESS); + + cCtx.publicKeyType = pubKeyType; + cCtx.publicKeyTypeSz = pubKeyTypeSz; + cCtx.publicKey = pubKeyBuf; + cCtx.publicKeySz = pubKeySz; + cCtx.privateKey = privKeyBuf; + cCtx.privateKeySz = privKeySz; + + run_pubkey_test_ex(&sCtx, &cCtx, WS_SUCCESS, hostKeyDer, + (word32)hostKeyDerSz); + + WFREE(hostKeyDer, NULL, 0); +} +#endif /* !WOLFSSH_NO_ECDSA && !WOLFSSH_NO_RSA */ + #endif /* pubkey test guard */ /* ----------------------------------------------------------------------- @@ -2104,6 +2309,20 @@ int wolfSSH_AuthTest(int argc, char** argv) #if !defined(WOLFSSH_NO_RSA) && !defined(WOLFSSH_NO_ECC) test_pubkey_auth_wrong_key(); #endif +#if !defined(WOLFSSH_NO_ED25519) && defined(HAVE_ED25519) && \ + defined(HAVE_ED25519_MAKE_KEY) && defined(HAVE_ED25519_KEY_EXPORT) && \ + !defined(WOLFSSH_NO_ECDSA) + test_pubkey_auth_ed25519_privonly_hostkey(); +#endif +#if !defined(WOLFSSH_NO_ECDSA) && !defined(WOLFSSH_NO_RSA) && \ + defined(HAVE_ECC_KEY_EXPORT) + test_pubkey_auth_ecdsa_privonly_hostkey(); +#endif +#if !defined(WOLFSSH_NO_MLDSA) && !defined(WOLFSSH_NO_MLDSA44) && \ + defined(WOLFSSL_MLDSA_PRIVATE_KEY) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) + test_pubkey_load_mldsa_privonly_hostkey(); +#endif #endif /* !NO_SHA256 */ /* Keyboard-interactive auth tests */ diff --git a/tests/auth.h b/tests/auth.h index f8e563c18..32805f1ab 100644 --- a/tests/auth.h +++ b/tests/auth.h @@ -32,6 +32,8 @@ typedef struct thread_args { WS_CallbackUserAuth userAuth; /* server userAuth callback; NULL = none */ const byte* caCert; /* CA cert for AddRootCert; NULL = skip */ word32 caCertSz; + const byte* hostKeyBuf; /* server host key; NULL = use load_key() */ + word32 hostKeyBufSz; } thread_args; #endif /* _WOLFSSH_TESTS_AUTH_H_ */ diff --git a/tests/unit.c b/tests/unit.c index 0f57c728f..be346cbd8 100644 --- a/tests/unit.c +++ b/tests/unit.c @@ -5995,6 +5995,509 @@ static int test_BuildUserAuthRequestMlDsa(void) } #endif +#if !defined(WOLFSSH_NO_ECDSA) && defined(HAVE_ECC_KEY_EXPORT) +/* Private-only SEC1 DER (public point omitted): must succeed as cert may supply + * public key later. Shared across P-256/P-384/P-521. + * Return codes -667..-674 */ +static int test_IdentifyAsn1Key_EccPrivOnlyDer(int curveSz, + int expectedKeyId, const char* curveName) +{ + int ret; + ecc_key eccKey; + WC_RNG eccRng; + byte* eccDer = NULL; + int eccDerSz; + + WMEMSET(&eccKey, 0, sizeof(eccKey)); + if (wc_ecc_init(&eccKey) != 0) { + return -667; + } + if (wc_InitRng(&eccRng) != 0) { + wc_ecc_free(&eccKey); + return -668; + } + if (wc_ecc_make_key(&eccRng, curveSz, &eccKey) != 0) { + wc_FreeRng(&eccRng); + wc_ecc_free(&eccKey); + return -669; + } + wc_FreeRng(&eccRng); + + eccDerSz = wc_EccKeyDerSize(&eccKey, 0); + if (eccDerSz <= 0) { + wc_ecc_free(&eccKey); + return -670; + } + eccDer = (byte*)WMALLOC((word32)eccDerSz, NULL, 0); + if (eccDer == NULL) { + wc_ecc_free(&eccKey); + return -671; + } + eccDerSz = wc_EccPrivateKeyToDer(&eccKey, eccDer, (word32)eccDerSz); + wc_ecc_free(&eccKey); + if (eccDerSz <= 0) { + WFREE(eccDer, NULL, 0); + return -672; + } + + ret = IdentifyAsn1Key(eccDer, (word32)eccDerSz, 1, NULL, NULL); + if (ret != expectedKeyId) { + WFREE(eccDer, NULL, 0); + printf("IdentifyAsn1Key: private-only ECC %s DER expected " + "keyId %d, got %d\n", curveName, expectedKeyId, ret); + return -673; + } + + /* Requesting populated WS_KeySignature confirms key object survives. */ + { + WS_KeySignature* eccKeySig = NULL; + + ret = IdentifyAsn1Key(eccDer, (word32)eccDerSz, 1, NULL, + &eccKeySig); + WFREE(eccDer, NULL, 0); + if (ret != expectedKeyId || eccKeySig == NULL || + eccKeySig->keyId != expectedKeyId) { + printf("IdentifyAsn1Key: private-only ECC %s DER pkey-out " + "variant failed, ret=%d\n", curveName, ret); + if (eccKeySig != NULL) { + wolfSSH_KEY_clean(eccKeySig); + WFREE(eccKeySig, NULL, DYNTYPE_PRIVKEY); + } + return -674; + } + wolfSSH_KEY_clean(eccKeySig); + WFREE(eccKeySig, NULL, DYNTYPE_PRIVKEY); + } + + return 0; +} + +static int test_IdentifyAsn1Key_EccPrivOnlyDerFailure(int curveSz, + const char* curveName) +{ + int ret; + ecc_key eccKey; + WC_RNG eccRng; + byte* eccDer = NULL; + int eccDerSz; + int i; + + WMEMSET(&eccKey, 0, sizeof(eccKey)); + if (wc_ecc_init(&eccKey) != 0) { + return -6932; + } + if (wc_InitRng(&eccRng) != 0) { + wc_ecc_free(&eccKey); + return -6933; + } + if (wc_ecc_make_key(&eccRng, curveSz, &eccKey) != 0) { + wc_FreeRng(&eccRng); + wc_ecc_free(&eccKey); + return -6934; + } + wc_FreeRng(&eccRng); + + eccDerSz = wc_EccKeyDerSize(&eccKey, 0); + if (eccDerSz <= 0) { + wc_ecc_free(&eccKey); + return -6935; + } + eccDer = (byte*)WMALLOC((word32)eccDerSz, NULL, 0); + if (eccDer == NULL) { + wc_ecc_free(&eccKey); + return -6936; + } + eccDerSz = wc_EccPrivateKeyToDer(&eccKey, eccDer, (word32)eccDerSz); + wc_ecc_free(&eccKey); + if (eccDerSz <= 0) { + WFREE(eccDer, NULL, 0); + return -6937; + } + + /* Corrupt the private scalar to force a wc_ecc_make_pub failure during + * pubkey derivation in IdentifyAsn1Key. */ + for (i = 0; i < eccDerSz - curveSz - 2; i++) { + if (eccDer[i] == 0x04 && eccDer[i+1] == curveSz) { + WMEMSET(eccDer + i + 2, 0, curveSz); + break; + } + } + + ret = IdentifyAsn1Key(eccDer, (word32)eccDerSz, 1, NULL, NULL); + WFREE(eccDer, NULL, 0); + + if (ret != WS_CRYPTO_FAILED) { + printf("IdentifyAsn1Key: private-only ECC %s DER fallback derivation " + "expected WS_CRYPTO_FAILED, got %d\n", curveName, ret); + return -6938; + } + + return 0; +} +#endif + +#if !defined(WOLFSSH_NO_ED25519) && defined(HAVE_ED25519) && \ + defined(HAVE_ED25519_MAKE_KEY) && defined(HAVE_ED25519_KEY_EXPORT) +/* Private-only DER: Ed25519 public key is deterministically derivable, so + * IdentifyAsn1Key must load it successfully. + * Return codes -675..-681 */ +static int test_IdentifyAsn1Key_Ed25519PrivOnlyDer(void) +{ + int ret; + ed25519_key edKey; + WC_RNG edRng; + byte* edDer = NULL; + int edDerSz; + + if (wc_ed25519_init(&edKey) != 0) { + return -675; + } + if (wc_InitRng(&edRng) != 0) { + wc_ed25519_free(&edKey); + return -676; + } + if (wc_ed25519_make_key(&edRng, ED25519_KEY_SIZE, &edKey) != 0) { + wc_FreeRng(&edRng); + wc_ed25519_free(&edKey); + return -677; + } + wc_FreeRng(&edRng); + + edDerSz = wc_Ed25519PrivateKeyToDer(&edKey, NULL, 0); + if (edDerSz <= 0) { + wc_ed25519_free(&edKey); + return -678; + } + edDer = (byte*)WMALLOC((word32)edDerSz, NULL, 0); + if (edDer == NULL) { + wc_ed25519_free(&edKey); + return -679; + } + edDerSz = wc_Ed25519PrivateKeyToDer(&edKey, edDer, (word32)edDerSz); + wc_ed25519_free(&edKey); + if (edDerSz <= 0) { + WFREE(edDer, NULL, 0); + return -680; + } + + { + WS_KeySignature* edKeySig = NULL; + + ret = IdentifyAsn1Key(edDer, (word32)edDerSz, 1, NULL, &edKeySig); + WFREE(edDer, NULL, 0); + if (ret != ID_ED25519 || edKeySig == NULL || + edKeySig->keyId != ID_ED25519) { + printf("IdentifyAsn1Key: private-only Ed25519 DER expected " + "ID_ED25519, got %d\n", ret); + if (edKeySig != NULL) { + wolfSSH_KEY_clean(edKeySig); + WFREE(edKeySig, NULL, DYNTYPE_PRIVKEY); + } + return -681; + } + wolfSSH_KEY_clean(edKeySig); + WFREE(edKeySig, NULL, DYNTYPE_PRIVKEY); + } + + return 0; +} +#endif + +#if !defined(WOLFSSH_NO_ED25519) && defined(HAVE_ED25519) && \ + !defined(HAVE_ED25519_MAKE_KEY) +/* Private-only Ed25519 DER must be rejected if no HAVE_ED25519_MAKE_KEY. + * Hardcoded PKCS8 DER since make_key is unavailable. */ +static int test_IdentifyAsn1Key_Ed25519PrivOnlyDerNoMakeKey(void) +{ + int ret; + /* SEQUENCE { version 0, AlgorithmIdentifier{Ed25519 OID}, + * OCTET STRING { OCTET STRING <32-byte seed> } } */ + static const byte edDer[] = { + 0x30, 0x2E, + 0x02, 0x01, 0x00, + 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x70, + 0x04, 0x22, + 0x04, 0x20, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20 + }; + WS_KeySignature* edKeySig = NULL; + + ret = IdentifyAsn1Key(edDer, (word32)sizeof(edDer), 1, NULL, &edKeySig); + if (ret != WS_CRYPTO_FAILED) { + printf("IdentifyAsn1Key: private-only Ed25519 DER expected " + "WS_CRYPTO_FAILED without HAVE_ED25519_MAKE_KEY, got %d\n", + ret); + if (edKeySig != NULL) { + wolfSSH_KEY_clean(edKeySig); + WFREE(edKeySig, NULL, DYNTYPE_PRIVKEY); + } + return -682; + } + if (edKeySig != NULL) { + printf("IdentifyAsn1Key: expected NULL keySig on rejection\n"); + wolfSSH_KEY_clean(edKeySig); + WFREE(edKeySig, NULL, DYNTYPE_PRIVKEY); + return -683; + } + + return 0; +} +#endif + +#if !defined(WOLFSSH_NO_ED25519) && defined(HAVE_ED25519) && \ + defined(HAVE_ED25519_MAKE_KEY) && defined(HAVE_ED25519_KEY_EXPORT) && \ + defined(HAVE_ED25519_SIGN) && defined(HAVE_ED25519_VERIFY) +/* Mirrors KEX host-key signing fallback: decode private-only DER, let + * export_public fail, derive public key, and confirm signing/verification. + * This only proves the underlying wolfCrypt sequence; the real regression + * guard for SendKexGetSigningKey's derive fallback (Ed25519 and ECDSA) is + * the end-to-end test_pubkey_auth_ed25519_privonly_hostkey / + * test_pubkey_auth_ecdsa_privonly_hostkey in tests/auth.c. + * Return codes -682..-691. Mutually exclusive with + * test_IdentifyAsn1Key_Ed25519PrivOnlyDerNoMakeKey, which reuses -682/-683. */ +static int test_Ed25519KexDeriveFallback(void) +{ + ed25519_key edKey; + WC_RNG edRng; + byte der[128]; + int derSz; + word32 idx = 0; + ed25519_key kexKey; + byte q[ED25519_PUB_KEY_SIZE]; + word32 qSz = sizeof(q); + byte sig[ED25519_SIG_SIZE]; + word32 sigSz = sizeof(sig); + const byte msg[] = "wolfSSH KEX derive-fallback test message"; + int verifyRes = 0; + + WMEMSET(&edKey, 0, sizeof(edKey)); + if (wc_ed25519_init(&edKey) != 0) { + return -682; + } + if (wc_InitRng(&edRng) != 0) { + wc_ed25519_free(&edKey); + return -683; + } + if (wc_ed25519_make_key(&edRng, ED25519_KEY_SIZE, &edKey) != 0) { + wc_FreeRng(&edRng); + wc_ed25519_free(&edKey); + return -684; + } + wc_FreeRng(&edRng); + derSz = wc_Ed25519PrivateKeyToDer(&edKey, der, sizeof(der)); + wc_ed25519_free(&edKey); + if (derSz <= 0) { + return -685; + } + + /* KEX sequence: fresh decode, export_public, then fallback. */ + WMEMSET(&kexKey, 0, sizeof(kexKey)); + if (wc_ed25519_init(&kexKey) != 0) { + return -686; + } + if (wc_Ed25519PrivateKeyDecode(der, &idx, &kexKey, + (word32)derSz) != 0) { + wc_ed25519_free(&kexKey); + return -687; + } + if (wc_ed25519_export_public(&kexKey, q, &qSz) == 0) { + /* Public key derived during decode; fallback not needed. */ + wc_ed25519_free(&kexKey); + return 0; + } + /* trusted=1: matches production (src/internal.c). */ + if (wc_ed25519_make_public(&kexKey, q, ED25519_PUB_KEY_SIZE) != 0 || + wc_ed25519_import_public_ex(q, ED25519_PUB_KEY_SIZE, &kexKey, + 1) != 0) { + wc_ed25519_free(&kexKey); + printf("test_Ed25519KexDeriveFallback: derive/import failed\n"); + return -689; + } + + if (wc_ed25519_sign_msg(msg, sizeof(msg), sig, &sigSz, &kexKey) != 0) { + wc_ed25519_free(&kexKey); + printf("test_Ed25519KexDeriveFallback: sign failed\n"); + return -690; + } + if (wc_ed25519_verify_msg(sig, sigSz, msg, sizeof(msg), &verifyRes, + &kexKey) != 0 || verifyRes != 1) { + wc_ed25519_free(&kexKey); + printf("test_Ed25519KexDeriveFallback: verify failed\n"); + return -691; + } + wc_ed25519_free(&kexKey); + + return 0; +} + +/* Failure path: wc_ed25519_make_public() must fail cleanly (not fabricate + * a key) when handed an undersized output buffer. + * Return codes -6920..-6926 */ +static int test_Ed25519KexDeriveFallbackFailure(void) +{ + ed25519_key edKey; + WC_RNG edRng; + byte der[128]; + int derSz; + word32 idx = 0; + ed25519_key kexKey; + byte q[ED25519_PUB_KEY_SIZE]; + word32 qSz = sizeof(q); + + WMEMSET(&edKey, 0, sizeof(edKey)); + if (wc_ed25519_init(&edKey) != 0) { + return -6920; + } + if (wc_InitRng(&edRng) != 0) { + wc_ed25519_free(&edKey); + return -6921; + } + if (wc_ed25519_make_key(&edRng, ED25519_KEY_SIZE, &edKey) != 0) { + wc_FreeRng(&edRng); + wc_ed25519_free(&edKey); + return -6922; + } + wc_FreeRng(&edRng); + derSz = wc_Ed25519PrivateKeyToDer(&edKey, der, sizeof(der)); + wc_ed25519_free(&edKey); + if (derSz <= 0) { + return -6923; + } + + WMEMSET(&kexKey, 0, sizeof(kexKey)); + if (wc_ed25519_init(&kexKey) != 0) { + return -6924; + } + if (wc_Ed25519PrivateKeyDecode(der, &idx, &kexKey, + (word32)derSz) != 0) { + wc_ed25519_free(&kexKey); + return -6925; + } + if (wc_ed25519_export_public(&kexKey, q, &qSz) == 0) { + /* Public key derived during decode; fallback not exercised. */ + wc_ed25519_free(&kexKey); + return 0; + } + + if (wc_ed25519_make_public(&kexKey, q, 0) == 0) { + wc_ed25519_free(&kexKey); + printf("test_Ed25519KexDeriveFallbackFailure: expected failure on " + "undersized buffer, got success\n"); + return -6926; + } + wc_ed25519_free(&kexKey); + + return 0; +} +#endif + +#ifndef WOLFSSH_NO_ECDSA +/* Failure path counterpart for ECDSA: wc_ecc_make_pub() must fail cleanly + * when a key has no private scalar loaded, mirroring the guard added in + * IdentifyAsn1Key and the fallback in SendKexGetSigningKey. + * Return codes -6930..-6931 */ +static int test_ECCKexDeriveFallbackFailure(void) +{ + ecc_key eccKey; + int ret; + + WMEMSET(&eccKey, 0, sizeof(eccKey)); + if (wc_ecc_init(&eccKey) != 0) { + return -6930; + } + + ret = wc_ecc_make_pub(&eccKey, NULL); + wc_ecc_free(&eccKey); + if (ret == 0) { + printf("test_ECCKexDeriveFallbackFailure: expected failure on key " + "with no private scalar, got success\n"); + return -6931; + } + + return 0; +} +#endif + +#if !defined(WOLFSSH_NO_MLDSA) && defined(WOLFSSL_MLDSA_PRIVATE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + (!defined(WOLFSSH_NO_MLDSA44) || !defined(WOLFSSH_NO_MLDSA65) || \ + !defined(WOLFSSH_NO_MLDSA87)) +/* Private-only DER: rejected at decode time. Shared across 44/65/87 levels. + * Return codes -692..-699 */ +static int test_IdentifyAsn1Key_MlDsaPrivOnlyDer(byte level, + word32 derBufSz, const char* levelName) +{ + int ret; + MlDsaKey mlKey; + WC_RNG mlRng; + byte* mlDer = NULL; + int mlDerSz; + + WMEMSET(&mlKey, 0, sizeof(mlKey)); + if (wc_MlDsaKey_Init(&mlKey, NULL, INVALID_DEVID) != 0) { + return -692; + } + if (wc_MlDsaKey_SetParams(&mlKey, level) != 0) { + wc_MlDsaKey_Free(&mlKey); + return -693; + } + if (wc_InitRng(&mlRng) != 0) { + wc_MlDsaKey_Free(&mlKey); + return -694; + } + if (wc_MlDsaKey_MakeKey(&mlKey, &mlRng) != 0) { + wc_FreeRng(&mlRng); + wc_MlDsaKey_Free(&mlKey); + return -695; + } + wc_FreeRng(&mlRng); + + mlDer = (byte*)WMALLOC(derBufSz, NULL, 0); + if (mlDer == NULL) { + wc_MlDsaKey_Free(&mlKey); + return -696; + } + mlDerSz = wc_MlDsaKey_PrivateKeyToDer(&mlKey, mlDer, derBufSz); + wc_MlDsaKey_Free(&mlKey); + if (mlDerSz <= 0) { + WFREE(mlDer, NULL, 0); + return -697; + } + + ret = IdentifyAsn1Key(mlDer, (word32)mlDerSz, 1, NULL, NULL); + if (ret != WS_CRYPTO_FAILED) { + WFREE(mlDer, NULL, 0); + printf("IdentifyAsn1Key: private-only MlDsa %s DER expected " + "WS_CRYPTO_FAILED, got %d\n", levelName, ret); + return -698; + } + + /* Confirms *pkey stays NULL on rejection path. */ + { + WS_KeySignature* mlKeySig = NULL; + + ret = IdentifyAsn1Key(mlDer, (word32)mlDerSz, 1, NULL, + &mlKeySig); + WFREE(mlDer, NULL, 0); + if (ret != WS_CRYPTO_FAILED || mlKeySig != NULL) { + printf("IdentifyAsn1Key: private-only MlDsa %s DER pkey-out " + "variant failed, ret=%d\n", levelName, ret); + if (mlKeySig != NULL) { + wolfSSH_KEY_clean(mlKeySig); + WFREE(mlKeySig, NULL, DYNTYPE_PRIVKEY); + } + return -699; + } + } + + return 0; +} +#endif + /* IdentifyAsn1Key unit test * * Exercises every new wc_Free* error-path added in IdentifyAsn1Key: @@ -6027,6 +6530,18 @@ static int test_IdentifyAsn1Key(void) printf("IdentifyAsn1Key: ECC P-256 priv failed, ret=%d\n", ret); result = -601; goto done; } + +#ifdef HAVE_ECC_KEY_EXPORT + ret = test_IdentifyAsn1Key_EccPrivOnlyDer(32, ID_ECDSA_SHA2_NISTP256, + "P-256"); + if (ret != 0) { + result = ret; goto done; + } + ret = test_IdentifyAsn1Key_EccPrivOnlyDerFailure(32, "P-256"); + if (ret != 0) { + result = ret; goto done; + } +#endif #endif #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP384 @@ -6037,6 +6552,18 @@ static int test_IdentifyAsn1Key(void) printf("IdentifyAsn1Key: ECC P-384 priv failed, ret=%d\n", ret); result = -602; goto done; } + +#ifdef HAVE_ECC_KEY_EXPORT + ret = test_IdentifyAsn1Key_EccPrivOnlyDer(48, ID_ECDSA_SHA2_NISTP384, + "P-384"); + if (ret != 0) { + result = ret; goto done; + } + ret = test_IdentifyAsn1Key_EccPrivOnlyDerFailure(48, "P-384"); + if (ret != 0) { + result = ret; goto done; + } +#endif #endif #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP521 @@ -6047,6 +6574,18 @@ static int test_IdentifyAsn1Key(void) printf("IdentifyAsn1Key: ECC P-521 priv failed, ret=%d\n", ret); result = -603; goto done; } + +#ifdef HAVE_ECC_KEY_EXPORT + ret = test_IdentifyAsn1Key_EccPrivOnlyDer(66, ID_ECDSA_SHA2_NISTP521, + "P-521"); + if (ret != 0) { + result = ret; goto done; + } + ret = test_IdentifyAsn1Key_EccPrivOnlyDerFailure(66, "P-521"); + if (ret != 0) { + result = ret; goto done; + } +#endif #endif #if !defined(WOLFSSH_NO_ED25519) @@ -6057,6 +6596,41 @@ static int test_IdentifyAsn1Key(void) printf("IdentifyAsn1Key: Ed25519 priv failed, ret=%d\n", ret); result = -604; goto done; } + +#if defined(HAVE_ED25519) && defined(HAVE_ED25519_MAKE_KEY) && \ + defined(HAVE_ED25519_KEY_EXPORT) + ret = test_IdentifyAsn1Key_Ed25519PrivOnlyDer(); + if (ret != 0) { + result = ret; goto done; + } +#endif + +#if defined(HAVE_ED25519) && !defined(HAVE_ED25519_MAKE_KEY) + ret = test_IdentifyAsn1Key_Ed25519PrivOnlyDerNoMakeKey(); + if (ret != 0) { + result = ret; goto done; + } +#endif + +#if defined(HAVE_ED25519) && defined(HAVE_ED25519_MAKE_KEY) && \ + defined(HAVE_ED25519_KEY_EXPORT) && defined(HAVE_ED25519_SIGN) && \ + defined(HAVE_ED25519_VERIFY) + ret = test_Ed25519KexDeriveFallback(); + if (ret != 0) { + result = ret; goto done; + } + ret = test_Ed25519KexDeriveFallbackFailure(); + if (ret != 0) { + result = ret; goto done; + } +#endif +#endif + +#ifndef WOLFSSH_NO_ECDSA + ret = test_ECCKexDeriveFallbackFailure(); + if (ret != 0) { + result = ret; goto done; + } #endif #if !defined(WOLFSSH_NO_MLDSA) && !defined(WOLFSSH_NO_MLDSA44) @@ -6116,6 +6690,35 @@ static int test_IdentifyAsn1Key(void) result = -611; goto done; } } + +#if defined(WOLFSSL_MLDSA_PRIVATE_KEY) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) + ret = test_IdentifyAsn1Key_MlDsaPrivOnlyDer(WC_ML_DSA_44, + WC_MLDSA_44_PRV_KEY_DER_SIZE, "44"); + if (ret != 0) { + result = ret; goto done; + } +#endif +#endif + +#if !defined(WOLFSSH_NO_MLDSA) && !defined(WOLFSSH_NO_MLDSA65) && \ + defined(WOLFSSL_MLDSA_PRIVATE_KEY) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) + ret = test_IdentifyAsn1Key_MlDsaPrivOnlyDer(WC_ML_DSA_65, + WC_MLDSA_65_PRV_KEY_DER_SIZE, "65"); + if (ret != 0) { + result = ret; goto done; + } +#endif + +#if !defined(WOLFSSH_NO_MLDSA) && !defined(WOLFSSH_NO_MLDSA87) && \ + defined(WOLFSSL_MLDSA_PRIVATE_KEY) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) + ret = test_IdentifyAsn1Key_MlDsaPrivOnlyDer(WC_ML_DSA_87, + WC_MLDSA_87_PRV_KEY_DER_SIZE, "87"); + if (ret != 0) { + result = ret; goto done; + } #endif /* Unsupported ECC curve: triggers wc_ecc_free in the default: branch diff --git a/wolfssh/internal.h b/wolfssh/internal.h index 56c5abb04..179f79466 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -105,7 +106,16 @@ extern "C" { #define WOLFSSH_NO_DH #endif -#ifndef HAVE_DILITHIUM +#define WOLFSSL_V5_0_0 0x05000000 +#define WOLFSSL_V5_7_0 0x05007000 +#define WOLFSSL_V5_7_2 0x05007002 +#define WOLFSSL_V5_9_2 0x05009002 + +/* wc_MlDsaKey_* / WC_MLDSA_* naming replaced the wc_Dilithium_* API in + * wolfSSL 5.9.2. HAVE_DILITHIUM alone doesn't distinguish the two, so + * require the version that has the new API too. */ +#if !defined(HAVE_DILITHIUM) || \ + (LIBWOLFSSL_VERSION_HEX < WOLFSSL_V5_9_2) #undef WOLFSSH_NO_MLDSA #define WOLFSSH_NO_MLDSA #undef WOLFSSH_NO_MLDSA44 @@ -1810,11 +1820,6 @@ enum TerminalModes { #endif /* WOLFSSH_TERM */ -#define WOLFSSL_V5_0_0 0x05000000 -#define WOLFSSL_V5_7_0 0x05007000 -#define WOLFSSL_V5_7_2 0x05007002 - - #ifdef __cplusplus } #endif