diff --git a/src/ssh.c b/src/ssh.c index 09bf297fe..2481df3c2 100644 --- a/src/ssh.c +++ b/src/ssh.c @@ -3083,6 +3083,15 @@ static int ReadFileIntoBuffer(const char* name, byte** out, word32* outSz, } +/* Compares only the first bytes of in, not the secret key body after them. */ +static int KeyFileHasPrefix(const byte* in, word32 inSz, const char* prefix) +{ + word32 prefixSz = (word32)WSTRLEN(prefix); + + return inSz >= prefixSz && WMEMCMP(in, prefix, prefixSz) == 0; +} + + /* Reads a key from the file name into a buffer. An SSH algorithm name marks an SSH format public key and "-----BEGIN " a PEM one, otherwise it is an ASN.1 private key. The buffer goes to wolfSSH_ReadKey_buffer_ex(). */ @@ -3104,24 +3113,21 @@ int wolfSSH_ReadKey_file(const char* name, ret = ReadFileIntoBuffer(name, &in, &inSz, heap); if (ret == WS_SUCCESS) { - if (WSTRNSTR((const char*)in, "ssh-rsa", inSz) == (const char*)in - || WSTRNSTR((const char*)in, - "ecdsa-sha2-nistp", inSz) == (const char*)in - || WSTRNSTR((const char*)in, - "ssh-ed25519", inSz) == (const char*)in - || WSTRNSTR((const char*)in, - "ssh-mldsa-", inSz) == (const char*)in - || WSTRNSTR((const char*)in, - "x509v3-ssh-mldsa-", inSz) == (const char*)in) { + if (KeyFileHasPrefix(in, inSz, "ssh-rsa") + || KeyFileHasPrefix(in, inSz, "ecdsa-sha2-nistp") + || KeyFileHasPrefix(in, inSz, "ssh-ed25519") + || KeyFileHasPrefix(in, inSz, "ssh-mldsa") + || KeyFileHasPrefix(in, inSz, "x509v3-ssh-mldsa-")) { *isPrivate = 0; format = WOLFSSH_FORMAT_SSH; } - else if (WSTRNSTR((const char*)in, PrivBeginOpenSSH, inSz) != NULL) { + else if (KeyFileHasPrefix(in, inSz, PrivBeginOpenSSH)) { *isPrivate = 1; format = WOLFSSH_FORMAT_OPENSSH; } - else if ((WSTRNSTR((const char*)in, PrivBeginPrefix, inSz) - == (const char*)in) + /* The suffix is searched for, not anchored: an EC PARAMETERS block + * can come before the private key. */ + else if (KeyFileHasPrefix(in, inSz, PrivBeginPrefix) && (WSTRNSTR((const char*)in, PrivSuffix, inSz) != NULL)) { *isPrivate = 1; diff --git a/tests/api.c b/tests/api.c index 8a7fe35e0..15dd8d5f9 100644 --- a/tests/api.c +++ b/tests/api.c @@ -2887,6 +2887,63 @@ static void test_wolfSSH_ReadKey_sshNoComment(void) } +/* wolfSSH_ReadKey_file() must take every ML-DSA public key name as an SSH + * format public key, including the composite ones such as + * "ssh-mldsa44-es256@wolfssl.com", which have no '-' after "ssh-mldsa". */ +static void test_wolfSSH_ReadKey_file_mldsaPub(void) +{ +#if !defined(NO_FILESYSTEM) && !defined(NO_WRITE_TEMP_FILES) && \ + !defined(WOLFSSH_USER_FILESYSTEM) +#ifdef WOLFSSH_ZEPHYR + static const char path[] = + CONFIG_WOLFSSH_SFTP_DEFAULT_DIR "/readkey-mldsa-test.pub"; +#else + static const char path[] = "./readkey-mldsa-test.pub"; +#endif + static const char* keys[] = { + "ssh-mldsa44-es256@wolfssl.com AAAAB3NzaC1yc2E= comment\n", + "ssh-mldsa65-ed25519@wolfssl.com AAAAB3NzaC1yc2E= comment\n", + "ssh-mldsa87-es384@wolfssl.com AAAAB3NzaC1yc2E= comment\n", + "ssh-mldsa44-ed25519@openssh.com AAAAB3NzaC1yc2E= comment\n", + "ssh-mldsa-44 AAAAB3NzaC1yc2E= comment\n" + }; + WFILE* fp = NULL; + byte* out = NULL; + const byte* outType = NULL; + word32 outSz = 0; + word32 outTypeSz = 0; + word32 i; + word32 keySz; + byte isPrivate; + + for (i = 0; i < (word32)(sizeof(keys) / sizeof(keys[0])); i++) { + keySz = (word32)WSTRLEN(keys[i]); + AssertIntEQ(WFOPEN(NULL, &fp, path, "wb"), 0); + AssertNotNull(fp); + AssertIntEQ((int)WFWRITE(NULL, keys[i], 1, keySz, fp), (int)keySz); + WFCLOSE(NULL, fp); + + out = NULL; + outSz = 0; + isPrivate = 1; + AssertIntEQ(wolfSSH_ReadKey_file(path, &out, &outSz, + &outType, &outTypeSz, &isPrivate, NULL), WS_SUCCESS); + AssertIntEQ(isPrivate, 0); + AssertNotNull(out); + AssertIntGT(outSz, 0); + WFREE(out, NULL, DYNTYPE_PRIVKEY); + + /* Zephyr's "w" open does not truncate, so start each key afresh. */ +#ifdef WOLFSSH_ZEPHYR + AssertIntEQ(0, fs_unlink(path)); +#else + AssertIntEQ(0, remove(path)); +#endif + } +#endif +} + + #ifdef WOLFSSH_SCP static int my_ScpRecv(WOLFSSH* ssh, int state, const char* basePath, @@ -9355,6 +9412,7 @@ int wolfSSH_ApiTest(int argc, char** argv) test_wolfSSH_ReadKey_shortBuffer(); test_wolfSSH_ReadKey_noTrailingNewline(); test_wolfSSH_ReadKey_sshNoComment(); + test_wolfSSH_ReadKey_file_mldsaPub(); test_wolfSSH_QueryAlgoList(); test_wolfSSH_SetMaxAuthAttempts(); test_wolfSSH_StrictKex();