Skip to content

Commit 9e1bbd9

Browse files
author
Emma Stensland
committed
F-6700: Add PermitRootLogin prohibit-password and forced-commands-only modes
Update test keys for authentication testing
1 parent cc5311a commit 9e1bbd9

8 files changed

Lines changed: 519 additions & 22 deletions

apps/wolfsshd/auth.c

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -360,20 +360,20 @@ static int ExtractSalt(char* hash, char** salt, int saltSz)
360360

361361
#if defined(WOLFSSH_HAVE_LIBCRYPT) || defined(WOLFSSH_HAVE_LIBLOGIN)
362362
#ifdef WOLFSSHD_UNIT_TEST
363-
int CheckPasswordHashUnix(const char* input, char* stored)
363+
int CheckPasswordHashUnix(const char* input, const char* stored)
364364
#else
365-
static int CheckPasswordHashUnix(const char* input, char* stored)
365+
static int CheckPasswordHashUnix(const char* input, const char* stored)
366366
#endif
367367
{
368368
int ret = WSSHD_AUTH_SUCCESS;
369-
char* hashedInput;
369+
char* hashedInput = NULL;
370370
word32 hashedInputSz = 0, storedSz = 0;
371371

372372
if (input == NULL || stored == NULL) {
373373
ret = WS_BAD_ARGUMENT;
374374
}
375375

376-
/* empty password case */
376+
/* empty password case: fast return based on real stored hash. */
377377
if (ret == WSSHD_AUTH_SUCCESS && stored[0] == 0 && WSTRLEN(input) == 0) {
378378
wolfSSH_Log(WS_LOG_INFO,
379379
"[SSHD] User logged in with empty password");
@@ -1626,6 +1626,32 @@ static int RequestAuthentication(WS_UserAuthData* authData,
16261626

16271627
usr = (const char*)authData->username;
16281628
ret = DoCheckUser(usr, authCtx);
1629+
1630+
/* Avoid timing leak for invalid users with a fake crypt() call using a
1631+
* real $6$ (SHA-512) salt so it costs the same as a real lookup. */
1632+
if (ret == WOLFSSH_USERAUTH_INVALID_USER &&
1633+
authData->type == WOLFSSH_USERAUTH_PASSWORD) {
1634+
#if defined(WOLFSSH_HAVE_LIBCRYPT) || defined(WOLFSSH_HAVE_LIBLOGIN)
1635+
word32 fakePwSz = authData->sf.password.passwordSz;
1636+
char* fakePwStr = (char*)WMALLOC(fakePwSz + 1, NULL, DYNTYPE_STRING);
1637+
1638+
if (fakePwStr != NULL) {
1639+
if (fakePwSz > 0) {
1640+
XMEMCPY(fakePwStr, authData->sf.password.password, fakePwSz);
1641+
}
1642+
fakePwStr[fakePwSz] = 0;
1643+
}
1644+
1645+
CheckPasswordHashUnix(fakePwStr != NULL ? fakePwStr : "",
1646+
"$6$wolfSSHDfakeSalt$");
1647+
1648+
if (fakePwStr != NULL) {
1649+
WS_FORCEZERO(fakePwStr, fakePwSz + 1);
1650+
WFREE(fakePwStr, NULL, DYNTYPE_STRING);
1651+
}
1652+
#endif
1653+
}
1654+
16291655
/* temporarily elevate permissions */
16301656
if (ret == WOLFSSH_USERAUTH_SUCCESS &&
16311657
wolfSSHD_AuthRaisePermissions(authCtx) != WS_SUCCESS) {
@@ -1660,12 +1686,23 @@ static int RequestAuthentication(WS_UserAuthData* authData,
16601686

16611687
if (ret == WOLFSSH_USERAUTH_SUCCESS &&
16621688
authData->type == WOLFSSH_USERAUTH_PASSWORD) {
1689+
int configAllowed = 0;
16631690

16641691
if (wolfSSHD_ConfigGetPwAuth(usrConf) != 1) {
16651692
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Password authentication not "
16661693
"allowed by configuration!");
16671694
ret = WOLFSSH_USERAUTH_REJECTED;
16681695
}
1696+
else if (XSTRCMP(usr, "root") == 0 &&
1697+
(wolfSSHD_ConfigGetPermitRoot(usrConf) ==
1698+
WOLFSSHD_PERMIT_ROOT_PROHIBIT_PW ||
1699+
wolfSSHD_ConfigGetPermitRoot(usrConf) ==
1700+
WOLFSSHD_PERMIT_ROOT_FORCED_CMD)) {
1701+
/* prohibit-password and forced-commands-only both block this. */
1702+
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Password authentication for "
1703+
"root not allowed by configuration!");
1704+
ret = WOLFSSH_USERAUTH_REJECTED;
1705+
}
16691706
/* Check if password is valid for this user. */
16701707
/* first handle empty password cases */
16711708
else if (authData->sf.password.passwordSz == 0 &&
@@ -1675,8 +1712,14 @@ static int RequestAuthentication(WS_UserAuthData* authData,
16751712
ret = WOLFSSH_USERAUTH_FAILURE;
16761713
}
16771714
else {
1715+
configAllowed = 1;
1716+
}
1717+
1718+
/* Only run password check when config allows it (avoids leaks). */
1719+
if (configAllowed) {
16781720
rc = authCtx->checkPasswordCb(usr, authData->sf.password.password,
1679-
authData->sf.password.passwordSz, authCtx);
1721+
authData->sf.password.passwordSz, authCtx);
1722+
16801723
if (rc == WSSHD_AUTH_SUCCESS) {
16811724
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Password ok.");
16821725
}
@@ -1707,6 +1750,18 @@ static int RequestAuthentication(WS_UserAuthData* authData,
17071750
ret = WOLFSSH_USERAUTH_REJECTED;
17081751
}
17091752

1753+
if (ret == WOLFSSH_USERAUTH_SUCCESS &&
1754+
authData->type == WOLFSSH_USERAUTH_PUBLICKEY &&
1755+
XSTRCMP(usr, "root") == 0 &&
1756+
wolfSSHD_ConfigGetPermitRoot(usrConf) ==
1757+
WOLFSSHD_PERMIT_ROOT_FORCED_CMD &&
1758+
wolfSSHD_ConfigGetForcedCmd(usrConf) == NULL) {
1759+
/* forced-commands-only requires a forced command for root pubkey login. */
1760+
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Public key login for root requires "
1761+
"a forced command by configuration!");
1762+
ret = WOLFSSH_USERAUTH_REJECTED;
1763+
}
1764+
17101765
#ifdef WOLFSSL_FPKI
17111766
if (ret == WOLFSSH_USERAUTH_SUCCESS &&
17121767
authData->type == WOLFSSH_USERAUTH_PUBLICKEY) {
@@ -1935,15 +1990,15 @@ int DefaultUserAuthTypes(WOLFSSH* ssh, void* ctx)
19351990
int ret = 0;
19361991

19371992
if (ssh == NULL || ctx == NULL)
1938-
return WS_BAD_ARGUMENT;
1993+
return 0;
19391994
authCtx = (WOLFSSHD_AUTH*)ctx;
19401995

19411996
/* get configuration for user */
19421997
usr = wolfSSH_GetUsername(ssh);
19431998
usrConf = wolfSSHD_AuthGetUserConf(authCtx, usr, NULL, NULL,
19441999
NULL, NULL, NULL);
19452000
if (usrConf == NULL) {
1946-
ret = WS_BAD_ARGUMENT;
2001+
ret = 0;
19472002
}
19482003
else {
19492004
ret = wolfSSHD_GetUserAuthTypes(usrConf);

apps/wolfsshd/auth.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ int wolfSSHD_GetUserGroupNames(void* heap, const char* usr, WGID_T primaryGid,
105105
void wolfSSHD_FreeUserGroupNames(void* heap, char** names, word32 count);
106106
#endif
107107
#if defined(WOLFSSH_HAVE_LIBCRYPT) || defined(WOLFSSH_HAVE_LIBLOGIN)
108-
int CheckPasswordHashUnix(const char* input, char* stored);
108+
int CheckPasswordHashUnix(const char* input, const char* stored);
109109
#endif
110110
int CheckAuthKeysLine(char* line, word32 lineSz, const byte* key,
111111
word32 keySz);

apps/wolfsshd/configuration.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ struct WOLFSSHD_CONFIG {
100100
byte usePrivilegeSeparation:2;
101101
byte passwordAuth:1;
102102
byte pubKeyAuth:1;
103-
byte permitRootLogin:1;
103+
byte permitRootLogin:2;
104104
byte permitEmptyPasswords:1;
105105
byte authKeysFileSet:1; /* if not set then no explicit authorized keys */
106106
byte strictModes:1; /* enforce file permission/ownership checks */
@@ -555,10 +555,20 @@ static int HandlePermitRoot(WOLFSSHD_CONFIG* conf, const char* value)
555555

556556
if (ret == WS_SUCCESS) {
557557
if (WSTRCMP(value, "no") == 0) {
558-
conf->permitRootLogin = 0;
558+
conf->permitRootLogin = WOLFSSHD_PERMIT_ROOT_NO;
559559
}
560560
else if (WSTRCMP(value, "yes") == 0) {
561-
conf->permitRootLogin = 1;
561+
conf->permitRootLogin = WOLFSSHD_PERMIT_ROOT_YES;
562+
}
563+
else if (WSTRCMP(value, "prohibit-password") == 0 ||
564+
WSTRCMP(value, "without-password") == 0) {
565+
conf->permitRootLogin = WOLFSSHD_PERMIT_ROOT_PROHIBIT_PW;
566+
}
567+
else if (WSTRCMP(value, "forced-commands-only") == 0) {
568+
wolfSSH_Log(WS_LOG_WARN, "[SSHD] PermitRootLogin "
569+
"forced-commands-only: authorized_keys command= "
570+
"restrictions not enforced");
571+
conf->permitRootLogin = WOLFSSHD_PERMIT_ROOT_FORCED_CMD;
562572
}
563573
else {
564574
ret = WS_BAD_ARGUMENT;

apps/wolfsshd/configuration.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ typedef struct WOLFSSHD_CONFIG WOLFSSHD_CONFIG;
4343
#define MAX_PATH_SZ 80
4444
#endif
4545

46+
/* 0 so that root login is default off after struct memset'd on init */
47+
#define WOLFSSHD_PERMIT_ROOT_NO 0
48+
#define WOLFSSHD_PERMIT_ROOT_YES 1
49+
#define WOLFSSHD_PERMIT_ROOT_PROHIBIT_PW 2
50+
/* Prohibits passwords; pubkey logins require a configured ForceCommand
51+
* (ignores authorized_keys command="..." restrictions). */
52+
#define WOLFSSHD_PERMIT_ROOT_FORCED_CMD 3
53+
4654
WOLFSSHD_CONFIG* wolfSSHD_ConfigNew(void* heap);
4755
void wolfSSHD_ConfigFree(WOLFSSHD_CONFIG* conf);
4856
int wolfSSHD_ConfigLoad(WOLFSSHD_CONFIG* conf, const char* filename);

apps/wolfsshd/test/run_all_sshd_tests.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,12 +395,14 @@ else
395395
run_test "sshd_forcedcmd_test.sh"
396396
run_test "sshd_window_full_test.sh"
397397
run_test "sshd_empty_password_test.sh"
398+
run_test "sshd_permitroot_prohibit_password.sh"
399+
run_test "sshd_permitroot_forced_cmd.sh"
398400
run_strictmodes_negative_test
399401
run_test "sshd_login_grace_test.sh"
400402
run_test "sshd_privdrop_fail_test.sh"
401403
else
402404
printf "Skipping tests that need to setup local SSHD\n"
403-
SKIPPED=$((SKIPPED+6))
405+
SKIPPED=$((SKIPPED+8))
404406
fi
405407

406408
# these tests run with X509 sshd-config loaded
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#!/bin/bash
2+
3+
# Test for PermitRootLogin forced-commands-only enforcement.
4+
5+
if [ -z "$1" ] || [ -z "$2" ]; then
6+
echo "expecting host and port as arguments"
7+
echo "$0 127.0.0.1 22222"
8+
exit 1
9+
fi
10+
11+
TEST_HOST="$1"
12+
TEST_PORT="$2"
13+
USER="root"
14+
PWD=`pwd`
15+
16+
source ./start_sshd.sh
17+
18+
# Build an authorized_keys file for "root" so we can test root public-key
19+
# auth both with and without a ForceCommand configured.
20+
cat ../../../keys/hansel-*.pub > authorized_keys_test_forced_cmd
21+
sed -i.bak "s/hansel/root/" ./authorized_keys_test_forced_cmd
22+
23+
TEST_CLIENT="../../../examples/client/client"
24+
25+
cleanup() {
26+
stop_wolfsshd
27+
rm -f authorized_keys_test_forced_cmd authorized_keys_test_forced_cmd.bak \
28+
sshd_config_test_forced_cmd sshd_config_test_forced_cmd_with_cmd
29+
}
30+
trap cleanup EXIT
31+
32+
### Scenario 1: forced-commands-only with no ForceCommand configured -- both
33+
### root password auth and root pubkey auth must be rejected.
34+
cat <<EOF > sshd_config_test_forced_cmd
35+
Port $TEST_PORT
36+
Protocol 2
37+
LoginGraceTime 600
38+
PermitRootLogin forced-commands-only
39+
PasswordAuthentication yes
40+
AuthorizedKeysFile $PWD/authorized_keys_test_forced_cmd
41+
StrictModes no
42+
UsePrivilegeSeparation no
43+
UseDNS no
44+
HostKey $PWD/../../../keys/server-key.pem
45+
EOF
46+
47+
sudo rm -f ./log.txt
48+
49+
start_wolfsshd "sshd_config_test_forced_cmd"
50+
if [ -z "$PID" ]; then
51+
echo "Failed to start wolfsshd"
52+
exit 1
53+
fi
54+
55+
timeout 10 $TEST_CLIENT -u "$USER" -P "somepassword" -c 'true' \
56+
-h "$TEST_HOST" -p "$TEST_PORT" > /dev/null 2>&1
57+
PASS_RESULT=$?
58+
59+
if [ "$PASS_RESULT" = 0 ]; then
60+
echo "FAIL: root password login unexpectedly succeeded under forced-commands-only"
61+
exit 1
62+
fi
63+
64+
timeout 10 $TEST_CLIENT -u "$USER" -c 'true' \
65+
-i ../../../keys/hansel-key-ecc.der -j ../../../keys/hansel-key-ecc.pub \
66+
-h "$TEST_HOST" -p "$TEST_PORT" > /dev/null 2>&1
67+
NOCMD_PUBKEY_RESULT=$?
68+
69+
if [ "$NOCMD_PUBKEY_RESULT" = 0 ]; then
70+
echo "FAIL: root pubkey login without a ForceCommand unexpectedly succeeded"
71+
exit 1
72+
fi
73+
74+
# Let wolfsshd flush the log before we stop it.
75+
sleep 1
76+
77+
if ! sudo grep -q "Password authentication for root not allowed by configuration!" ./log.txt; then
78+
echo "FAIL: forced-commands-only did not reject root password login as expected"
79+
echo "----- log.txt -----"
80+
sudo cat ./log.txt
81+
exit 1
82+
fi
83+
84+
if ! sudo grep -q "Public key login for root requires a forced command by configuration!" ./log.txt; then
85+
echo "FAIL: forced-commands-only did not reject root pubkey login without a ForceCommand"
86+
echo "----- log.txt -----"
87+
sudo cat ./log.txt
88+
exit 1
89+
fi
90+
91+
stop_wolfsshd
92+
93+
### Scenario 2: forced-commands-only WITH a ForceCommand configured -- root
94+
### pubkey login must now be permitted.
95+
cat <<EOF > sshd_config_test_forced_cmd_with_cmd
96+
Port $TEST_PORT
97+
Protocol 2
98+
LoginGraceTime 600
99+
PermitRootLogin forced-commands-only
100+
ForceCommand true
101+
PasswordAuthentication yes
102+
AuthorizedKeysFile $PWD/authorized_keys_test_forced_cmd
103+
StrictModes no
104+
UsePrivilegeSeparation no
105+
UseDNS no
106+
HostKey $PWD/../../../keys/server-key.pem
107+
EOF
108+
109+
sudo rm -f ./log.txt
110+
111+
start_wolfsshd "sshd_config_test_forced_cmd_with_cmd"
112+
if [ -z "$PID" ]; then
113+
echo "Failed to start wolfsshd"
114+
exit 1
115+
fi
116+
117+
timeout 10 $TEST_CLIENT -u "$USER" -c 'true' \
118+
-i ../../../keys/hansel-key-ecc.der -j ../../../keys/hansel-key-ecc.pub \
119+
-h "$TEST_HOST" -p "$TEST_PORT" > /dev/null 2>&1
120+
WITHCMD_PUBKEY_RESULT=$?
121+
122+
if [ "$WITHCMD_PUBKEY_RESULT" != 0 ]; then
123+
echo "FAIL: root pubkey login with a ForceCommand configured was rejected"
124+
echo "----- log.txt -----"
125+
sudo cat ./log.txt
126+
exit 1
127+
fi
128+
129+
exit 0

0 commit comments

Comments
 (0)