diff --git a/.github/workflows/os-check.yml b/.github/workflows/os-check.yml index 8de9a277b..9d39e60d5 100644 --- a/.github/workflows/os-check.yml +++ b/.github/workflows/os-check.yml @@ -142,3 +142,39 @@ jobs: set -o pipefail LD_LIBRARY_PATH=${{ github.workspace }}/build-dir/lib ./tests/unit.test | tee unit-test.log grep "ScpSendCallback_ExactFitBuffer: SUCCESS" unit-test.log + + build_wolfssh_no_rsa_ecc: + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest, macos-latest ] + name: Build and test wolfssh without RSA or ECC + runs-on: ${{ matrix.os }} + timeout-minutes: 16 + steps: + # Not the cached --enable-all wolfSSL: this job needs one with no RSA + # and no ECC. Ed25519 is then the only signing algorithm wolfSSH has, + # and wolfssh/internal.h wants WOLFSSL_ED25519_STREAMING_VERIFY with + # it, so --enable-ed25519-stream is required, not optional. The ssh + # client app needs Base64_Encode_NoNl(), which nothing else in this + # short list pulls in, hence --enable-base64encode. + - name: Checkout, build, and install wolfssl + uses: wolfSSL/actions-build-autotools-project@v1 + with: + repository: wolfssl/wolfssl + ref: master + path: wolfssl + configure: >- + --enable-ssh --enable-ed25519 --enable-ed25519-stream + --enable-curve25519 --enable-aesctr --enable-base64encode + --disable-rsa --disable-ecc --disable-dsa --disable-mldsa + check: false + install: true + + - name: Checkout, build, and test wolfssh + uses: wolfSSL/actions-build-autotools-project@v1 + with: + repository: wolfssl/wolfssh + path: wolfssh + configure: --enable-keyboard-interactive --enable-sftp --enable-scp --enable-sshclient LDFLAGS="-L${{ github.workspace }}/build-dir/lib" CPPFLAGS="-I${{ github.workspace }}/build-dir/include" + check: true diff --git a/examples/client/common.c b/examples/client/common.c index c6d471ae1..9e8e9d22e 100644 --- a/examples/client/common.c +++ b/examples/client/common.c @@ -222,7 +222,7 @@ static const unsigned int hanselPrivateRsaSz = 1191; #endif -#ifndef WOLFSSH_NO_ECC +#ifndef WOLFSSH_NO_ECDSA #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256 static const char* hanselPublicEcc = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAA" @@ -275,6 +275,23 @@ static const unsigned int hanselPrivateEccSz = 223; #endif #endif +/* The pair in keys/hansel-key-ed25519.*, the only built-in user key left + * when both RSA and ECDSA are compiled out. */ +#if defined(WOLFSSH_NO_RSA) && defined(WOLFSSH_NO_ECDSA) && \ + !defined(WOLFSSH_NO_ED25519) +static const char* hanselPublicEd25519 = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHTSoBZIJBO2V0Jb2OWyMWNbkD" + "d6ReDfKxnrAPlbPuCe hansel"; +static const byte hanselPrivateEd25519[] = { + 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, + 0x04, 0x22, 0x04, 0x20, 0x28, 0xc6, 0xe9, 0xd8, 0x37, 0x4d, 0x0c, 0x52, + 0x7e, 0x5f, 0xb3, 0x4c, 0x81, 0xe8, 0x68, 0xee, 0xc9, 0x7c, 0xad, 0x00, + 0xad, 0xa0, 0xe3, 0xe2, 0x13, 0x06, 0x55, 0xf1, 0x17, 0xf1, 0x0a, 0xf0 +}; +static const unsigned int hanselPrivateEd25519Sz = + (unsigned int)sizeof(hanselPrivateEd25519); +#endif + #if defined(WOLFSSH_CERTS) @@ -476,6 +493,13 @@ int ClientUserAuth(byte authType, if (authType == WOLFSSH_USERAUTH_PUBLICKEY) { WS_UserAuthData_PublicKey* pk = &authData->sf.publicKey; + if (userPublicKeyType == NULL || userPublicKeySz == 0) { + /* Nothing to sign with. SendUserAuthRequest() turns this + * into WS_FATAL_ERROR rather than putting an untyped + * publickey request on the wire. */ + return WOLFSSH_USERAUTH_FAILURE; + } + pk->publicKeyType = userPublicKeyType; pk->publicKeyTypeSz = userPublicKeyTypeSz; pk->publicKey = userPublicKey; @@ -1008,16 +1032,25 @@ int ClientSetPrivateKey(const char* privKeyName, int userEcc, (void)tpmKeyAuth; /* Not used */ if (privKeyName == NULL) { - #if defined(WOLFSSH_NO_RSA) && defined(WOLFSSH_NO_ECC) + #if defined(WOLFSSH_NO_RSA) && defined(WOLFSSH_NO_ECDSA) + (void)userEcc; + #ifndef WOLFSSH_NO_ED25519 + userPrivateKeySz = sizeof(userPrivateKeyBuf); + ret = wolfSSH_ReadKey_buffer(hanselPrivateEd25519, + hanselPrivateEd25519Sz, WOLFSSH_FORMAT_ASN1, + &userPrivateKey, &userPrivateKeySz, + &userPrivateKeyType, &userPrivateKeyTypeSz, heap); + isPrivate = 1; + #else /* No built-in key to load. Leave the client to authenticate * some other way rather than failing here. */ userPrivateKeySz = 0; userPrivateKeyType = NULL; - (void)userEcc; (void)heap; + #endif #else if (userEcc) { - #ifndef WOLFSSH_NO_ECC + #ifndef WOLFSSH_NO_ECDSA userPrivateKeySz = sizeof(userPrivateKeyBuf); ret = wolfSSH_ReadKey_buffer(hanselPrivateEcc, hanselPrivateEccSz, WOLFSSH_FORMAT_ASN1, &userPrivateKey, &userPrivateKeySz, @@ -1080,19 +1113,32 @@ int ClientUsePubKey(const char* pubKeyName, int userEcc, void* heap) int ret = 0; if (pubKeyName == NULL) { - #if defined(WOLFSSH_NO_RSA) && defined(WOLFSSH_NO_ECC) + #if defined(WOLFSSH_NO_RSA) && defined(WOLFSSH_NO_ECDSA) + (void)userEcc; + #ifndef WOLFSSH_NO_ED25519 + { + byte* p = userPublicKey; + + userPublicKeySz = sizeof(userPublicKeyBuf); + ret = wolfSSH_ReadKey_buffer((const byte*)hanselPublicEd25519, + (word32)strlen(hanselPublicEd25519), WOLFSSH_FORMAT_SSH, + &p, &userPublicKeySz, + &userPublicKeyType, &userPublicKeyTypeSz, heap); + isPrivate = 1; + } + #else /* No built-in key to load. Leave the client to authenticate * some other way rather than failing here. */ userPublicKeySz = 0; userPublicKeyType = NULL; - (void)userEcc; (void)heap; + #endif #else byte* p = userPublicKey; userPublicKeySz = sizeof(userPublicKeyBuf); if (userEcc) { - #ifndef WOLFSSH_NO_ECC + #ifndef WOLFSSH_NO_ECDSA ret = wolfSSH_ReadKey_buffer((const byte*)hanselPublicEcc, (word32)strlen(hanselPublicEcc), WOLFSSH_FORMAT_SSH, &p, &userPublicKeySz, diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index d3ba00eb3..76156bd47 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -2284,6 +2284,7 @@ static int load_file(const char* fileName, byte* buf, word32* bufSz) } #endif /* NO_FILESYSTEM */ +#if !defined(WOLFSSH_NO_RSA) || !defined(WOLFSSH_NO_ECDSA) #ifdef WOLFSSH_NO_ECDSA_SHA2_NISTP256 #define ECC_PATH "./keys/server-key-ecc-521.der" #else @@ -2319,6 +2320,7 @@ static int load_key(byte isEcc, byte* buf, word32 bufSz) return sz; } +#endif /* !WOLFSSH_NO_RSA || !WOLFSSH_NO_ECDSA */ #ifndef WOLFSSH_NO_ED25519 /* returns buffer size on success */ @@ -2659,7 +2661,7 @@ static const char samplePasswordBuffer[] = "jack:fetchapail\n"; -#ifndef WOLFSSH_NO_ECC +#ifndef WOLFSSH_NO_ECDSA #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256 static const char samplePublicKeyEccBuffer[] = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAA" @@ -2703,6 +2705,17 @@ static const char samplePublicKeyEccBuffer[] = #endif /* WOLFSSH_TPM */ #endif /* WOLFSSH_NO_RSA */ +/* Ed25519 is the only signing algorithm left when neither RSA nor ECDSA + * is compiled in, so the server needs sample keys of its own. */ +#if defined(WOLFSSH_NO_RSA) && defined(WOLFSSH_NO_ECDSA) && \ + !defined(WOLFSSH_NO_ED25519) +static const char samplePublicKeyEd25519Buffer[] = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHTSoBZIJBO2V0Jb2OWyMWNbkD" + "d6ReDfKxnrAPlbPuCe hansel\n" + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFD8Bwir++gzNJmif9ooAZdaRi" + "sFZjlp9XU2seaec7/m gretel\n"; +#endif + #ifdef WOLFSSH_ALLOW_USERAUTH_NONE static const char sampleNoneBuffer[] = @@ -3882,12 +3895,14 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) userEcc = 1; peerEcc = 1; #endif -#ifdef WOLFSSH_NO_ECC - /* If wolfCrypt isn't built with ECC, force ECC off. */ +#ifdef WOLFSSH_NO_ECDSA + /* If wolfCrypt isn't built with ECDSA, force ECC off. */ userEcc = 0; peerEcc = 0; #endif (void)userEcc; + /* Only load_key() reads it, and that is RSA/ECDSA only. */ + (void)peerEcc; if (wolfSSH_Init() != WS_SUCCESS) { ES_ERROR("Couldn't initialize wolfSSH.\n"); @@ -4130,6 +4145,7 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) #endif if (loadDefaultHostKeys) { + #if !defined(WOLFSSH_NO_RSA) || !defined(WOLFSSH_NO_ECDSA) bufSz = load_key(peerEcc, keyLoadBuf, bufSz); if (bufSz == 0) { #ifdef WOLFSSH_SMALL_STACK @@ -4147,7 +4163,7 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) ES_ERROR("Couldn't use first key buffer.\n"); } - #if !defined(WOLFSSH_NO_RSA) && !defined(WOLFSSH_NO_ECC) + #if !defined(WOLFSSH_NO_RSA) && !defined(WOLFSSH_NO_ECDSA) peerEcc = !peerEcc; bufSz = EXAMPLE_KEYLOAD_BUFFER_SZ; @@ -4168,6 +4184,7 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) ES_ERROR("Couldn't use second key buffer.\n"); } #endif + #endif /* !WOLFSSH_NO_RSA || !WOLFSSH_NO_ECDSA */ #ifndef WOLFSSH_NO_ED25519 bufSz = EXAMPLE_KEYLOAD_BUFFER_SZ; @@ -4300,8 +4317,15 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) keyLoadBuf[bufSz] = 0; LoadPasswordBuffer(keyLoadBuf, bufSz, &pwMapList); + #if defined(WOLFSSH_NO_RSA) && defined(WOLFSSH_NO_ECDSA) + /* Ed25519 is the only sample key left, so -e has nothing to pick + * between and is ignored. */ + #ifndef WOLFSSH_NO_ED25519 + bufName = samplePublicKeyEd25519Buffer; + #endif + #else if (userEcc) { - #ifndef WOLFSSH_NO_ECC + #ifndef WOLFSSH_NO_ECDSA bufName = samplePublicKeyEccBuffer; #endif } @@ -4314,6 +4338,7 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) #endif #endif } + #endif if (bufName != NULL) { bufSz = (word32)WSTRLEN(bufName); WMEMCPY(keyLoadBuf, bufName, bufSz); diff --git a/keys/gretel-key-ed25519.der b/keys/gretel-key-ed25519.der new file mode 100644 index 000000000..4ee73f8be Binary files /dev/null and b/keys/gretel-key-ed25519.der differ diff --git a/keys/gretel-key-ed25519.pem b/keys/gretel-key-ed25519.pem new file mode 100644 index 000000000..b322e776c --- /dev/null +++ b/keys/gretel-key-ed25519.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIEHo0C0B8ZM9j3mX6D3LLQLp+wHhvXG6eriviX8uBDhc +-----END PRIVATE KEY----- diff --git a/keys/gretel-key-ed25519.pub b/keys/gretel-key-ed25519.pub new file mode 100644 index 000000000..b172de693 --- /dev/null +++ b/keys/gretel-key-ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFD8Bwir++gzNJmif9ooAZdaRisFZjlp9XU2seaec7/m gretel diff --git a/keys/hansel-key-ed25519.der b/keys/hansel-key-ed25519.der new file mode 100644 index 000000000..ee8603afb Binary files /dev/null and b/keys/hansel-key-ed25519.der differ diff --git a/keys/hansel-key-ed25519.pem b/keys/hansel-key-ed25519.pem new file mode 100644 index 000000000..2d2a8432f --- /dev/null +++ b/keys/hansel-key-ed25519.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEICjG6dg3TQxSfl+zTIHoaO7JfK0AraDj4hMGVfEX8Qrw +-----END PRIVATE KEY----- diff --git a/keys/hansel-key-ed25519.pub b/keys/hansel-key-ed25519.pub new file mode 100644 index 000000000..825818260 --- /dev/null +++ b/keys/hansel-key-ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHTSoBZIJBO2V0Jb2OWyMWNbkDd6ReDfKxnrAPlbPuCe hansel diff --git a/keys/include.am b/keys/include.am index e465ee7ec..db10dd306 100644 --- a/keys/include.am +++ b/keys/include.am @@ -16,6 +16,8 @@ EXTRA_DIST+= \ keys/gretel-key-ecc-384.der keys/gretel-key-ecc-384.pem keys/gretel-key-ecc-384.pub \ keys/gretel-key-ecc-521.der keys/gretel-key-ecc-521.pem keys/gretel-key-ecc-521.pub \ keys/gretel-key-rsa.der keys/gretel-key-rsa.pem keys/gretel-key-rsa.pub \ + keys/hansel-key-ed25519.der keys/hansel-key-ed25519.pem keys/hansel-key-ed25519.pub \ + keys/gretel-key-ed25519.der keys/gretel-key-ed25519.pem keys/gretel-key-ed25519.pub \ keys/pubkeys-ecc.txt keys/pubkeys-ecc-384.txt keys/pubkeys-ecc-521.txt \ keys/pubkeys-rsa.txt keys/passwd.txt keys/ca-cert-ecc.der \ keys/ca-cert-ecc.pem keys/ca-key-ecc.der keys/ca-key-ecc.pem \ diff --git a/src/internal.c b/src/internal.c index 77382f50c..b2f7ebd85 100644 --- a/src/internal.c +++ b/src/internal.c @@ -6400,7 +6400,10 @@ int wcPrimeForId(byte id) return ECC_CURVE_INVALID; } } +#endif /* !WOLFSSH_NO_ECDSA || !WOLFSSH_NO_ECDH */ + +#ifndef WOLFSSH_NO_ECDSA static INLINE const char *PrimeNameForId(byte id) { switch (id) { @@ -9228,10 +9231,14 @@ static int ValidateKexDhGexGroup(const byte* primeGroup, word32 primeGroupSz, } } - /* Safe prime check: q = (p - 1) / 2 must also be prime. */ + /* Safe prime check: q = (p - 1) / 2 must also be prime. mp_rshb() rather + * than mp_div_2(): the latter is an ECC-only entry point in SP math, and + * q is positive here, so the shift is the same operation. */ if (ret == WS_SUCCESS) { - if (mp_sub_d(&p, 1, &q) != MP_OKAY || mp_div_2(&q, &q) != MP_OKAY) + if (mp_sub_d(&p, 1, &q) != MP_OKAY) ret = WS_CRYPTO_FAILED; + else + mp_rshb(&q, 1); } if (ret == WS_SUCCESS) { isPrime = MP_NO; @@ -11644,6 +11651,9 @@ static int DoUserAuthRequestPublicKey(WOLFSSH* ssh, WS_UserAuthData* authData, } } + /* Only the RSA and ECDSA arms above read the digest size; + * with both compiled out the switch is just the default. */ + WOLFSSH_UNUSED(digestSz); WS_FORCEZERO(digest, sizeof(digest)); } @@ -15732,6 +15742,8 @@ static int SendKexGetSigningKey(WOLFSSH* ssh, heap = ssh->ctx->heap; + /* Only the RSA, ECDSA and ML-DSA arms allocate; Ed25519 does not. */ + WOLFSSH_UNUSED(heap); #ifdef WOLFSSH_TPM ssh->handshake->useTpm = ssh->ctx->privateKey[keyIdx].isTpm; @@ -16049,6 +16061,7 @@ static int SendKexGetSigningKey(WOLFSSH* ssh, sigKeyBlock_ptr->sk.ecc.qSz); } break; + #endif /* WOLFSSH_NO_ECDSA */ #ifndef WOLFSSH_NO_ED25519 case ID_ED25519: @@ -16117,8 +16130,7 @@ static int SendKexGetSigningKey(WOLFSSH* ssh, sigKeyBlock_ptr->sk.ed.q, sigKeyBlock_ptr->sk.ed.qSz); break; - #endif - #endif + #endif /* WOLFSSH_NO_ED25519 */ #ifndef WOLFSSH_NO_MLDSA #ifdef WOLFSSH_CERTS @@ -21285,9 +21297,46 @@ static int PrepareUserAuthRequestEd25519(WOLFSSH* ssh, word32* payloadSz, else #endif { - ret = GetOpenSshKey(keySig, - authData->sf.publicKey.privateKey, - authData->sf.publicKey.privateKeySz, &idx); + int derRet; + + /* As in the RSA and ECDSA paths, try DER first and fall back to + * the OpenSSH container. Only a decode failure falls back; a + * derive failure keeps its own error. */ + derRet = wc_Ed25519PrivateKeyDecode( + authData->sf.publicKey.privateKey, &idx, + &keySig->ks.ed25519.key, + authData->sf.publicKey.privateKeySz); + + if (derRet != 0) { + idx = 0; + ret = GetOpenSshKey(keySig, + authData->sf.publicKey.privateKey, + authData->sf.publicKey.privateKeySz, &idx); + } + else { + ret = WS_SUCCESS; + + if (!keySig->ks.ed25519.key.pubKeySet) { + #ifdef HAVE_ED25519_MAKE_KEY + /* Priv-only DER: derive the public key from the seed, + * the way SendKexGetSigningKey() does for a host key. */ + byte q[ED25519_PUB_KEY_SIZE]; + + ret = wc_ed25519_make_public(&keySig->ks.ed25519.key, + q, (word32)sizeof(q)); + if (ret == 0) { + /* trusted=1: q came from this key's own scalar. */ + ret = wc_ed25519_import_public_ex(q, + ED25519_PUB_KEY_SIZE, + &keySig->ks.ed25519.key, 1); + } + #else + /* Nothing to derive it with; reject here rather than + * failing inside wc_ed25519_sign_msg(). */ + ret = WS_KEY_FORMAT_E; + #endif /* HAVE_ED25519_MAKE_KEY */ + } + } } } diff --git a/src/ssh.c b/src/ssh.c index 2afd0cab4..05e99d372 100644 --- a/src/ssh.c +++ b/src/ssh.c @@ -5494,6 +5494,8 @@ static const char* CurveNameForId(byte id) return "Curve25519"; #endif } +#else + WOLFSSH_UNUSED(id); #endif return ""; } diff --git a/tests/api.c b/tests/api.c index fa14cf980..51e291d08 100644 --- a/tests/api.c +++ b/tests/api.c @@ -461,6 +461,14 @@ static const byte serverKeyEccCurveId = ID_ECDSA_SHA2_NISTP521; #endif #endif +/* ./keys/server-key-ed25519.der */ +#ifndef WOLFSSH_NO_ED25519 +static const char serverKeyEd25519Der[] = + "3050020100300506032b6570042204206a67f30e64ea52fef4ad654d45606138" + "58110784f0039493147b7b331abaf61981200f560c9f7d7a6287f026161931e4" + "b21de9bdee4a7f55ae262da125e4ee4a5100"; +#endif + #ifndef WOLFSSH_NO_RSA static const char serverKeyRsaDer[] = "308204a30201000282010100da5dad2514761559f340fd3cb86230b36dc0f9ec" @@ -515,12 +523,17 @@ static void test_wolfSSH_CTX_UsePrivateKey_buffer(void) #ifndef WOLFSSH_NO_RSA byte* rsaKey; word32 rsaKeySz; +#endif +#ifndef WOLFSSH_NO_ED25519 + byte* ed25519Key; + word32 ed25519KeySz; + word32 ed25519Idx; #endif const byte* lastKey = NULL; word32 lastKeySz = 0; int i; -#ifndef WOLFSSH_NO_ECC +#ifndef WOLFSSH_NO_ECDSA AssertIntEQ(0, ConvertHexToBin(serverKeyEccDer, &eccKey, &eccKeySz, NULL, NULL, NULL, @@ -534,6 +547,13 @@ static void test_wolfSSH_CTX_UsePrivateKey_buffer(void) NULL, NULL, NULL, NULL, NULL, NULL)); #endif +#ifndef WOLFSSH_NO_ED25519 + AssertIntEQ(0, + ConvertHexToBin(serverKeyEd25519Der, &ed25519Key, &ed25519KeySz, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL)); +#endif AssertNotNull(ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL)); for (i = 0; i < WOLFSSH_MAX_PVT_KEYS; i++) { @@ -618,6 +638,26 @@ static void test_wolfSSH_CTX_UsePrivateKey_buffer(void) AssertIntNE(lastKeySz, ctx->privateKey[0].keySz); #endif +#ifndef WOLFSSH_NO_ED25519 + /* Ed25519 may land in any slot, so track the index rather than + * assuming 0. In an Ed25519-only build this is the only key the test + * loads successfully. */ + ed25519Idx = ctx->privateKeyCount; + lastKey = ctx->privateKey[ed25519Idx].key; + lastKeySz = ctx->privateKey[ed25519Idx].keySz; + + AssertIntEQ(WS_SUCCESS, + wolfSSH_CTX_UsePrivateKey_buffer(ctx, ed25519Key, ed25519KeySz, + TEST_GOOD_FORMAT_ASN1)); + AssertIntEQ(ed25519Idx + 1, ctx->privateKeyCount); + AssertNotNull(ctx->privateKey[ed25519Idx].key); + AssertIntNE(0, ctx->privateKey[ed25519Idx].keySz); + AssertIntEQ(ID_ED25519, ctx->privateKey[ed25519Idx].publicKeyFmt); + + AssertIntEQ(0, (lastKey == ctx->privateKey[ed25519Idx].key)); + AssertIntNE(lastKeySz, ctx->privateKey[ed25519Idx].keySz); +#endif + /* Add the same keys again. This should succeed. */ #if !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256) || \ !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP384) || \ @@ -631,6 +671,11 @@ static void test_wolfSSH_CTX_UsePrivateKey_buffer(void) wolfSSH_CTX_UsePrivateKey_buffer(ctx, rsaKey, rsaKeySz, TEST_GOOD_FORMAT_ASN1)); #endif +#ifndef WOLFSSH_NO_ED25519 + AssertIntEQ(WS_SUCCESS, + wolfSSH_CTX_UsePrivateKey_buffer(ctx, ed25519Key, ed25519KeySz, + TEST_GOOD_FORMAT_ASN1)); +#endif wolfSSH_CTX_free(ctx); #if !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256) || \ @@ -641,6 +686,9 @@ static void test_wolfSSH_CTX_UsePrivateKey_buffer(void) #ifndef WOLFSSH_NO_RSA FreeBins(rsaKey, NULL, NULL, NULL); #endif +#ifndef WOLFSSH_NO_ED25519 + FreeBins(ed25519Key, NULL, NULL, NULL); +#endif #endif /* NO_WOLFSSH_SERVER */ } @@ -7489,6 +7537,8 @@ static void test_wolfSSH_SetAlgoList(void) rawKey = serverKeyEccDer; #elif !defined(WOLFSSH_NO_RSA) rawKey = serverKeyRsaDer; +#elif !defined(WOLFSSH_NO_ED25519) + rawKey = serverKeyEd25519Der; #endif AssertNotNull(rawKey); AssertIntEQ(0, diff --git a/tests/auth.c b/tests/auth.c index b8b63b7b6..01ee216b7 100644 --- a/tests/auth.c +++ b/tests/auth.c @@ -216,7 +216,21 @@ static int load_key(byte isEcc, byte* buf, word32 bufSz) { word32 sz = 0; +#if defined(WOLFSSH_NO_RSA) && defined(WOLFSSH_NO_ECDSA) && \ + !defined(WOLFSSH_NO_ED25519) + /* Neither key this picks between is compiled in; Ed25519 is what is + * left, and the server threads need some host key to offer. */ + (void)isEcc; #ifndef NO_FILESYSTEM + sz = load_file("./keys/server-key-ed25519.der", buf, &bufSz); +#else + if ((word32)sizeof_ed25519_key_der_ssh > bufSz) { + return 0; + } + WMEMCPY(buf, ed25519_key_der_ssh, sizeof_ed25519_key_der_ssh); + sz = (word32)sizeof_ed25519_key_der_ssh; +#endif +#elif !defined(NO_FILESYSTEM) const char* bufName; bufName = isEcc ? ECC_PATH : "./keys/server-key-rsa.der"; sz = load_file(bufName, buf, &bufSz); @@ -365,7 +379,7 @@ static const unsigned int hanselPrivateRsaSz = (unsigned int)sizeof(hanselPrivat #endif /* WOLFSSH_NO_RSA */ /* Hansel's ECC keypair */ -#ifndef WOLFSSH_NO_ECC +#ifndef WOLFSSH_NO_ECDSA #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256 static const char* hanselPublicEcc = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAA" @@ -438,7 +452,24 @@ static const unsigned int hanselPrivateEccSz = (unsigned int)sizeof(hanselPrivat #else #error "Enable nistp256, nistp384, nistp521, or disable ECC." #endif -#endif /* WOLFSSH_NO_ECC */ +#endif /* WOLFSSH_NO_ECDSA */ + +#if !defined(WOLFSSH_NO_ED25519) && defined(HAVE_ED25519) && \ + defined(HAVE_ED25519_MAKE_KEY) && defined(HAVE_ED25519_KEY_EXPORT) +static const char* hanselPublicEd25519 = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHTSoBZIJBO2V0Jb2OWyMWNbkD" + "d6ReDfKxnrAPlbPuCe hansel"; +/* PKCS#8 Ed25519 private key with no public key attribute, the pair to + * hanselPublicEd25519. */ +static const byte hanselPrivateEd25519[] = { + 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, + 0x04, 0x22, 0x04, 0x20, 0x28, 0xc6, 0xe9, 0xd8, 0x37, 0x4d, 0x0c, 0x52, + 0x7e, 0x5f, 0xb3, 0x4c, 0x81, 0xe8, 0x68, 0xee, 0xc9, 0x7c, 0xad, 0x00, + 0xad, 0xa0, 0xe3, 0xe2, 0x13, 0x06, 0x55, 0xf1, 0x17, 0xf1, 0x0a, 0xf0 +}; +static const unsigned int hanselPrivateEd25519Sz = + (unsigned int)sizeof(hanselPrivateEd25519); +#endif /* !WOLFSSH_NO_ED25519 && HAVE_ED25519_MAKE_KEY */ /* Server context: SHA256 hash of the authorized key/cert, and optional CA * cert for cert-based auth (NULL/0 for plain pubkey tests). */ @@ -677,12 +708,15 @@ static int run_pubkey_test_ex(PubkeyServerCtx* sCtx, PubkeyClientCtx* cCtx, return WS_SUCCESS; } -/* Existing callers all want the default fixture host key. */ +/* Existing callers all want the default fixture host key, and every one of + * them is an RSA or ECDSA test. */ +#if !defined(WOLFSSH_NO_RSA) || !defined(WOLFSSH_NO_ECDSA) static int run_pubkey_test(PubkeyServerCtx* sCtx, PubkeyClientCtx* cCtx, int expect) { return run_pubkey_test_ex(sCtx, cCtx, expect, NULL, 0); } +#endif #ifndef WOLFSSH_NO_RSA static void test_pubkey_auth_rsa(void) @@ -1222,7 +1256,7 @@ static void test_pubkey_auth_rsacert_bad_sig(void) #endif /* WOLFSSH_CERTS && !WOLFSSH_NO_RSA && WOLFSSH_NO_SHA1_SOFT_DISABLE && !WOLFSSH_NO_SSH_RSA_SHA1 */ -#ifndef WOLFSSH_NO_ECC +#ifndef WOLFSSH_NO_ECDSA static void test_pubkey_auth_ecc(void) { PubkeyServerCtx sCtx = {0}; @@ -1320,9 +1354,9 @@ static void test_pubkey_auth_ecc_bad_sig(void) run_pubkey_test(&sCtx, &cCtx, WS_FATAL_ERROR); } -#endif /* WOLFSSH_NO_ECC */ +#endif /* WOLFSSH_NO_ECDSA */ -#if !defined(WOLFSSH_NO_RSA) && !defined(WOLFSSH_NO_ECC) +#if !defined(WOLFSSH_NO_RSA) && !defined(WOLFSSH_NO_ECDSA) /* Negative test: server authorises the RSA key but client presents the ECC key. * The unauthorised key must be rejected. */ @@ -1376,7 +1410,7 @@ static void test_pubkey_auth_wrong_key(void) * wrap inner errors as WS_FATAL_ERROR at the API boundary */ run_pubkey_test(&sCtx, &cCtx, WS_FATAL_ERROR); } -#endif /* !WOLFSSH_NO_RSA && !WOLFSSH_NO_ECC */ +#endif /* !WOLFSSH_NO_RSA && !WOLFSSH_NO_ECDSA */ #if !defined(WOLFSSH_NO_MLDSA) && !defined(WOLFSSH_NO_MLDSA44) && \ defined(WOLFSSL_MLDSA_PRIVATE_KEY) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ @@ -1420,11 +1454,12 @@ static void test_pubkey_load_mldsa_privonly_hostkey(void) * && !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) + defined(HAVE_ED25519_MAKE_KEY) && defined(HAVE_ED25519_KEY_EXPORT) /* 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. */ + * Ed25519 host key to ensure wolfSSH correctly derives the public key. + * The user key is Ed25519 as well, so the whole exchange runs on a build + * with neither RSA nor ECDSA compiled in. */ static void test_pubkey_auth_ed25519_privonly_hostkey(void) { PubkeyServerCtx sCtx = {0}; @@ -1457,14 +1492,14 @@ static void test_pubkey_auth_ed25519_privonly_hostkey(void) wc_ed25519_free(&edKey); AssertIntGT(hostKeyDerSz, 0); - AssertIntEQ(wolfSSH_ReadKey_buffer((const byte*)hanselPublicEcc, - (word32)WSTRLEN(hanselPublicEcc), WOLFSSH_FORMAT_SSH, + AssertIntEQ(wolfSSH_ReadKey_buffer((const byte*)hanselPublicEd25519, + (word32)WSTRLEN(hanselPublicEd25519), 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, + AssertIntEQ(wolfSSH_ReadKey_buffer(hanselPrivateEd25519, + hanselPrivateEd25519Sz, WOLFSSH_FORMAT_ASN1, &privKeyPtr, &privKeySz, &privKeyType, &privKeyTypeSz, NULL), WS_SUCCESS); @@ -1478,7 +1513,7 @@ static void test_pubkey_auth_ed25519_privonly_hostkey(void) run_pubkey_test_ex(&sCtx, &cCtx, WS_SUCCESS, hostKeyDer, (word32)hostKeyDerSz); } -#endif /* !WOLFSSH_NO_ED25519 && HAVE_ED25519_MAKE_KEY && !WOLFSSH_NO_ECDSA */ +#endif /* !WOLFSSH_NO_ED25519 && HAVE_ED25519_MAKE_KEY */ #if !defined(WOLFSSH_NO_ECDSA) && !defined(WOLFSSH_NO_RSA) && \ defined(HAVE_ECC_KEY_EXPORT) @@ -2445,16 +2480,15 @@ int wolfSSH_AuthTest(int argc, char** argv) test_pubkey_auth_rsacert_bad_sig(); #endif #endif -#ifndef WOLFSSH_NO_ECC +#ifndef WOLFSSH_NO_ECDSA test_pubkey_auth_ecc(); test_pubkey_auth_ecc_bad_sig(); #endif -#if !defined(WOLFSSH_NO_RSA) && !defined(WOLFSSH_NO_ECC) +#if !defined(WOLFSSH_NO_RSA) && !defined(WOLFSSH_NO_ECDSA) 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) + defined(HAVE_ED25519_MAKE_KEY) && defined(HAVE_ED25519_KEY_EXPORT) test_pubkey_auth_ed25519_privonly_hostkey(); #endif #if !defined(WOLFSSH_NO_ECDSA) && !defined(WOLFSSH_NO_RSA) && \ diff --git a/tests/unit.c b/tests/unit.c index e8ddc1d2a..29292e1dd 100644 --- a/tests/unit.c +++ b/tests/unit.c @@ -42,6 +42,9 @@ #include #include #endif +/* The Ed25519 key DER helpers arrive with asn.h, but that include is RSA-only + * and the Ed25519 tests do not need RSA. */ +#include #define WOLFSSH_TEST_HEX2BIN #include @@ -56,7 +59,6 @@ defined(WOLFSSL_CERT_GEN) && !defined(WOLFSSH_NO_ECDSA) && \ !defined(NO_FILESYSTEM) #define WOLFSSH_TEST_CERTMAN_PROMOTE - #include #include #endif