Skip to content

Commit edecd0a

Browse files
expand test cases, add negative tests, cap string log print outs, add documentation
1 parent 5f9c072 commit edecd0a

25 files changed

Lines changed: 1998 additions & 649 deletions

.github/workflows/windows-cert-store-test.yml

Lines changed: 366 additions & 14 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,60 @@ fred-cert.der would be:
538538

539539
$ ./examples/client/client -u fred -J ./keys/fred-cert.der -i ./keys/fred-key.der
540540

541+
WINDOWS CERTIFICATE STORE
542+
=========================
543+
544+
On Windows, host and user keys can come from the MS Certificate Store instead
545+
of files. Requires certificate support (`--enable-certs` or `WOLFSSH_CERTS`);
546+
enable it with the `--enable-windows-cert-store` build option (mingw hosts
547+
only) or by defining `WOLFSSH_WINDOWS_CERT_STORE`. The build links against
548+
`crypt32` and `ncrypt`. For the Visual Studio build see the comment block in
549+
`ide/winvs/user_settings.h`, including the `WOLFSSH_NO_SHA1_SOFT_DISABLE` and
550+
`WC_SIG_MIN_HASH_TYPE` caveats an RSA store certificate needs (RFC 6187's only
551+
RSA algorithm, `x509v3-ssh-rsa`, signs with SHA-1); ECDSA store keys need
552+
neither.
553+
554+
The echoserver and the SFTP client take a `-W store:subject[:flags]` option
555+
naming the store, the certificate's subject CN, and optionally the store
556+
location. Accepted location names are CURRENT_USER (the default),
557+
LOCAL_MACHINE, USERS, CURRENT_SERVICE, SERVICES, CURRENT_USER_GROUP_POLICY,
558+
LOCAL_MACHINE_GROUP_POLICY and LOCAL_MACHINE_ENTERPRISE, each also accepted
559+
with a `CERT_SYSTEM_STORE_` prefix or as a number. `-W` supplies both the
560+
certificate and its private key, so it cannot be combined with `-i`, `-j`, or
561+
`-J`, and it skips the wolfssh home directory search so file arguments resolve
562+
against the current directory.
563+
564+
$ ./examples/echoserver/echoserver -W "My:wolfSSH-Server:LOCAL_MACHINE" -a ./keys/ca-cert-ecc.pem
565+
566+
$ ./examples/sftpclient/wolfsftp -u testuser -W "My:testuser:CURRENT_USER" -A ./keys/ca-cert-ecc.der -X
567+
568+
wolfSSHd gains these configuration directives, all global only (they are
569+
rejected inside a `Match` block):
570+
571+
* `HostKeyStore <store>`, `HostKeyStoreSubject <CN>`, and
572+
`HostKeyStoreFlags <location>` select the host key from a certificate
573+
store. All three must be set together, and they conflict with `HostKey`,
574+
`HostCertificate`, and the `-h` command line option.
575+
* `wolfSSH_TrustedUserCAStore yes|no` loads the client-certificate trust
576+
anchors from a Windows store named by `wolfSSH_WinUserPvPara <store>` at
577+
the mandatory location `wolfSSH_WinUserDwFlags <location>`
578+
(`wolfSSH_WinUserStores` optionally names the provider; only
579+
`CERT_STORE_PROV_SYSTEM` is supported). Only certificates with
580+
basicConstraints CA:TRUE are loaded, and the OS-managed public trust
581+
stores (`Root`, `AuthRoot`, `CA`, ...) are refused: every CA in the named
582+
store becomes an SSH login authority, so point it at a store created for
583+
this purpose that holds nothing but your own CA.
584+
* `wolfSSH_TrustedSystemCAKeys yes|no` imports the OS trust store via
585+
wolfSSL (`WOLFSSL_SYS_CA_CERTS`) as the client-certificate trust anchors.
586+
On CN-binding builds (no FPKI) this additionally requires a per-user
587+
`AuthorizedKeysFile` on every config node, so a subject CN match alone can
588+
never log in.
589+
590+
Without FPKI, a client certificate is bound to the requested account by a
591+
case-insensitive subject CN match only; keep the trusted CA set narrow. Note
592+
also that the config parser requires whitespace between an option name and
593+
its value; the OpenSSH `Keyword=value` form is rejected.
594+
541595
TPM PUBLIC KEY AUTHENTICATION
542596
=============================
543597

apps/wolfsshd/auth.c

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2584,9 +2584,12 @@ static int RequestAuthentication(WS_UserAuthData* authData,
25842584
usrConf = wolfSSHD_AuthGetUserConf(authCtx, usr, NULL, NULL, NULL, NULL,
25852585
NULL);
25862586
if (usrConf == NULL) {
2587+
/* bound the untrusted name so it cannot consume the whole
2588+
* fixed-width log message (control bytes are scrubbed by
2589+
* wolfSSH_Log itself) */
25872590
wolfSSH_Log(WS_LOG_ERROR,
2588-
"[SSHD] Failure to get user configuration for auth (user=%s)",
2589-
usr);
2591+
"[SSHD] Failure to get user configuration for auth "
2592+
"(user=%.32s)", usr);
25902593
ret = WOLFSSH_USERAUTH_FAILURE;
25912594
needFakeCheck = 1;
25922595
}
@@ -2683,7 +2686,11 @@ static int RequestAuthentication(WS_UserAuthData* authData,
26832686
* (checked below) is a stronger binding than a CN name match. A
26842687
* shared AuthorizedKeysFile (an absolute pattern with no %u or %h)
26852688
* resolves to one file for every account and binds the certificate to
2686-
* nothing, so the name match still has to run. */
2689+
* nothing, so the name match still has to run. Note the strength of
2690+
* the file binding rests on the file's integrity: on Windows,
2691+
* wolfSSHD_OpenSecureFile() performs no ownership or ACL checks, so
2692+
* the guarantee is only as good as the NTFS ACLs on the profile
2693+
* directory. */
26872694
#ifdef WOLFSSL_FPKI
26882695
if (authData->sf.publicKey.isCert) {
26892696
#else
@@ -2740,11 +2747,20 @@ static int RequestAuthentication(WS_UserAuthData* authData,
27402747
current = current->next;
27412748
}
27422749

2743-
/* a UPN matched but no realm policy is set; note per auth
2744-
* attempt so the opt-in gap is visible, no shared state */
2750+
/* a UPN matched but no realm policy is set; this states a
2751+
* fixed configuration property, so emit it once per
2752+
* process rather than on every auth attempt (an attempt
2753+
* is peer-triggered and the log callback writes WARN
2754+
* unconditionally) */
27452755
if (upnRealmUnchecked) {
2746-
wolfSSH_Log(WS_LOG_WARN, "[SSHD] AuthorizedUPNDomains "
2747-
"not set; certificate UPN domain is not checked");
2756+
static int upnRealmWarned = 0;
2757+
2758+
if (!upnRealmWarned) {
2759+
upnRealmWarned = 1;
2760+
wolfSSH_Log(WS_LOG_WARN,
2761+
"[SSHD] AuthorizedUPNDomains not set; "
2762+
"certificate UPN domain is not checked");
2763+
}
27482764
}
27492765
#else
27502766
/* Without FPKI compare subject CN with user name. Only
@@ -2760,18 +2776,30 @@ static int RequestAuthentication(WS_UserAuthData* authData,
27602776
WSTRNCASECMP(usr, dCert->subjectCN,
27612777
(size_t)dCert->subjectCNLen) == 0) {
27622778
usrMatch = 1;
2763-
/* note per auth attempt so the weaker binding is
2764-
* visible without -d, no shared state */
2765-
wolfSSH_Log(WS_LOG_WARN, "[SSHD] certificate bound to "
2766-
"user by subject CN only; no issuer constraint is "
2767-
"applied, keep the trusted user CA set narrow");
2779+
/* states a fixed build/configuration property, so
2780+
* emit once per process; a per-attempt WARN would
2781+
* let a peer grow the log with every attempt now
2782+
* that the log callback writes WARN without -d */
2783+
{
2784+
static int cnBindWarned = 0;
2785+
2786+
if (!cnBindWarned) {
2787+
cnBindWarned = 1;
2788+
wolfSSH_Log(WS_LOG_WARN,
2789+
"[SSHD] certificate bound to user by "
2790+
"subject CN only; no issuer constraint");
2791+
wolfSSH_Log(WS_LOG_WARN,
2792+
"[SSHD] keep the trusted user CA set "
2793+
"narrow");
2794+
}
2795+
}
27682796
}
27692797
#endif
27702798

27712799
if (usrMatch == 0) {
27722800
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] incorrect user cert "
27732801
"sent; certificate identity does not match the "
2774-
"requested user (user=%s)", usr);
2802+
"requested user (user=%.32s)", usr);
27752803
ret = WOLFSSH_USERAUTH_INVALID_PUBLICKEY;
27762804
}
27772805
}
@@ -2800,7 +2828,8 @@ static int RequestAuthentication(WS_UserAuthData* authData,
28002828
wolfSSHD_ConfigGetUserCAKeysFile(usrConf))) {
28012829
wolfSSH_Log(WS_LOG_ERROR,
28022830
"[SSHD] Per-user TrustedUserCAKeys override is not enforced "
2803-
"for certificate authentication; rejecting (user=%s)", usr);
2831+
"for certificate authentication; rejecting (user=%.32s)",
2832+
usr);
28042833
ret = WOLFSSH_USERAUTH_REJECTED;
28052834
}
28062835
else {
@@ -2838,7 +2867,7 @@ static int RequestAuthentication(WS_UserAuthData* authData,
28382867
wolfSSH_Log(WS_LOG_ERROR,
28392868
"[SSHD] Certificate authentication cannot bind the requested "
28402869
"user without FPKI or AuthorizedKeysFile; rejecting "
2841-
"(user=%s)", usr);
2870+
"(user=%.32s)", usr);
28422871
ret = WOLFSSH_USERAUTH_REJECTED;
28432872
#endif
28442873
}

0 commit comments

Comments
 (0)