Skip to content

Commit 39fe41f

Browse files
fix for test correctness, use new build options probe
1 parent efa436b commit 39fe41f

12 files changed

Lines changed: 273 additions & 171 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,9 @@ rejected inside a `Match` block):
595595
Note that the pre-existing `HostKey` and `HostCertificate` directives are now
596596
also rejected when they appear after a `Match` block (matching OpenSSH); they
597597
were previously accepted there and silently ignored, so a config that relied
598-
on that will now stop the daemon at startup with a parse error.
598+
on that will now stop the daemon at startup with a parse error. Builds made
599+
with `WOLFSSH_IGNORE_UNKNOWN_CONFIG` instead log a warning and ignore the
600+
directive, preserving the old behavior as a migration path.
599601

600602
Without FPKI, a client certificate is bound to the requested account by a
601603
case-insensitive subject CN match only; keep the trusted CA set narrow. Note

apps/wolfsshd/configuration.c

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,7 @@ static int HandleInclude(WOLFSSHD_CONFIG *conf, const char *value, int depth)
855855
/* Ignore trailing whitespace */
856856
ptr = value + WSTRLEN(value) - 1;
857857
while (ptr != value) {
858-
if (WISSPACE(*ptr)) {
858+
if (WISSPACE((unsigned char)*ptr)) {
859859
ptr--;
860860
}
861861
else {
@@ -1412,14 +1412,32 @@ static int HandleConfigOption(WOLFSSHD_CONFIG** conf, int opt,
14121412
case OPT_HOST_KEY:
14131413
/* TODO: Add logic to check if file exists? */
14141414
ret = CheckNotInMatch(*conf, "HostKey");
1415-
if (ret == WS_SUCCESS)
1415+
if (ret == WS_SUCCESS) {
14161416
ret = wolfSSHD_ConfigSetHostKeyFile(*conf, value);
1417+
}
1418+
#ifdef WOLFSSH_IGNORE_UNKNOWN_CONFIG
1419+
else if (*conf != NULL) {
1420+
/* Earlier releases accepted (and never used) this placement,
1421+
* so ignore-unknown builds keep a migration path. */
1422+
wolfSSH_Log(WS_LOG_WARN,
1423+
"[SSHD] Ignoring HostKey inside a Match block");
1424+
ret = WS_SUCCESS;
1425+
}
1426+
#endif
14171427
break;
14181428
case OPT_HOST_CERT:
14191429
/* TODO: Add logic to check if file exists? */
14201430
ret = CheckNotInMatch(*conf, "HostCertificate");
1421-
if (ret == WS_SUCCESS)
1431+
if (ret == WS_SUCCESS) {
14221432
ret = wolfSSHD_ConfigSetHostCertFile(*conf, value);
1433+
}
1434+
#ifdef WOLFSSH_IGNORE_UNKNOWN_CONFIG
1435+
else if (*conf != NULL) {
1436+
wolfSSH_Log(WS_LOG_WARN,
1437+
"[SSHD] Ignoring HostCertificate inside a Match block");
1438+
ret = WS_SUCCESS;
1439+
}
1440+
#endif
14231441
break;
14241442
case OPT_PASSWORD_AUTH:
14251443
ret = HandlePwAuth(*conf, value);
@@ -1626,16 +1644,33 @@ WOLFSSHD_STATIC int ParseConfigLine(WOLFSSHD_CONFIG** conf, const char* l,
16261644
}
16271645
}
16281646
else {
1629-
#ifdef WOLFSSH_IGNORE_UNKNOWN_CONFIG
1630-
/* WARN, not DEBUG: a known option in the unsupported Keyword=value
1631-
* form also lands here, and silently dropping a directive such as
1632-
* PasswordAuthentication=no must stay visible without -d. */
1633-
wolfSSH_Log(WS_LOG_WARN, "[SSHD] ignoring config line %s.", l);
1634-
ret = WS_SUCCESS;
1635-
#else
1636-
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error parsing config line.");
1637-
ret = WS_FATAL_ERROR;
1638-
#endif
1647+
int isEqForm = 0;
1648+
1649+
/* A known keyword in the OpenSSH Keyword=value form must stay a
1650+
* fatal error even on builds that ignore unknown lines: dropping a
1651+
* directive such as PasswordAuthentication=no would fail open. */
1652+
for (idx = 0; idx < NUM_OPTIONS; ++idx) {
1653+
sz = (int)WSTRLEN(options[idx].name);
1654+
if (lSz > sz && WSTRNCMP(l, options[idx].name, sz) == 0 &&
1655+
l[sz] == '=') {
1656+
isEqForm = 1;
1657+
break;
1658+
}
1659+
}
1660+
if (isEqForm) {
1661+
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Keyword=value form is not "
1662+
"supported, use \"Keyword value\" : %s.", l);
1663+
ret = WS_FATAL_ERROR;
1664+
}
1665+
else {
1666+
#ifdef WOLFSSH_IGNORE_UNKNOWN_CONFIG
1667+
wolfSSH_Log(WS_LOG_WARN, "[SSHD] ignoring config line %s.", l);
1668+
ret = WS_SUCCESS;
1669+
#else
1670+
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error parsing config line.");
1671+
ret = WS_FATAL_ERROR;
1672+
#endif
1673+
}
16391674
}
16401675

16411676
return ret;

apps/wolfsshd/test/create_sshd_config.sh

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ EOF
3232

3333
# wolfSSHd refuses to start when AuthorizedUPNDomains is set on a build that
3434
# cannot enforce it (wolfSSL without FPKI), so only write the directive when
35-
# the daemon binary reports FPKI support. sshd_x509_upn_fail.sh skips itself
36-
# on such builds for the same reason.
35+
# the build reports FPKI support. sshd_x509_upn_fail.sh skips itself on such
36+
# builds using the same probe.
37+
. ./wolfssh_options.sh
3738
UPN_DOMAIN_GOOD=""
3839
UPN_DOMAIN_BAD=""
39-
if ../wolfsshd "-?" 2>&1 | grep -q "FPKI"; then
40+
if wolfssh_has FPKI; then
4041
UPN_DOMAIN_GOOD="AuthorizedUPNDomains example"
4142
UPN_DOMAIN_BAD="AuthorizedUPNDomains other.example"
4243
fi

apps/wolfsshd/test/run_all_sshd_tests.sh

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ EOF
174174
run_upn_unenforceable_negative_test() {
175175
printf "AuthorizedUPNDomains unenforceable-build negative test ... "
176176
TOTAL=$((TOTAL+1))
177-
if ../wolfsshd "-?" 2>&1 | grep -q "FPKI"; then
177+
if wolfssh_has FPKI; then
178178
printf "SKIPPED (FPKI build enforces the directive)\n"
179179
SKIPPED=$((SKIPPED+1))
180180
return
@@ -206,12 +206,10 @@ EOF
206206
# branch logs "Ignoring AuthorizedUPNDomains ... cannot enforce it" and
207207
# keeps running, which must not pass as the startup refusal. Also require
208208
# that the host key loaded: a "Refusing to load" failure would exit before
209-
# the UPN gate. Finally require that nothing was left listening on the
210-
# test port.
209+
# the UPN gate.
211210
if grep -q "AuthorizedUPNDomains is set" upn_nofpki_log.txt &&
212211
grep -q "but this build cannot enforce it" upn_nofpki_log.txt &&
213-
! grep -q "Refusing to load" upn_nofpki_log.txt &&
214-
! nc -z 127.0.0.1 22623 2>/dev/null; then
212+
! grep -q "Refusing to load" upn_nofpki_log.txt; then
215213
printf "PASSED\n"
216214
else
217215
printf "FAILED!\n"

apps/wolfsshd/test/test_configuration.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,12 +295,17 @@ static int test_ParseConfigLine(void)
295295

296296
/* The option matcher requires whitespace (or end of line) after the
297297
* matched name, so an unknown name that extends a real one must not
298-
* prefix-match it. */
298+
* prefix-match it. Ignore-unknown builds accept such lines with a
299+
* warning, so only assert rejection where it is observable. */
300+
#ifndef WOLFSSH_IGNORE_UNKNOWN_CONFIG
299301
{"Unknown extension of Port", "PortFoo 22", 1},
300302
{"Unknown extension of HostKey", "HostKeyFoo /tmp/x", 1},
301303
{"Unknown extension of HostKeyStore", "HostKeyStoreX MY", 1},
302304
{"Unknown extension of TrustedUserCAStore",
303305
"wolfSSH_TrustedUserCAStoreX yes", 1},
306+
#endif
307+
/* A known keyword in Keyword=value form is a hard error on every
308+
* build; ignoring it would silently drop the directive. */
304309
{"Keyword=value form is rejected", "Port=22", 1},
305310

306311
/* The two store-trust toggles follow the same yes/no/invalid

apps/wolfsshd/wolfsshd.c

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,9 @@ static void wolfSSHDLoggingCb(enum wolfSSH_LogLevel lvl, const char *const str)
311311
* level messages. Warnings carry the security relevant notices, e.g. that
312312
* a certificate was bound to an account by subject CN alone, so they must
313313
* not depend on -d. */
314+
if (logFile == NULL) {
315+
return;
316+
}
314317
if (lvl == WS_LOG_ERROR || lvl == WS_LOG_WARN || debugMode) {
315318
#ifdef _WIN32
316319
AcquireSRWLockExclusive(&logRepeatLock);
@@ -640,7 +643,7 @@ static int LoadUserCACertsFromStore(const WOLFSSHD_CONFIG* conf,
640643
if (providerStr != NULL &&
641644
WSTRCMP(providerStr, "CERT_STORE_PROV_SYSTEM") != 0) {
642645
wolfSSH_Log(WS_LOG_ERROR,
643-
"[SSHD] wolfSSH_WinUserStores='%s' is not supported; only "
646+
"[SSHD] wolfSSH_WinUserStores='%.48s' is not supported; only "
644647
"CERT_STORE_PROV_SYSTEM is supported", providerStr);
645648
return WS_BAD_ARGUMENT;
646649
}
@@ -704,7 +707,7 @@ static int LoadUserCACertsFromStore(const WOLFSSHD_CONFIG* conf,
704707
wStoreName);
705708
if (hStore == NULL) {
706709
wolfSSH_Log(WS_LOG_ERROR,
707-
"[SSHD] Unable to open user CA cert store '%s', error %lu",
710+
"[SSHD] Unable to open user CA cert store '%.48s', error %lu",
708711
storeNameStr, (unsigned long)GetLastError());
709712
WFREE(wStoreName, heap, DYNTYPE_SSHD);
710713
return WS_FATAL_ERROR;
@@ -797,6 +800,32 @@ static int LoadUserCACertsFromStore(const WOLFSSHD_CONFIG* conf,
797800
}
798801
#endif /* WOLFSSH_CERTS && WOLFSSH_WINDOWS_CERT_STORE */
799802

803+
#if defined(WOLFSSH_CERTS) && (defined(_WIN32) || defined(WOLFSSL_FPKI))
804+
/* Returns non-zero when any config node configures a certificate trust
805+
* anchor. TrustedUserCAKeys may live inside a Match block, so the whole list
806+
* must be walked; the two store flags are global-only and live on the head
807+
* node. */
808+
static int AnyNodeHasCertTrustAnchor(const WOLFSSHD_CONFIG* conf)
809+
{
810+
const WOLFSSHD_CONFIG* cur;
811+
int found = 0;
812+
813+
if (wolfSSHD_ConfigGetUserCAStore(conf) ||
814+
wolfSSHD_ConfigGetSystemCA(conf)) {
815+
found = 1;
816+
}
817+
cur = conf;
818+
while (!found && cur != NULL) {
819+
if (wolfSSHD_ConfigGetUserCAKeysFile(cur) != NULL) {
820+
found = 1;
821+
}
822+
cur = wolfSSHD_ConfigGetNext(cur);
823+
}
824+
825+
return found;
826+
}
827+
#endif /* WOLFSSH_CERTS && (_WIN32 || WOLFSSL_FPKI) */
828+
800829
/* Initializes and sets up the WOLFSSH_CTX struct based on the configure options
801830
* return WS_SUCCESS on success
802831
*/
@@ -1239,10 +1268,7 @@ static int SetupCTX(WOLFSSHD_CONFIG* conf, WOLFSSH_CTX** ctx,
12391268
* fixed by the wolfSSL build, not by configuration, so this cannot be
12401269
* derived from the config file. */
12411270
#if defined(WOLFSSH_CERTS) && !defined(WOLFSSL_FPKI) && defined(_WIN32)
1242-
if (ret == WS_SUCCESS &&
1243-
(wolfSSHD_ConfigGetUserCAKeysFile(conf) != NULL ||
1244-
wolfSSHD_ConfigGetUserCAStore(conf) ||
1245-
wolfSSHD_ConfigGetSystemCA(conf))) {
1271+
if (ret == WS_SUCCESS && AnyNodeHasCertTrustAnchor(conf)) {
12461272
wolfSSH_Log(WS_LOG_WARN,
12471273
"[SSHD] WARNING: client certificates are bound to an account by "
12481274
"subject CN only.");
@@ -1344,10 +1370,7 @@ static int SetupCTX(WOLFSSHD_CONFIG* conf, WOLFSSH_CTX** ctx,
13441370
* callback writes WARN unconditionally, so a per-attempt WARN would let
13451371
* a peer grow the log). Only emitted when a certificate trust anchor is
13461372
* actually configured; with no CA there is no UPN check to relax. */
1347-
if (ret == WS_SUCCESS &&
1348-
(wolfSSHD_ConfigGetUserCAKeysFile(conf) != NULL ||
1349-
wolfSSHD_ConfigGetUserCAStore(conf) ||
1350-
wolfSSHD_ConfigGetSystemCA(conf))) {
1373+
if (ret == WS_SUCCESS && AnyNodeHasCertTrustAnchor(conf)) {
13511374
const WOLFSSHD_CONFIG* cur;
13521375
const char* domains;
13531376

@@ -1376,10 +1399,7 @@ static int SetupCTX(WOLFSSHD_CONFIG* conf, WOLFSSH_CTX** ctx,
13761399
* no CN check to skip. With FPKI the UPN identity check always runs, so
13771400
* there is nothing to note. */
13781401
#if defined(WOLFSSH_CERTS) && !defined(WOLFSSL_FPKI) && defined(_WIN32)
1379-
if (ret == WS_SUCCESS &&
1380-
(wolfSSHD_ConfigGetUserCAKeysFile(conf) != NULL ||
1381-
wolfSSHD_ConfigGetUserCAStore(conf) ||
1382-
wolfSSHD_ConfigGetSystemCA(conf))) {
1402+
if (ret == WS_SUCCESS && AnyNodeHasCertTrustAnchor(conf)) {
13831403
const WOLFSSHD_CONFIG* cur;
13841404

13851405
cur = conf;
@@ -3229,6 +3249,9 @@ static void* HandleConnection(void* arg)
32293249
{
32303250
int ret = WS_SUCCESS;
32313251
int error;
3252+
#ifdef _WIN32
3253+
byte threaded = 0;
3254+
#endif
32323255

32333256
WOLFSSHD_CONNECTION* conn = NULL;
32343257
WOLFSSH* ssh = NULL;
@@ -3572,8 +3595,24 @@ static void* HandleConnection(void* arg)
35723595
WCLOSESOCKET(conn->fd);
35733596
}
35743597
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Return from closing connection = %d", ret);
3598+
#ifdef _WIN32
3599+
if (conn != NULL) {
3600+
threaded = conn->isThreaded;
3601+
}
3602+
#endif
35753603
WFREE(conn, NULL, DYNTYPE_SSHD);
3604+
3605+
/* The repeat state is per connection only when each connection is its own
3606+
* process (POSIX fork) or the lone in-process connection. Windows daemon
3607+
* threads share it, so flushing here would clear a streak another live
3608+
* connection thread still owns; the shutdown flush covers that path. */
3609+
#ifdef _WIN32
3610+
if (!threaded) {
3611+
wolfSSHDLoggingFlush();
3612+
}
3613+
#else
35763614
wolfSSHDLoggingFlush();
3615+
#endif
35773616

35783617
#ifdef _WIN32
35793618
return 0;
@@ -3865,6 +3904,9 @@ static int StartSSHD(int argc, char** argv)
38653904
ret = WFOPEN(NULL, &logFile, myoptarg, "ab");
38663905
if (ret != 0 || logFile == WBADFILE) {
38673906
fprintf(stderr, "Unable to open log file %s\n", myoptarg);
3907+
/* option parsing continues and may log before the error is
3908+
* acted on, so never leave the stream NULL */
3909+
logFile = stderr;
38683910
ret = WS_FATAL_ERROR;
38693911
}
38703912
break;

examples/echoserver/echoserver.c

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3104,8 +3104,8 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args)
31043104
const char* cipherList = NULL;
31053105
ES_HEAP_HINT* heap = NULL;
31063106
#ifdef WOLFSSH_TPM
3107-
static char* tpmKeyPath = NULL;
3108-
static char* tpmHostKeyPath = NULL;
3107+
char* tpmKeyPath = NULL;
3108+
char* tpmHostKeyPath = NULL;
31093109
#endif
31103110
int multipleConnections = 1;
31113111
int userEcc = 0;
@@ -3276,23 +3276,25 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args)
32763276
}
32773277
myoptind = 0; /* reset for test cases */
32783278

3279-
wc_InitMutex(&doneLock);
3280-
3281-
#ifdef WOLFSSH_TEST_BLOCK
3282-
if (!nonBlock) {
3283-
ES_ERROR("Use -N when testing forced non-blocking\n");
3284-
}
3285-
#endif
3286-
32873279
#if defined(WOLFSSH_TPM) && defined(WOLFSSH_WINDOWS_CERT_STORE)
32883280
/* Both register a host key on the same CTX; loading both would leave
32893281
* which key the server presents up to algorithm negotiation. The SFTP
3290-
* client and wolfsshd reject the equivalent mixes the same way. */
3282+
* client and wolfsshd reject the equivalent mixes the same way.
3283+
* Checked before wc_InitMutex(&doneLock) so ES_ERROR's return path
3284+
* does not leak an initialized mutex. */
32913285
if (tpmHostKeyPath != NULL && certStoreSpec != NULL) {
32923286
ES_ERROR("-W cannot be combined with -G\n");
32933287
}
32943288
#endif
32953289

3290+
wc_InitMutex(&doneLock);
3291+
3292+
#ifdef WOLFSSH_TEST_BLOCK
3293+
if (!nonBlock) {
3294+
ES_ERROR("Use -N when testing forced non-blocking\n");
3295+
}
3296+
#endif
3297+
32963298
#ifdef WOLFSSH_NO_RSA
32973299
/* If wolfCrypt isn't built with RSA, force ECC on. */
32983300
userEcc = 1;

0 commit comments

Comments
 (0)