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
89 changes: 63 additions & 26 deletions apps/wolfsshd/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -368,13 +368,13 @@ static int ExtractSalt(char* hash, char** salt, int saltSz)

#if defined(WOLFSSH_HAVE_LIBCRYPT) || defined(WOLFSSH_HAVE_LIBLOGIN)
#ifdef WOLFSSHD_UNIT_TEST
int CheckPasswordHashUnix(const char* input, char* stored)
int CheckPasswordHashUnix(const char* input, const char* stored)
#else
static int CheckPasswordHashUnix(const char* input, char* stored)
static int CheckPasswordHashUnix(const char* input, const char* stored)
#endif
{
int ret = WSSHD_AUTH_SUCCESS;
char* hashedInput;
char* hashedInput = NULL;
word32 hashedInputSz = 0, storedSz = 0;

if (input == NULL || stored == NULL) {
Expand Down Expand Up @@ -429,7 +429,7 @@ static int CheckPasswordUnix(const char* usr, const byte* pw, word32 pwSz, WOLFS
}

if (ret == WS_SUCCESS) {
pwStr = (char*)WMALLOC(pwSz + 1, NULL, DYNTYPE_STRING);
pwStr = (char*)WMALLOC(pwSz + 1, NULL, DYNAMIC_TYPE_STRING);
if (pwStr == NULL) {
ret = WS_MEMORY_E;
}
Expand Down Expand Up @@ -478,7 +478,7 @@ static int CheckPasswordUnix(const char* usr, const byte* pw, word32 pwSz, WOLFS
}
}
if (ret == WS_SUCCESS) {
storedHashCpy = WSTRDUP(storedHash, NULL, DYNTYPE_STRING);
storedHashCpy = WSTRDUP(storedHash, NULL, DYNAMIC_TYPE_STRING);
if (storedHashCpy == NULL) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Error getting stored hash copy");
Expand All @@ -497,11 +497,11 @@ static int CheckPasswordUnix(const char* usr, const byte* pw, word32 pwSz, WOLFS

if (pwStr != NULL) {
WS_FORCEZERO(pwStr, pwSz + 1);
WFREE(pwStr, NULL, DYNTYPE_STRING);
WFREE(pwStr, NULL, DYNAMIC_TYPE_STRING);
}
if (storedHashCpy != NULL) {
WS_FORCEZERO(storedHashCpy, (word32)WSTRLEN(storedHashCpy) + 1);
WFREE(storedHashCpy, NULL, DYNTYPE_STRING);
WFREE(storedHashCpy, NULL, DYNAMIC_TYPE_STRING);
}

WOLFSSH_UNUSED(authCtx);
Expand Down Expand Up @@ -1458,37 +1458,44 @@ static int CheckPublicKeyWIN(const char* usr,
}
#endif /* _WIN32*/

/* return WOLFSSH_USERAUTH_SUCCESS on success */
static int DoCheckUser(const char* usr, WOLFSSHD_AUTH* auth)
/* Returns 1 when 'usr' is root-equivalent for PermitRootLogin purposes, 0
* otherwise. On Unix this covers every uid 0 account (so an alias like
* "toor" cannot bypass PermitRootLogin) as well as the literal name "root",
* so a transient getpwnam failure cannot skip the check for root. On
* Windows there is no uid 0 and no logon token yet at this pre-auth stage,
* so this falls back to the literal name; a token based Administrators
* membership check would belong after authentication. Shared by DoCheckUser
* and RequestAuthentication so every PermitRootLogin enforcement point
* (login gate, password block, forced-command requirement) agrees on what
* counts as root. */
static int IsRootUser(const char* usr)
{
int ret = WOLFSSH_USERAUTH_SUCCESS;
int rc;
int isRoot = 0;
WOLFSSHD_CONFIG* usrConf;
#ifndef _WIN32
struct passwd* pwInfo;
#endif

wolfSSH_Log(WS_LOG_INFO, "[SSHD] Checking user name %s", usr);

#ifndef _WIN32
/* PermitRootLogin covers every uid 0 account (so an alias like "toor"
* cannot bypass it) and the literal name "root", so a transient getpwnam
* failure cannot skip the check for root. */
pwInfo = getpwnam(usr);
if ((pwInfo != NULL && pwInfo->pw_uid == 0) || XSTRCMP(usr, "root") == 0) {
isRoot = 1;
}
#else
/* No uid 0 on Windows and no logon token yet at this pre-auth stage, so
* fall back to the literal name; a token based Administrators membership
* check would belong after authentication. */
if (XSTRCMP(usr, "root") == 0) {
isRoot = 1;
}
#endif
return isRoot;
}

/* return WOLFSSH_USERAUTH_SUCCESS on success */
static int DoCheckUser(const char* usr, WOLFSSHD_AUTH* auth)
{
int ret = WOLFSSH_USERAUTH_SUCCESS;
int rc;
WOLFSSHD_CONFIG* usrConf;

wolfSSH_Log(WS_LOG_INFO, "[SSHD] Checking user name %s", usr);

if (isRoot == 1) {
if (IsRootUser(usr) == 1) {
/* Resolve per-user config so a Match override is honored; a NULL
* result is unresolvable, so fail closed and reject. */
usrConf = wolfSSHD_AuthGetUserConf(auth, usr, NULL, NULL, NULL, NULL,
Expand Down Expand Up @@ -1647,6 +1654,7 @@ static int RequestAuthentication(WS_UserAuthData* authData,

usr = (const char*)authData->username;
ret = DoCheckUser(usr, authCtx);

/* temporarily elevate permissions */
if (ret == WOLFSSH_USERAUTH_SUCCESS &&
wolfSSHD_AuthRaisePermissions(authCtx) != WS_SUCCESS) {
Expand Down Expand Up @@ -1681,12 +1689,23 @@ static int RequestAuthentication(WS_UserAuthData* authData,

if (ret == WOLFSSH_USERAUTH_SUCCESS &&
authData->type == WOLFSSH_USERAUTH_PASSWORD) {
int configAllowed = 0;

if (wolfSSHD_ConfigGetPwAuth(usrConf) != 1) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Password authentication not "
"allowed by configuration!");
ret = WOLFSSH_USERAUTH_REJECTED;
}
else if (IsRootUser(usr) == 1 &&
(wolfSSHD_ConfigGetPermitRoot(usrConf) ==
WOLFSSHD_PERMIT_ROOT_PROHIBIT_PW ||
wolfSSHD_ConfigGetPermitRoot(usrConf) ==
WOLFSSHD_PERMIT_ROOT_FORCED_CMD)) {
/* prohibit-password and forced-commands-only both block this. */
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Password authentication for "
"root not allowed by configuration!");
ret = WOLFSSH_USERAUTH_REJECTED;
}
/* Check if password is valid for this user. */
/* first handle empty password cases */
else if (authData->sf.password.passwordSz == 0 &&
Expand All @@ -1696,8 +1715,14 @@ static int RequestAuthentication(WS_UserAuthData* authData,
ret = WOLFSSH_USERAUTH_FAILURE;
}
else {
configAllowed = 1;
}

/* Only run password check when config allows it (avoids leaks). */
if (configAllowed) {
rc = authCtx->checkPasswordCb(usr, authData->sf.password.password,
authData->sf.password.passwordSz, authCtx);
authData->sf.password.passwordSz, authCtx);

if (rc == WSSHD_AUTH_SUCCESS) {
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Password ok.");
}
Expand Down Expand Up @@ -1728,6 +1753,18 @@ static int RequestAuthentication(WS_UserAuthData* authData,
ret = WOLFSSH_USERAUTH_REJECTED;
}

if (ret == WOLFSSH_USERAUTH_SUCCESS &&
authData->type == WOLFSSH_USERAUTH_PUBLICKEY &&
IsRootUser(usr) == 1 &&
wolfSSHD_ConfigGetPermitRoot(usrConf) ==
WOLFSSHD_PERMIT_ROOT_FORCED_CMD &&
wolfSSHD_ConfigGetForcedCmd(usrConf) == NULL) {
/* forced-commands-only requires a forced command for root pubkey login. */
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Public key login for root requires "
"a forced command by configuration!");
ret = WOLFSSH_USERAUTH_REJECTED;
}

#ifdef WOLFSSL_FPKI
if (ret == WOLFSSH_USERAUTH_SUCCESS &&
authData->type == WOLFSSH_USERAUTH_PUBLICKEY) {
Expand Down Expand Up @@ -1956,15 +1993,15 @@ int DefaultUserAuthTypes(WOLFSSH* ssh, void* ctx)
int ret = 0;

if (ssh == NULL || ctx == NULL)
return WS_BAD_ARGUMENT;
return 0;
authCtx = (WOLFSSHD_AUTH*)ctx;

/* get configuration for user */
usr = wolfSSH_GetUsername(ssh);
usrConf = wolfSSHD_AuthGetUserConf(authCtx, usr, NULL, NULL,
NULL, NULL, NULL);
if (usrConf == NULL) {
ret = WS_BAD_ARGUMENT;
ret = 0;
}
else {
ret = wolfSSHD_GetUserAuthTypes(usrConf);
Expand Down
2 changes: 1 addition & 1 deletion apps/wolfsshd/auth.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ int wolfSSHD_GetUserGroupNames(void* heap, const char* usr, WGID_T primaryGid,
void wolfSSHD_FreeUserGroupNames(void* heap, char** names, word32 count);
#endif
#if defined(WOLFSSH_HAVE_LIBCRYPT) || defined(WOLFSSH_HAVE_LIBLOGIN)
int CheckPasswordHashUnix(const char* input, char* stored);
int CheckPasswordHashUnix(const char* input, const char* stored);
#endif
int CheckAuthKeysLine(char* line, word32 lineSz, const byte* key,
word32 keySz);
Expand Down
16 changes: 13 additions & 3 deletions apps/wolfsshd/configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ struct WOLFSSHD_CONFIG {
byte usePrivilegeSeparation:2;
byte passwordAuth:1;
byte pubKeyAuth:1;
byte permitRootLogin:1;
byte permitRootLogin:2;
byte permitEmptyPasswords:1;
byte authKeysFileSet:1; /* if not set then no explicit authorized keys */
byte strictModes:1; /* enforce file permission/ownership checks */
Expand Down Expand Up @@ -555,10 +555,20 @@ static int HandlePermitRoot(WOLFSSHD_CONFIG* conf, const char* value)

if (ret == WS_SUCCESS) {
if (WSTRCMP(value, "no") == 0) {
conf->permitRootLogin = 0;
conf->permitRootLogin = WOLFSSHD_PERMIT_ROOT_NO;
}
else if (WSTRCMP(value, "yes") == 0) {
conf->permitRootLogin = 1;
conf->permitRootLogin = WOLFSSHD_PERMIT_ROOT_YES;
}
else if (WSTRCMP(value, "prohibit-password") == 0 ||
WSTRCMP(value, "without-password") == 0) {
conf->permitRootLogin = WOLFSSHD_PERMIT_ROOT_PROHIBIT_PW;
}
else if (WSTRCMP(value, "forced-commands-only") == 0) {
wolfSSH_Log(WS_LOG_WARN, "[SSHD] PermitRootLogin "
"forced-commands-only: authorized_keys command= "
"restrictions not enforced");
conf->permitRootLogin = WOLFSSHD_PERMIT_ROOT_FORCED_CMD;
}
else {
ret = WS_BAD_ARGUMENT;
Expand Down
8 changes: 8 additions & 0 deletions apps/wolfsshd/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ typedef struct WOLFSSHD_CONFIG WOLFSSHD_CONFIG;
#define MAX_PATH_SZ 80
#endif

/* 0 so that root login is default off after struct memset'd on init */
#define WOLFSSHD_PERMIT_ROOT_NO 0
#define WOLFSSHD_PERMIT_ROOT_YES 1
#define WOLFSSHD_PERMIT_ROOT_PROHIBIT_PW 2
/* Prohibits passwords; pubkey logins require a configured ForceCommand
* (ignores authorized_keys command="..." restrictions). */
#define WOLFSSHD_PERMIT_ROOT_FORCED_CMD 3

WOLFSSHD_CONFIG* wolfSSHD_ConfigNew(void* heap);
void wolfSSHD_ConfigFree(WOLFSSHD_CONFIG* conf);
int wolfSSHD_ConfigLoad(WOLFSSHD_CONFIG* conf, const char* filename);
Expand Down
4 changes: 3 additions & 1 deletion apps/wolfsshd/test/run_all_sshd_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -396,12 +396,14 @@ else
run_test "sshd_window_full_test.sh"
run_test "sshd_empty_password_test.sh"
run_test "sshd_permitroot_test.sh"
run_test "sshd_permitroot_prohibit_password.sh"
run_test "sshd_permitroot_forced_cmd.sh"
run_strictmodes_negative_test
run_test "sshd_login_grace_test.sh"
run_test "sshd_privdrop_fail_test.sh"
else
printf "Skipping tests that need to setup local SSHD\n"
SKIPPED=$((SKIPPED+7))
SKIPPED=$((SKIPPED+9))
fi

# these tests run with X509 sshd-config loaded
Expand Down
129 changes: 129 additions & 0 deletions apps/wolfsshd/test/sshd_permitroot_forced_cmd.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/bin/bash

# Test for PermitRootLogin forced-commands-only enforcement.

if [ -z "$1" ] || [ -z "$2" ]; then
echo "expecting host and port as arguments"
echo "$0 127.0.0.1 22222"
exit 1
fi

TEST_HOST="$1"
TEST_PORT="$2"
USER="root"
PWD=`pwd`

source ./start_sshd.sh

# Build an authorized_keys file for "root" so we can test root public-key
# auth both with and without a ForceCommand configured.
cat ../../../keys/hansel-*.pub > authorized_keys_test_forced_cmd
sed -i.bak "s/hansel/root/" ./authorized_keys_test_forced_cmd

TEST_CLIENT="../../../examples/client/client"

cleanup() {
stop_wolfsshd
rm -f authorized_keys_test_forced_cmd authorized_keys_test_forced_cmd.bak \
sshd_config_test_forced_cmd sshd_config_test_forced_cmd_with_cmd
}
trap cleanup EXIT

### Scenario 1: forced-commands-only with no ForceCommand configured -- both
### root password auth and root pubkey auth must be rejected.
cat <<EOF > sshd_config_test_forced_cmd
Port $TEST_PORT
Protocol 2
LoginGraceTime 600
PermitRootLogin forced-commands-only
PasswordAuthentication yes
AuthorizedKeysFile $PWD/authorized_keys_test_forced_cmd
StrictModes no
UsePrivilegeSeparation no
UseDNS no
HostKey $PWD/../../../keys/server-key.pem
EOF

sudo rm -f ./log.txt

start_wolfsshd "sshd_config_test_forced_cmd"
if [ -z "$PID" ]; then
echo "Failed to start wolfsshd"
exit 1
fi

timeout 10 $TEST_CLIENT -u "$USER" -P "somepassword" -c 'true' \
-h "$TEST_HOST" -p "$TEST_PORT" > /dev/null 2>&1
PASS_RESULT=$?

if [ "$PASS_RESULT" = 0 ]; then
echo "FAIL: root password login unexpectedly succeeded under forced-commands-only"
exit 1
fi

timeout 10 $TEST_CLIENT -u "$USER" -c 'true' \
-i ../../../keys/hansel-key-ecc.der -j ../../../keys/hansel-key-ecc.pub \
-h "$TEST_HOST" -p "$TEST_PORT" > /dev/null 2>&1
NOCMD_PUBKEY_RESULT=$?

if [ "$NOCMD_PUBKEY_RESULT" = 0 ]; then
echo "FAIL: root pubkey login without a ForceCommand unexpectedly succeeded"
exit 1
fi

# Let wolfsshd flush the log before we stop it.
sleep 1

if ! sudo grep -q "Password authentication for root not allowed by configuration!" ./log.txt; then
echo "FAIL: forced-commands-only did not reject root password login as expected"
echo "----- log.txt -----"
sudo cat ./log.txt
exit 1
fi

if ! sudo grep -q "Public key login for root requires a forced command by configuration!" ./log.txt; then
echo "FAIL: forced-commands-only did not reject root pubkey login without a ForceCommand"
echo "----- log.txt -----"
sudo cat ./log.txt
exit 1
fi

stop_wolfsshd

### Scenario 2: forced-commands-only WITH a ForceCommand configured -- root
### pubkey login must now be permitted.
cat <<EOF > sshd_config_test_forced_cmd_with_cmd
Port $TEST_PORT
Protocol 2
LoginGraceTime 600
PermitRootLogin forced-commands-only
ForceCommand true
PasswordAuthentication yes
AuthorizedKeysFile $PWD/authorized_keys_test_forced_cmd
StrictModes no
UsePrivilegeSeparation no
UseDNS no
HostKey $PWD/../../../keys/server-key.pem
EOF

sudo rm -f ./log.txt

start_wolfsshd "sshd_config_test_forced_cmd_with_cmd"
if [ -z "$PID" ]; then
echo "Failed to start wolfsshd"
exit 1
fi

timeout 10 $TEST_CLIENT -u "$USER" -c 'true' \
-i ../../../keys/hansel-key-ecc.der -j ../../../keys/hansel-key-ecc.pub \
-h "$TEST_HOST" -p "$TEST_PORT" > /dev/null 2>&1
WITHCMD_PUBKEY_RESULT=$?

if [ "$WITHCMD_PUBKEY_RESULT" != 0 ]; then
echo "FAIL: root pubkey login with a ForceCommand configured was rejected"
echo "----- log.txt -----"
sudo cat ./log.txt
exit 1
fi

exit 0
Loading
Loading