Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
688 changes: 688 additions & 0 deletions .github/workflows/windows-cert-store-test.yml

Large diffs are not rendered by default.

322 changes: 321 additions & 1 deletion apps/wolfsshd/configuration.c

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions apps/wolfsshd/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,23 @@ char* wolfSSHD_ConfigGetHostCertFile(const WOLFSSHD_CONFIG* conf);
char* wolfSSHD_ConfigGetUserCAKeysFile(const WOLFSSHD_CONFIG* conf);
int wolfSSHD_ConfigSetHostKeyFile(WOLFSSHD_CONFIG* conf, const char* file);
int wolfSSHD_ConfigSetHostCertFile(WOLFSSHD_CONFIG* conf, const char* file);
#ifdef WOLFSSH_WINDOWS_CERT_STORE
char* wolfSSHD_ConfigGetHostKeyStore(const WOLFSSHD_CONFIG* conf);
char* wolfSSHD_ConfigGetHostKeyStoreSubject(const WOLFSSHD_CONFIG* conf);
char* wolfSSHD_ConfigGetHostKeyStoreFlags(const WOLFSSHD_CONFIG* conf);
#endif /* WOLFSSH_WINDOWS_CERT_STORE */
int wolfSSHD_ConfigSetSystemCA(WOLFSSHD_CONFIG* conf, const char* value);
int wolfSSHD_ConfigGetSystemCA(const WOLFSSHD_CONFIG* conf);
int wolfSSHD_ConfigSetUserCAStore(WOLFSSHD_CONFIG* conf, const char* value);
int wolfSSHD_ConfigGetUserCAStore(const WOLFSSHD_CONFIG* conf);
#ifdef USE_WINDOWS_API
char* wolfSSHD_ConfigGetWinUserStores(WOLFSSHD_CONFIG* conf);
int wolfSSHD_ConfigSetWinUserStores(WOLFSSHD_CONFIG* conf, const char* value);
char* wolfSSHD_ConfigGetWinUserDwFlags(WOLFSSHD_CONFIG* conf);
int wolfSSHD_ConfigSetWinUserDwFlags(WOLFSSHD_CONFIG* conf, const char* value);
char* wolfSSHD_ConfigGetWinUserPvPara(WOLFSSHD_CONFIG* conf);
int wolfSSHD_ConfigSetWinUserPvPara(WOLFSSHD_CONFIG* conf, const char* value);
#endif /* USE_WINDOWS_API */
int wolfSSHD_ConfigSetUserCAKeysFile(WOLFSSHD_CONFIG* conf, const char* file);
word16 wolfSSHD_ConfigGetPort(const WOLFSSHD_CONFIG* conf);
char* wolfSSHD_ConfigGetAuthKeysFile(const WOLFSSHD_CONFIG* conf);
Expand Down
251 changes: 209 additions & 42 deletions apps/wolfsshd/wolfsshd.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@
#include <wolfssl/wolfcrypt/logging.h>
#include <wolfssl/wolfcrypt/asn_public.h>

#ifdef WOLFSSH_WINDOWS_CERT_STORE
#include <windows.h>
#include <wincrypt.h>
#include <ncrypt.h>
#ifndef CERT_SYSTEM_STORE_CURRENT_USER
#define CERT_SYSTEM_STORE_CURRENT_USER 0x00010000
#endif
#ifndef CERT_SYSTEM_STORE_LOCAL_MACHINE
#define CERT_SYSTEM_STORE_LOCAL_MACHINE 0x00020000
#endif
#endif /* WOLFSSH_WINDOWS_CERT_STORE */

#define WOLFSSH_TEST_SERVER
#include <wolfssh/test.h>

Expand Down Expand Up @@ -372,56 +384,131 @@ static int SetupCTX(WOLFSSHD_CONFIG* conf, WOLFSSH_CTX** ctx,

/* Load in host private key */
if (ret == WS_SUCCESS) {
#ifdef WOLFSSH_WINDOWS_CERT_STORE
char* hostKeyStore = wolfSSHD_ConfigGetHostKeyStore(conf);
char* hostKeyStoreSubject = wolfSSHD_ConfigGetHostKeyStoreSubject(conf);
char* hostKeyStoreFlags = wolfSSHD_ConfigGetHostKeyStoreFlags(conf);

char* hostKey = wolfSSHD_ConfigGetHostKeyFile(conf);
wolfSSH_Log(WS_LOG_INFO,
"[SSHD] Cert store code compiled in. "
"hostKeyStore=%s, hostKeyStoreSubject=%s, hostKeyStoreFlags=%s",
hostKeyStore ? hostKeyStore : "(null)",
hostKeyStoreSubject ? hostKeyStoreSubject : "(null)",
hostKeyStoreFlags ? hostKeyStoreFlags : "(null)");

if (hostKeyStore != NULL && hostKeyStoreSubject != NULL) {
/* Use cert store host key */
wchar_t* wStoreName = NULL;
wchar_t* wSubjectName = NULL;
word32 dwFlags = CERT_SYSTEM_STORE_CURRENT_USER;
int storeNameLen, subjectNameLen;

/* Parse flags if provided */
if (hostKeyStoreFlags != NULL) {
if (WSTRCMP(hostKeyStoreFlags, "CURRENT_USER") == 0) {
dwFlags = CERT_SYSTEM_STORE_CURRENT_USER;
} else if (WSTRCMP(hostKeyStoreFlags, "LOCAL_MACHINE") == 0) {
dwFlags = CERT_SYSTEM_STORE_LOCAL_MACHINE;
} else {
dwFlags = (word32)atoi(hostKeyStoreFlags);
}
}

if (hostKey == NULL) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] No host private key set");
ret = WS_BAD_ARGUMENT;
}
else {
byte* data;
word32 dataSz = 0;
/* Convert to wide strings */
storeNameLen = MultiByteToWideChar(CP_UTF8, 0, hostKeyStore, -1, NULL, 0);
subjectNameLen = MultiByteToWideChar(CP_UTF8, 0, hostKeyStoreSubject, -1, NULL, 0);

/* The host private key is a secret trust anchor: refuse a symlink,
* an unsafe owner or path, or a group/world readable/writable
* file. */
data = getBufferFromFile(hostKey, &dataSz, heap,
WOLFSSHD_LOAD_SECRET);
if (data == NULL) {
/* NULL means the secure gate rejected the file (bad owner,
* symlink, group/world writable/readable; reason already
* logged) or the read failed, so report a file error rather
* than a memory error. */
if (storeNameLen == 0 || subjectNameLen == 0) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Error reading host key file.");
ret = WS_BAD_FILE_E;
"[SSHD] Failed to convert cert store strings to wide characters");
ret = WS_BAD_ARGUMENT;
} else {
wStoreName = (wchar_t*)WMALLOC(storeNameLen * sizeof(wchar_t), heap, DYNTYPE_SSHD);
wSubjectName = (wchar_t*)WMALLOC(subjectNameLen * sizeof(wchar_t), heap, DYNTYPE_SSHD);

if (wStoreName == NULL || wSubjectName == NULL) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Memory allocation failed for cert store strings");
ret = WS_MEMORY_E;
} else {
MultiByteToWideChar(CP_UTF8, 0, hostKeyStore, -1, wStoreName, storeNameLen);
MultiByteToWideChar(CP_UTF8, 0, hostKeyStoreSubject, -1, wSubjectName, subjectNameLen);

}
ret = wolfSSH_CTX_UsePrivateKey_fromStore(*ctx, wStoreName, dwFlags, wSubjectName);
if (ret != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Failed to load host key from certificate store");
}
}

if (ret == WS_SUCCESS) {
if (wc_PemToDer(data, dataSz, PRIVATEKEY_TYPE, &der, NULL,
NULL, NULL) != 0) {
wolfSSH_Log(WS_LOG_DEBUG, "[SSHD] Failed to convert host "
"private key from PEM. Assuming key in DER "
"format.");
privBuf = data;
privBufSz = dataSz;
if (wStoreName != NULL) {
WFREE(wStoreName, heap, DYNTYPE_SSHD);
}
else {
privBuf = der->buffer;
privBufSz = der->length;
if (wSubjectName != NULL) {
WFREE(wSubjectName, heap, DYNTYPE_SSHD);
}
}
Comment thread
JacobBarthelmeh marked this conversation as resolved.
} else
#elif defined(WOLFSSH_CERTS)
wolfSSH_Log(WS_LOG_INFO,
"[SSHD] WOLFSSH_WINDOWS_CERT_STORE not defined - cert store support disabled");
#else
wolfSSH_Log(WS_LOG_INFO,
"[SSHD] WOLFSSH_CERTS not defined - cert store support disabled");
#endif /* WOLFSSH_WINDOWS_CERT_STORE */
{
char* hostKey = wolfSSHD_ConfigGetHostKeyFile(conf);

wolfSSH_Log(WS_LOG_INFO,
"[SSHD] File-based host key path entered. hostKey=%s",
hostKey ? hostKey : "(null)");

if (wolfSSH_CTX_UsePrivateKey_buffer(*ctx, privBuf, privBufSz,
WOLFSSH_FORMAT_ASN1) < 0) {
if (hostKey == NULL) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] No host private key set");
ret = WS_BAD_ARGUMENT;
}
else {
byte* data;
word32 dataSz = 0;

/* The host private key is a secret trust anchor: refuse a symlink,
* an unsafe owner or path, or a group/world readable/writable
* file. */
data = getBufferFromFile(hostKey, &dataSz, heap,
WOLFSSHD_LOAD_SECRET);
if (data == NULL) {
/* NULL means the secure gate rejected the file (bad owner,
* symlink, group/world writable/readable; reason already
* logged) or the read failed, so report a file error rather
* than a memory error. */
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Failed to use host private key.");
ret = WS_BAD_ARGUMENT;
"[SSHD] Error reading host key file.");
ret = WS_BAD_FILE_E;

}

freeBufferFromFile(data, heap);
wc_FreeDer(&der);
if (ret == WS_SUCCESS) {
if (wc_PemToDer(data, dataSz, PRIVATEKEY_TYPE, &der, NULL,
NULL, NULL) != 0) {
wolfSSH_Log(WS_LOG_DEBUG, "[SSHD] Failed to convert host "
"private key from PEM. Assuming key in DER "
"format.");
privBuf = data;
privBufSz = dataSz;
}
else {
privBuf = der->buffer;
privBufSz = der->length;
}

if (wolfSSH_CTX_UsePrivateKey_buffer(*ctx, privBuf, privBufSz,
WOLFSSH_FORMAT_ASN1) < 0) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Failed to use host private key.");
ret = WS_BAD_ARGUMENT;
}

freeBufferFromFile(data, heap);
wc_FreeDer(&der);
}
}
}
}
Expand Down Expand Up @@ -475,6 +562,72 @@ static int SetupCTX(WOLFSSHD_CONFIG* conf, WOLFSSH_CTX** ctx,
#endif /* WOLFSSH_OSSH_CERTS || WOLFSSH_CERTS */

#ifdef WOLFSSH_CERTS
/* check if loading in system and/or user CA certs */
#ifdef WOLFSSL_SYS_CA_CERTS
if (ret == WS_SUCCESS && (wolfSSHD_ConfigGetSystemCA(conf)
|| wolfSSHD_ConfigGetUserCAStore(conf))) {
WOLFSSL_CTX* sslCtx;

wolfSSH_Log(WS_LOG_INFO, "[SSHD] Using system CAs");
sslCtx = wolfSSL_CTX_new(wolfSSLv23_server_method());
if (sslCtx == NULL) {
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Unable to create temporary CTX");
ret = WS_FATAL_ERROR;
}

if (ret == WS_SUCCESS) {
if (wolfSSHD_ConfigGetSystemCA(conf)) {
if (wolfSSL_CTX_load_system_CA_certs(sslCtx) != WOLFSSL_SUCCESS) {
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Issue loading system CAs");
ret = WS_FATAL_ERROR;
}
}
}

if (ret == WS_SUCCESS) {
if (wolfSSHD_ConfigGetUserCAStore(conf)) {
#ifdef USE_WINDOWS_API
if (wolfSSL_CTX_load_windows_user_CA_certs(sslCtx,
wolfSSHD_ConfigGetWinUserStores(conf),
wolfSSHD_ConfigGetWinUserDwFlags(conf),
wolfSSHD_ConfigGetWinUserPvPara(conf)) != WOLFSSL_SUCCESS) {
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Issue loading user CAs");
ret = WS_FATAL_ERROR;
}
#else
wolfSSH_Log(WS_LOG_INFO,
"[SSHD] User CA store is only supported on Windows");
ret = WS_BAD_ARGUMENT;
#endif /* USE_WINDOWS_API */
}
}

if (ret == WS_SUCCESS) {
if (wolfSSH_SetCertManager(*ctx,
wolfSSL_CTX_GetCertManager(sslCtx)) != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_INFO,
"[SSHD] Issue copying over system CAs");
ret = WS_FATAL_ERROR;
}
}

if (sslCtx != NULL) {
wolfSSL_CTX_free(sslCtx);
}
}
#else
/* The system/user CA-store directives are parsed unconditionally, so warn
* if they were set but wolfSSL was built without WOLFSSL_SYS_CA_CERTS,
* rather than silently ignoring them. */
if (ret == WS_SUCCESS && (wolfSSHD_ConfigGetSystemCA(conf)
|| wolfSSHD_ConfigGetUserCAStore(conf))) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] wolfSSH_TrustedSystemCAKeys/wolfSSH_TrustedUserCaStore set "
"but wolfSSL was built without WOLFSSL_SYS_CA_CERTS; ignoring.");
}
#endif

/* load in CA certs from file set */
if (ret == WS_SUCCESS) {
char* caCert = wolfSSHD_ConfigGetUserCAKeysFile(conf);
if (caCert != NULL) {
Expand Down Expand Up @@ -2563,6 +2716,24 @@ static int StartSSHD(int argc, char** argv)
}
}

if (logFile == NULL) {
logFile = stderr;
}
#ifdef _WIN32
/* The early -D detection (wide-string comparison of cmdArgs before
* conversion) may have set ServiceDebugCb even when -D was supplied.
* Now that mygetopt has been processed, restore the file-based
* callback in any case where output should go to logFile:
* - isDaemon==0 → running interactively, logs to logFile (stderr)
* - isDaemon==1 but -E was used → logs to the specified file
* This must happen BEFORE config/SetupCTX so their log messages are
* captured in the file (or stderr) rather than lost to
* OutputDebugString. */
if (!isDaemon || logFile != stderr) {
wolfSSH_SetLoggingCb(wolfSSHDLoggingCb);
}
#endif

if (ret == WS_SUCCESS) {
ret = wolfSSHD_ConfigLoad(conf, configFile);
if (ret != WS_SUCCESS) {
Expand Down Expand Up @@ -2594,10 +2765,6 @@ static int StartSSHD(int argc, char** argv)
}
}

if (logFile == NULL) {
logFile = stderr;
}

/* run as a daemon or service */
#ifndef WIN32
if (ret == WS_SUCCESS && isDaemon) {
Expand Down
13 changes: 13 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,12 @@ AC_ARG_ENABLE([certs],
[AS_HELP_STRING([--enable-certs],[Enable X.509 cert support (default: disabled)])],
[ENABLED_CERTS=$enableval],[ENABLED_CERTS=no])

# Windows certificate store (host/client keys)
AC_ARG_ENABLE([windows-cert-store],
[AS_HELP_STRING([--enable-windows-cert-store],[Enable Windows certificate store integration for keys (default: disabled)])],
[ENABLED_WINDOWS_CERT_STORE=$enableval],
[ENABLED_WINDOWS_CERT_STORE=no])

# TPM 2.0 Support
AC_ARG_ENABLE([tpm],
[AS_HELP_STRING([--enable-tpm],[Enable TPM 2.0 support (default: disabled)])],
Expand Down Expand Up @@ -276,6 +282,12 @@ AS_IF([test "x$ENABLED_AGENT" = "xyes"],
[AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_AGENT"])
AS_IF([test "x$ENABLED_CERTS" = "xyes"],
[AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_CERTS"])
AS_IF([test "x$ENABLED_WINDOWS_CERT_STORE" = "xyes"],
[AS_IF([test "x$ENABLED_CERTS" != "xyes"],
[AC_MSG_ERROR([--enable-windows-cert-store requires X.509 cert support (--enable-certs)])])
AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_WINDOWS_CERT_STORE"
AS_CASE([$host],
[*mingw*|*msys*|*cygwin*],[LIBS="$LIBS -lcrypt32 -lncrypt"])])
Comment thread
JacobBarthelmeh marked this conversation as resolved.
AS_IF([test "x$ENABLED_SMALLSTACK" = "xyes"],
[AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_SMALL_STACK"])
AS_IF([test "x$ENABLED_SSHCLIENT" = "xyes"],
Expand Down Expand Up @@ -380,4 +392,5 @@ AS_ECHO([" * agent: $ENABLED_AGENT"])
AS_ECHO([" * TPM 2.0 support: $ENABLED_TPM"])
AS_ECHO([" * TCP/IP Forwarding: $ENABLED_FWD"])
AS_ECHO([" * X.509 Certs: $ENABLED_CERTS"])
AS_ECHO([" * Windows cert store: $ENABLED_WINDOWS_CERT_STORE"])
AS_ECHO([" * Examples: $ENABLED_EXAMPLES"])
Loading
Loading