Skip to content

Commit 253c157

Browse files
yosuke-wolfsslejohnstown
authored andcommitted
wolfsshd: implement PubkeyAuthentication config directive
1 parent 2425f7c commit 253c157

8 files changed

Lines changed: 322 additions & 13 deletions

File tree

apps/wolfsshd/auth.c

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,14 @@ static int RequestAuthentication(WS_UserAuthData* authData,
12531253
}
12541254

12551255

1256+
if (ret == WOLFSSH_USERAUTH_SUCCESS &&
1257+
authData->type == WOLFSSH_USERAUTH_PUBLICKEY &&
1258+
wolfSSHD_ConfigGetPubKeyAuth(usrConf) != 1) {
1259+
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Public key authentication not "
1260+
"allowed by configuration!");
1261+
ret = WOLFSSH_USERAUTH_REJECTED;
1262+
}
1263+
12561264
#ifdef WOLFSSL_FPKI
12571265
if (ret == WOLFSSH_USERAUTH_SUCCESS &&
12581266
authData->type == WOLFSSH_USERAUTH_PUBLICKEY) {
@@ -1449,6 +1457,26 @@ int DefaultUserAuth(byte authType, WS_UserAuthData* authData, void* ctx)
14491457
}
14501458

14511459

1460+
/* Builds the bit mask of authentication methods advertised to a peer based on
1461+
* the resolved per-user configuration. A method is only offered when its
1462+
* corresponding config option is enabled, so PasswordAuthentication no and
1463+
* PubkeyAuthentication no remove the method from the advertisement. Returns 0
1464+
* when both are disabled (no methods advertised). */
1465+
WOLFSSHD_STATIC int wolfSSHD_GetUserAuthTypes(const WOLFSSHD_CONFIG* usrConf)
1466+
{
1467+
int ret = 0;
1468+
1469+
if (wolfSSHD_ConfigGetPwAuth(usrConf) == 1) {
1470+
ret |= WOLFSSH_USERAUTH_PASSWORD;
1471+
}
1472+
if (wolfSSHD_ConfigGetPubKeyAuth(usrConf) == 1) {
1473+
ret |= WOLFSSH_USERAUTH_PUBLICKEY;
1474+
}
1475+
1476+
return ret;
1477+
}
1478+
1479+
14521480
int DefaultUserAuthTypes(WOLFSSH* ssh, void* ctx)
14531481
{
14541482
WOLFSSHD_CONFIG* usrConf;
@@ -1468,10 +1496,7 @@ int DefaultUserAuthTypes(WOLFSSH* ssh, void* ctx)
14681496
ret = WS_BAD_ARGUMENT;
14691497
}
14701498
else {
1471-
if (wolfSSHD_ConfigGetPwAuth(usrConf) == 1) {
1472-
ret |= WOLFSSH_USERAUTH_PASSWORD;
1473-
}
1474-
ret |= WOLFSSH_USERAUTH_PUBLICKEY;
1499+
ret = wolfSSHD_GetUserAuthTypes(usrConf);
14751500
}
14761501

14771502
return ret;

apps/wolfsshd/auth.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,6 @@ int CheckPasswordHashUnix(const char* input, char* stored);
9191
int CheckAuthKeysLine(char* line, word32 lineSz, const byte* key,
9292
word32 keySz);
9393
int CAKeysFileDiffers(const char* a, const char* b);
94+
int wolfSSHD_GetUserAuthTypes(const WOLFSSHD_CONFIG* usrConf);
9495
#endif
9596
#endif /* WOLFAUTH_H */

apps/wolfsshd/configuration.c

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,8 @@
3232
/* functions for parsing out options from a config file and for handling loading
3333
* key/certs using the env. filesystem */
3434

35-
#ifdef WOLFSSHD_UNIT_TEST
36-
#define WOLFSSHD_STATIC
37-
#else
38-
#define WOLFSSHD_STATIC static
39-
#endif
35+
/* WOLFSSHD_STATIC is defined in configuration.h so configuration.c and auth.c
36+
* share the same test-visibility convention. */
4037

4138
#include <wolfssh/ssh.h>
4239
#include <wolfssh/internal.h>
@@ -225,6 +222,7 @@ WOLFSSHD_CONFIG* wolfSSHD_ConfigNew(void* heap)
225222
/* default values */
226223
ret->port = 22;
227224
ret->passwordAuth = 1;
225+
ret->pubKeyAuth = 1;
228226
ret->loginTimer = 120;
229227
}
230228
return ret;
@@ -396,9 +394,10 @@ enum {
396394
OPT_TRUSTED_USER_CA_KEYS = 21,
397395
OPT_PIDFILE = 22,
398396
OPT_BANNER = 23,
397+
OPT_PUBKEY_AUTH = 24,
399398
};
400399
enum {
401-
NUM_OPTIONS = 24
400+
NUM_OPTIONS = 25
402401
};
403402

404403
static const CONFIG_OPTION options[NUM_OPTIONS] = {
@@ -415,6 +414,7 @@ static const CONFIG_OPTION options[NUM_OPTIONS] = {
415414
{OPT_LOGIN_GRACE_TIME, "LoginGraceTime"},
416415
{OPT_HOST_KEY, "HostKey"},
417416
{OPT_PASSWORD_AUTH, "PasswordAuthentication"},
417+
{OPT_PUBKEY_AUTH, "PubkeyAuthentication"},
418418
{OPT_PORT, "Port"},
419419
{OPT_PERMIT_ROOT, "PermitRootLogin"},
420420
{OPT_USE_DNS, "UseDNS"},
@@ -561,6 +561,32 @@ static int HandlePwAuth(WOLFSSHD_CONFIG* conf, const char* value)
561561
return ret;
562562
}
563563

564+
/* returns WS_SUCCESS on success */
565+
static int HandlePubKeyAuth(WOLFSSHD_CONFIG* conf, const char* value)
566+
{
567+
int ret = WS_SUCCESS;
568+
569+
if (conf == NULL || value == NULL) {
570+
ret = WS_BAD_ARGUMENT;
571+
}
572+
573+
if (ret == WS_SUCCESS) {
574+
if (WSTRCMP(value, "no") == 0) {
575+
wolfSSH_Log(WS_LOG_INFO,
576+
"[SSHD] public key authentication disabled");
577+
conf->pubKeyAuth = 0;
578+
}
579+
else if (WSTRCMP(value, "yes") == 0) {
580+
conf->pubKeyAuth = 1;
581+
}
582+
else {
583+
ret = WS_BAD_ARGUMENT;
584+
}
585+
}
586+
587+
return ret;
588+
}
589+
564590
#define WOLFSSH_PROTOCOL_VERSION 2
565591

566592
/* returns WS_SUCCESS on success */
@@ -1115,6 +1141,9 @@ static int HandleConfigOption(WOLFSSHD_CONFIG** conf, int opt,
11151141
case OPT_PASSWORD_AUTH:
11161142
ret = HandlePwAuth(*conf, value);
11171143
break;
1144+
case OPT_PUBKEY_AUTH:
1145+
ret = HandlePubKeyAuth(*conf, value);
1146+
break;
11181147
case OPT_PORT:
11191148
ret = HandlePort(*conf, value);
11201149
break;
@@ -1567,6 +1596,17 @@ byte wolfSSHD_ConfigGetPwAuth(const WOLFSSHD_CONFIG* conf)
15671596
return ret;
15681597
}
15691598

1599+
byte wolfSSHD_ConfigGetPubKeyAuth(const WOLFSSHD_CONFIG* conf)
1600+
{
1601+
byte ret = 0;
1602+
1603+
if (conf != NULL) {
1604+
ret = conf->pubKeyAuth;
1605+
}
1606+
1607+
return ret;
1608+
}
1609+
15701610
byte wolfSSHD_ConfigGetPermitRoot(const WOLFSSHD_CONFIG* conf)
15711611
{
15721612
byte ret = 0;

apps/wolfsshd/configuration.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@
2323

2424
typedef struct WOLFSSHD_CONFIG WOLFSSHD_CONFIG;
2525

26+
/* Expose otherwise-static wolfsshd internals to the unit tests while keeping
27+
* them file-local in normal builds. Shared so configuration.c and auth.c use
28+
* the same convention. */
29+
#ifdef WOLFSSHD_UNIT_TEST
30+
#define WOLFSSHD_STATIC
31+
#else
32+
#define WOLFSSHD_STATIC static
33+
#endif
34+
2635
#include "auth.h"
2736

2837
/* 0 so that privilege separation is default on after struct memset'd on init */
@@ -52,6 +61,7 @@ byte wolfSSHD_ConfigGetPermitRoot(const WOLFSSHD_CONFIG* conf);
5261
byte wolfSSHD_ConfigGetPrivilegeSeparation(const WOLFSSHD_CONFIG* conf);
5362
long wolfSSHD_ConfigGetGraceTime(const WOLFSSHD_CONFIG* conf);
5463
byte wolfSSHD_ConfigGetPwAuth(const WOLFSSHD_CONFIG* conf);
64+
byte wolfSSHD_ConfigGetPubKeyAuth(const WOLFSSHD_CONFIG* conf);
5565
WOLFSSHD_CONFIG* wolfSSHD_GetUserConf(const WOLFSSHD_CONFIG* conf,
5666
const char* usr, const char* grp, const char* host,
5767
const char* localAdr, word16* localPort, const char* RDomain,

apps/wolfsshd/test/test_configuration.c

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ static int test_ConfigDefaults(void)
194194
if (wolfSSHD_ConfigGetPwAuth(conf) == 0)
195195
ret = WS_FATAL_ERROR;
196196
}
197+
if (ret == WS_SUCCESS) {
198+
if (wolfSSHD_ConfigGetPubKeyAuth(conf) == 0)
199+
ret = WS_FATAL_ERROR;
200+
}
197201

198202
wolfSSHD_ConfigFree(conf);
199203
return ret;
@@ -242,6 +246,11 @@ static int test_ParseConfigLine(void)
242246
{"Password auth yes", "PasswordAuthentication yes", 0},
243247
{"Password auth invalid", "PasswordAuthentication wolfsshd", 1},
244248

249+
/* Public key auth tests. */
250+
{"Pubkey auth no", "PubkeyAuthentication no", 0},
251+
{"Pubkey auth yes", "PubkeyAuthentication yes", 0},
252+
{"Pubkey auth invalid", "PubkeyAuthentication wolfsshd", 1},
253+
245254
/* Include files tests. */
246255
{"Include file bad", "Include sshd_config.d/test.bad", 1},
247256
{"Include file exists", "Include sshd_config.d/01-test.conf", 0},
@@ -312,6 +321,9 @@ static int test_ConfigCopy(void)
312321
if (ret == WS_SUCCESS) ret = PCL("Port 2222");
313322
if (ret == WS_SUCCESS) ret = PCL("LoginGraceTime 30");
314323
if (ret == WS_SUCCESS) ret = PCL("PasswordAuthentication yes");
324+
/* set to the non-default value so a dropped copy (which would leave the
325+
* wolfSSHD_ConfigNew default of 1) is caught */
326+
if (ret == WS_SUCCESS) ret = PCL("PubkeyAuthentication no");
315327
if (ret == WS_SUCCESS) ret = PCL("PermitEmptyPasswords yes");
316328
if (ret == WS_SUCCESS) ret = PCL("PermitRootLogin yes");
317329
if (ret == WS_SUCCESS) ret = PCL("UsePrivilegeSeparation sandbox");
@@ -389,6 +401,12 @@ static int test_ConfigCopy(void)
389401
if (wolfSSHD_ConfigGetPwAuth(match) == 0)
390402
ret = WS_FATAL_ERROR;
391403
}
404+
/* pubKeyAuth was set to the non-default 'no' (0) on the head, so the copy
405+
* must carry 0; a dropped copy would surface as the default 1 here */
406+
if (ret == WS_SUCCESS) {
407+
if (wolfSSHD_ConfigGetPubKeyAuth(match) != 0)
408+
ret = WS_FATAL_ERROR;
409+
}
392410
if (ret == WS_SUCCESS) {
393411
if (wolfSSHD_ConfigGetPermitEmptyPw(match) == 0)
394412
ret = WS_FATAL_ERROR;
@@ -448,14 +466,17 @@ static int test_GetUserConfMatchOverride(void)
448466
* global head node unchanged. */
449467
if (ret == WS_SUCCESS) ret = PCL("Match User testuser");
450468
if (ret == WS_SUCCESS) ret = PCL("PasswordAuthentication no");
469+
if (ret == WS_SUCCESS) ret = PCL("PubkeyAuthentication no");
451470
if (ret == WS_SUCCESS) ret = PCL("PermitEmptyPasswords no");
452471
if (ret == WS_SUCCESS) ret = PCL("PermitRootLogin no");
453472
if (ret == WS_SUCCESS) ret = PCL("AuthorizedKeysFile .ssh/match_keys");
454473
#undef PCL
455474

456-
/* the global head node must keep the permissive values */
475+
/* the global head node must keep the permissive values (pubKeyAuth keeps
476+
* its default of 1, proving the Match override did not leak to the head) */
457477
if (ret == WS_SUCCESS) {
458478
if (wolfSSHD_ConfigGetPwAuth(head) != 1 ||
479+
wolfSSHD_ConfigGetPubKeyAuth(head) != 1 ||
459480
wolfSSHD_ConfigGetPermitEmptyPw(head) != 1 ||
460481
wolfSSHD_ConfigGetPermitRoot(head) != 1)
461482
ret = WS_FATAL_ERROR;
@@ -473,6 +494,7 @@ static int test_GetUserConfMatchOverride(void)
473494
* ones RequestAuthentication and DoCheckUser will now enforce */
474495
if (ret == WS_SUCCESS) {
475496
if (wolfSSHD_ConfigGetPwAuth(match) != 0 ||
497+
wolfSSHD_ConfigGetPubKeyAuth(match) != 0 ||
476498
wolfSSHD_ConfigGetPermitEmptyPw(match) != 0 ||
477499
wolfSSHD_ConfigGetPermitRoot(match) != 0)
478500
ret = WS_FATAL_ERROR;
@@ -1005,6 +1027,73 @@ static int test_CAKeysFileDiffers(void)
10051027
return ret;
10061028
}
10071029

1030+
/* Exercises the auth-method advertisement logic used by DefaultUserAuthTypes:
1031+
* a method is only offered when its config option is enabled. Covers all four
1032+
* permutations of PasswordAuthentication and PubkeyAuthentication, including the
1033+
* security-relevant cases where pubkey is disabled and where both are disabled
1034+
* (no methods advertised, mask == 0). */
1035+
static int test_GetUserAuthTypes(void)
1036+
{
1037+
int ret = WS_SUCCESS;
1038+
int i;
1039+
1040+
static const struct {
1041+
const char* desc;
1042+
int pwAuth; /* 1 = leave enabled, 0 = PasswordAuthentication no */
1043+
int pubKeyAuth; /* 1 = leave enabled, 0 = PubkeyAuthentication no */
1044+
int expected;
1045+
} vectors[] = {
1046+
{"both enabled advertises both", 1, 1,
1047+
WOLFSSH_USERAUTH_PASSWORD | WOLFSSH_USERAUTH_PUBLICKEY},
1048+
{"pubkey disabled advertises password only", 1, 0,
1049+
WOLFSSH_USERAUTH_PASSWORD},
1050+
{"password disabled advertises pubkey only", 0, 1,
1051+
WOLFSSH_USERAUTH_PUBLICKEY},
1052+
{"both disabled advertises nothing", 0, 0, 0},
1053+
};
1054+
const int numVectors = (int)(sizeof(vectors) / sizeof(*vectors));
1055+
WOLFSSHD_CONFIG* conf;
1056+
1057+
for (i = 0; i < numVectors && ret == WS_SUCCESS; ++i) {
1058+
Log(" Testing scenario: %s.", vectors[i].desc);
1059+
1060+
conf = wolfSSHD_ConfigNew(NULL);
1061+
if (conf == NULL) {
1062+
Log(" FAILED.\n");
1063+
ret = WS_MEMORY_E;
1064+
break;
1065+
}
1066+
1067+
/* both options default to enabled in wolfSSHD_ConfigNew, so only the
1068+
* disabled cases need an explicit directive */
1069+
if (vectors[i].pwAuth == 0) {
1070+
ret = ParseConfigLine(&conf, "PasswordAuthentication no",
1071+
(int)WSTRLEN("PasswordAuthentication no"), 0);
1072+
}
1073+
if (ret == WS_SUCCESS && vectors[i].pubKeyAuth == 0) {
1074+
ret = ParseConfigLine(&conf, "PubkeyAuthentication no",
1075+
(int)WSTRLEN("PubkeyAuthentication no"), 0);
1076+
}
1077+
1078+
if (ret == WS_SUCCESS) {
1079+
if (wolfSSHD_GetUserAuthTypes(conf) != vectors[i].expected) {
1080+
Log(" FAILED.\n");
1081+
ret = WS_FATAL_ERROR;
1082+
}
1083+
else {
1084+
Log(" PASSED.\n");
1085+
}
1086+
}
1087+
else {
1088+
Log(" FAILED.\n");
1089+
}
1090+
1091+
wolfSSHD_ConfigFree(conf);
1092+
}
1093+
1094+
return ret;
1095+
}
1096+
10081097
const TEST_CASE testCases[] = {
10091098
TEST_DECL(test_ConfigDefaults),
10101099
TEST_DECL(test_ParseConfigLine),
@@ -1013,6 +1102,7 @@ const TEST_CASE testCases[] = {
10131102
TEST_DECL(test_MatchUnsupportedSelector),
10141103
TEST_DECL(test_CAKeysFileDiffers),
10151104
TEST_DECL(test_IncludeRecursionBound),
1105+
TEST_DECL(test_GetUserAuthTypes),
10161106
TEST_DECL(test_ConfigFree),
10171107
#ifdef WOLFSSL_BASE64_ENCODE
10181108
TEST_DECL(test_CheckAuthKeysLine),

src/internal.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16466,6 +16466,7 @@ int SendUserAuthRequest(WOLFSSH* ssh, byte authType, int addSig)
1646616466
static int GetAllowedAuth(WOLFSSH* ssh, char* authStr)
1646716467
{
1646816468
int typeAllowed = 0;
16469+
int authStrSz;
1646916470

1647016471
if (ssh == NULL || authStr == NULL)
1647116472
return WS_BAD_ARGUMENT;
@@ -16496,8 +16497,16 @@ static int GetAllowedAuth(WOLFSSH* ssh, char* authStr)
1649616497
}
1649716498
#endif
1649816499

16499-
/* remove last comma from the list */
16500-
return (int)XSTRLEN(authStr) - 1;
16500+
/* Remove the trailing comma from the list. An empty list (no auth methods
16501+
* allowed, e.g. a server callback that returns 0) has length 0 and must not
16502+
* underflow to a negative size; return 0 so SendUserAuthFailure emits a
16503+
* valid USERAUTH_FAILURE with an empty name-list per RFC 4252. */
16504+
authStrSz = (int)XSTRLEN(authStr);
16505+
if (authStrSz > 0) {
16506+
authStrSz--;
16507+
}
16508+
16509+
return authStrSz;
1650116510
}
1650216511

1650316512
int SendUserAuthFailure(WOLFSSH* ssh, byte partialSuccess)
@@ -18459,6 +18468,11 @@ int wolfSSH_TestDoUserAuthRequest(WOLFSSH* ssh, byte* buf, word32 len,
1845918468
return DoUserAuthRequest(ssh, buf, len, idx);
1846018469
}
1846118470

18471+
int wolfSSH_TestSendUserAuthFailure(WOLFSSH* ssh, byte partialSuccess)
18472+
{
18473+
return SendUserAuthFailure(ssh, partialSuccess);
18474+
}
18475+
1846218476
int wolfSSH_TestHighwaterCheck(WOLFSSH* ssh, byte side)
1846318477
{
1846418478
return HighwaterCheck(ssh, side);

0 commit comments

Comments
 (0)