diff --git a/apps/wolfsshd/auth.c b/apps/wolfsshd/auth.c index d79d21508..efca99b51 100644 --- a/apps/wolfsshd/auth.c +++ b/apps/wolfsshd/auth.c @@ -835,7 +835,6 @@ static int SearchForPubKey(const char* path, const char* authKeysFile, if (f != WBADFILE) { WFCLOSE(NULL, f); } - if (lineBuf != NULL) { WFREE(lineBuf, NULL, DYNTYPE_BUFFER); } @@ -882,19 +881,23 @@ static int CheckPublicKeyUnix(const char* name, if (pubKeyCtx->isOsshCert) { int rc; byte* caKey = NULL; - word32 caKeySz; + word32 caKeySz = 0; const byte* caKeyType = NULL; - word32 caKeyTypeSz; + word32 caKeyTypeSz = 0; byte fingerprint[WC_SHA256_DIGEST_SIZE]; - - if (pubKeyCtx->caKey == NULL || - pubKeyCtx->caKeySz != WC_SHA256_DIGEST_SIZE) { + WFILE* f = WBADFILE; + char* lineBuf = NULL; + char* current = NULL; + word32 currentSz = 0; + int foundKey = 0; + + if (pubKeyCtx->caKeyHash == NULL || + pubKeyCtx->caKeyHashSz != WC_SHA256_DIGEST_SIZE) { ret = WS_FATAL_ERROR; } if (ret == WSSHD_AUTH_SUCCESS) { - f = XFOPEN(usrCaKeysFile, "rb"); - if (f == XBADFILE) { + if (WFOPEN(NULL, &f, usrCaKeysFile, "rb") != 0) { wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Unable to open %s", usrCaKeysFile); ret = WS_BAD_FILE_E; @@ -907,8 +910,8 @@ static int CheckPublicKeyUnix(const char* name, } } while (ret == WSSHD_AUTH_SUCCESS && - (current = XFGETS(lineBuf, MAX_LINE_SZ, f)) != NULL) { - currentSz = (word32)XSTRLEN(current); + (current = WFGETS(lineBuf, MAX_LINE_SZ, f)) != NULL) { + currentSz = (word32)WSTRLEN(current); /* remove leading spaces */ while (currentSz > 0 && current[0] == ' ') { @@ -924,19 +927,41 @@ static int CheckPublicKeyUnix(const char* name, continue; /* commented out line */ } + if (caKey != NULL) { + WFREE(caKey, NULL, DYNTYPE_PRIVKEY); + caKey = NULL; + caKeySz = 0; + caKeyType = NULL; + caKeyTypeSz = 0; + } + rc = wolfSSH_ReadKey_buffer((const byte*)current, currentSz, WOLFSSH_FORMAT_SSH, &caKey, &caKeySz, &caKeyType, &caKeyTypeSz, NULL); if (rc == WS_SUCCESS) { rc = wc_Hash(WC_HASH_TYPE_SHA256, caKey, caKeySz, fingerprint, WC_SHA256_DIGEST_SIZE); - if (rc == 0 && ConstantCompare(fingerprint, pubKeyCtx->caKey, + if (rc == 0 && ConstantCompare(fingerprint, pubKeyCtx->caKeyHash, WC_SHA256_DIGEST_SIZE) == 0) { foundKey = 1; break; } } } + + if (caKey != NULL) { + WFREE(caKey, NULL, DYNTYPE_PRIVKEY); + } + if (f != WBADFILE) { + WFCLOSE(NULL, f); + } + if (lineBuf != NULL) { + WFREE(lineBuf, NULL, DYNTYPE_BUFFER); + } + + if (ret == WSSHD_AUTH_SUCCESS && !foundKey) { + ret = WSSHD_AUTH_FAILURE; + } } else #endif /* WOLFSSH_OSSH_CERTS */ @@ -1626,9 +1651,9 @@ static int RequestAuthentication(WS_UserAuthData* authData, * closed: require AuthorizedKeysFile (per-user key/cert mapping) * or a wolfSSL build with FPKI. */ wolfSSH_Log(WS_LOG_ERROR, - "[SSHD] Certificate authentication cannot bind the requested " - "user without FPKI or AuthorizedKeysFile; rejecting " - "(user=%s)", usr); + "[SSHD] Certificate authentication cannot bind " + "the requested user without FPKI or " + "AuthorizedKeysFile; rejecting (user=%s)", usr); ret = WOLFSSH_USERAUTH_REJECTED; #endif } @@ -2249,4 +2274,19 @@ WOLFSSHD_CONFIG* wolfSSHD_AuthGetUserConf(const WOLFSSHD_AUTH* auth, } return ret; } +#if defined(WOLFSSHD_UNIT_TEST) && defined(WOLFSSH_OSSH_CERTS) +/* Expose the OSSH CA-fingerprint matching path of CheckPublicKeyUnix for + * unit testing. caKeyHash must be a SHA-256 digest of the raw public key. */ +int wolfSSHD_TestCheckOsshCertCa(const byte* caKeyHash, + word32 caKeyHashSz, + const char* caKeysFile) +{ + WS_UserAuthData_PublicKey pk; + WMEMSET(&pk, 0, sizeof(pk)); + pk.isOsshCert = 1; + pk.caKeyHash = caKeyHash; + pk.caKeyHashSz = caKeyHashSz; + return CheckPublicKeyUnix(NULL, &pk, caKeysFile, NULL, NULL); +} +#endif /* WOLFSSHD_UNIT_TEST && WOLFSSH_OSSH_CERTS */ #endif /* WOLFSSH_SSHD */ diff --git a/apps/wolfsshd/auth.h b/apps/wolfsshd/auth.h index c487a68ca..347225b6a 100644 --- a/apps/wolfsshd/auth.h +++ b/apps/wolfsshd/auth.h @@ -101,5 +101,10 @@ int CheckAuthKeysLine(char* line, word32 lineSz, const byte* key, word32 keySz); int CAKeysFileDiffers(const char* a, const char* b); int wolfSSHD_GetUserAuthTypes(const WOLFSSHD_CONFIG* usrConf); +#if defined(WOLFSSHD_UNIT_TEST) && defined(WOLFSSH_OSSH_CERTS) +int wolfSSHD_TestCheckOsshCertCa(const byte* caKeyHash, + word32 caKeyHashSz, + const char* caKeysFile); +#endif #endif #endif /* WOLFAUTH_H */ diff --git a/apps/wolfsshd/test/test_configuration.c b/apps/wolfsshd/test/test_configuration.c index aa5c410de..bf74b7fd8 100644 --- a/apps/wolfsshd/test/test_configuration.c +++ b/apps/wolfsshd/test/test_configuration.c @@ -23,6 +23,7 @@ #endif #include +#include #include #include #include @@ -1900,6 +1901,159 @@ static int test_OpenSecureFile(void) } #endif /* !_WIN32 */ +#if defined(WOLFSSH_OSSH_CERTS) && !defined(NO_SHA256) +#include +static int test_CheckOsshCertCa(void) +{ + int ret = WS_SUCCESS; + int rc; + /* ECC-P256 public key in OpenSSH format used as a stand-in CA key. */ + static const char caKeyStr[] = + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAA" + "BBBNkI5JTP6D0lF42tbxX19cE87hztUS6FSDoGvPfiU0CgeNSbI+aFdKIzTP5CQEJSvm25" + "qUzgDtH7oyaQROUnNvk= hansel"; + static const char caKeyFile[] = "./test_ossh_ca_keys.txt"; + byte fingerprint[WC_SHA256_DIGEST_SIZE]; + byte wrongHash[WC_SHA256_DIGEST_SIZE]; + byte* caKeyRaw = NULL; + word32 caKeyRawSz = 0; + const byte* caKeyType = NULL; + word32 caKeyTypeSz = 0; + WFILE* f = WBADFILE; + + Log("test_CheckOsshCertCa\n"); + + rc = wolfSSH_ReadKey_buffer((const byte*)caKeyStr, + (word32)WSTRLEN(caKeyStr), + WOLFSSH_FORMAT_SSH, &caKeyRaw, &caKeyRawSz, + &caKeyType, &caKeyTypeSz, NULL); + if (rc != WS_SUCCESS) { + Log(" Failed to parse CA key: %d\n", rc); + return WS_FATAL_ERROR; + } + rc = wc_Sha256Hash(caKeyRaw, caKeyRawSz, fingerprint); + WFREE(caKeyRaw, NULL, DYNTYPE_PRIVKEY); + if (rc != 0) { + Log(" Failed to hash CA key: %d\n", rc); + return WS_FATAL_ERROR; + } + + /* Write a one-line CA keys file. */ + if (WFOPEN(NULL, &f, caKeyFile, "w") != 0) { + Log(" Failed to create CA keys file\n"); + return WS_FATAL_ERROR; + } + WFWRITE(NULL, caKeyStr, sizeof(char), WSTRLEN(caKeyStr), f); + WFWRITE(NULL, "\n", sizeof(char), 1, f); + WFCLOSE(NULL, f); + + Log(" Testing: matching CA fingerprint."); + rc = wolfSSHD_TestCheckOsshCertCa(fingerprint, WC_SHA256_DIGEST_SIZE, + caKeyFile); + if (rc == WSSHD_AUTH_SUCCESS) { + Log(" PASSED.\n"); + } + else { + Log(" FAILED (rc=%d).\n", rc); + ret = WS_FATAL_ERROR; + } + + if (ret == WS_SUCCESS) { + WMEMSET(wrongHash, 0xBB, WC_SHA256_DIGEST_SIZE); + Log(" Testing: non-matching CA fingerprint."); + rc = wolfSSHD_TestCheckOsshCertCa(wrongHash, WC_SHA256_DIGEST_SIZE, + caKeyFile); + if (rc == WSSHD_AUTH_FAILURE) { + Log(" PASSED.\n"); + } + else { + Log(" FAILED (rc=%d).\n", rc); + ret = WS_FATAL_ERROR; + } + } + + /* Empty file: no matching key fails with WSSHD_AUTH_FAILURE */ + if (ret == WS_SUCCESS && WFOPEN(NULL, &f, caKeyFile, "w") == 0) { + WFCLOSE(NULL, f); + Log(" Testing: empty CA keys file."); + rc = wolfSSHD_TestCheckOsshCertCa(fingerprint, WC_SHA256_DIGEST_SIZE, + caKeyFile); + if (rc == WSSHD_AUTH_FAILURE) { + Log(" PASSED.\n"); + } + else { + Log(" FAILED (rc=%d).\n", rc); + ret = WS_FATAL_ERROR; + } + } + + /* Comment-only file: no matching key fails with WSSHD_AUTH_FAILURE */ + if (ret == WS_SUCCESS && WFOPEN(NULL, &f, caKeyFile, "w") == 0) { + static const char comment[] = "# trusted CAs\n"; + WFWRITE(NULL, comment, sizeof(char), WSTRLEN(comment), f); + WFCLOSE(NULL, f); + Log(" Testing: comment-only CA keys file."); + rc = wolfSSHD_TestCheckOsshCertCa(fingerprint, WC_SHA256_DIGEST_SIZE, + caKeyFile); + if (rc == WSSHD_AUTH_FAILURE) { + Log(" PASSED.\n"); + } + else { + Log(" FAILED (rc=%d).\n", rc); + ret = WS_FATAL_ERROR; + } + } + + WREMOVE(0, caKeyFile); + return ret; +} +static int test_CheckOsshCertCa_Malformed(void) { + int ret = WS_SUCCESS; + int rc; + static const char caKeyStr[] = + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAA" + "BBBNkI5JTP6D0lF42tbxX19cE87hztUS6FSDoGvPfiU0CgeNSbI+aFdKIzTP5CQEJSvm25" + "qUzgDtH7oyaQROUnNvk= hansel"; + static const char caKeyFile[] = "./test_ossh_ca_keys_malformed.txt"; + static const char badLine[] = "not-a-valid-key\n"; + byte fingerprint[WC_SHA256_DIGEST_SIZE]; + byte* caKeyRaw = NULL; + word32 caKeyRawSz = 0; + const byte* caKeyType = NULL; + word32 caKeyTypeSz = 0; + WFILE* f = WBADFILE; + + rc = wolfSSH_ReadKey_buffer((const byte*)caKeyStr, + (word32)WSTRLEN(caKeyStr), + WOLFSSH_FORMAT_SSH, &caKeyRaw, &caKeyRawSz, + &caKeyType, &caKeyTypeSz, NULL); + if (rc != WS_SUCCESS) { return WS_FATAL_ERROR; } + rc = wc_Sha256Hash(caKeyRaw, caKeyRawSz, fingerprint); + WFREE(caKeyRaw, NULL, DYNTYPE_PRIVKEY); + if (rc != 0) { return WS_FATAL_ERROR; } + + /* write malformed line */ + if (WFOPEN(NULL, &f, caKeyFile, "w") != 0) { + return WS_FATAL_ERROR; + } + WFWRITE(NULL, badLine, sizeof(char), WSTRLEN(badLine), f); + WFCLOSE(NULL, f); + + Log(" Testing: malformed key line CA keys file."); + rc = wolfSSHD_TestCheckOsshCertCa(fingerprint, WC_SHA256_DIGEST_SIZE, + caKeyFile); + if (rc == WSSHD_AUTH_FAILURE) { + Log(" PASSED.\n"); + } else { + Log(" FAILED (rc=%d).\n", rc); + ret = WS_FATAL_ERROR; + } + + WREMOVE(0, caKeyFile); + return ret; +} +#endif /* WOLFSSH_OSSH_CERTS && !NO_SHA256 */ + const TEST_CASE testCases[] = { TEST_DECL(test_ConfigDefaults), TEST_DECL(test_ParseConfigLine), @@ -1933,6 +2087,10 @@ const TEST_CASE testCases[] = { #if defined(WOLFSSH_HAVE_LIBCRYPT) || defined(WOLFSSH_HAVE_LIBLOGIN) TEST_DECL(test_CheckPasswordHashUnix), #endif +#if defined(WOLFSSH_OSSH_CERTS) && !defined(NO_SHA256) + TEST_DECL(test_CheckOsshCertCa), + TEST_DECL(test_CheckOsshCertCa_Malformed), +#endif }; int main(int argc, char** argv) diff --git a/wolfssh/ssh.h b/wolfssh/ssh.h index 6ce19aa9d..f5322827a 100644 --- a/wolfssh/ssh.h +++ b/wolfssh/ssh.h @@ -384,6 +384,11 @@ typedef struct WS_UserAuthData_PublicKey { const byte* signature; word32 signatureSz; byte isCert:1; +#ifdef WOLFSSH_OSSH_CERTS + byte isOsshCert:1; + const byte* caKeyHash; + word32 caKeyHashSz; +#endif } WS_UserAuthData_PublicKey; typedef struct WS_UserAuthData {