Skip to content

Commit ccaaa6d

Browse files
committed
internal: sign without server-sig-algs
EXT_INFO is optional, RFC 8308 section 2.2. When the server has sent no server-sig-algs, a publickey USERAUTH_REQUEST now picks the key's signature algorithm from the client's canned list, and an OpenSSH RSA certificate follows the same order. A list that names nothing usable still fails with WS_MATCH_KEY_ALGO_E. - record in peerSigAlgsSeen that server-sig-algs arrived - ClientOsshRsaCertSigId() picks the certificate's name and hash - regress: publickey requests with and without server-sig-algs - regress: the signature is named for the offered algorithm - regress: a list superseding a usable one still refuses Issue: #1286
1 parent 8221e4e commit ccaaa6d

3 files changed

Lines changed: 253 additions & 8 deletions

File tree

‎src/internal.c‎

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9748,6 +9748,9 @@ static int DoExtInfoServerSigAlgs(WOLFSSH* ssh,
97489748
* ids[0] may be ID_UNKNOWN by design, inert downstream. */
97499749
ret = GetNameListRaw(ids, &idsSz, names, namesSz);
97509750

9751+
if (ret == WS_SUCCESS)
9752+
ssh->peerSigAlgsSeen = 1;
9753+
97519754
if (ret == WS_SUCCESS && idsSz == 0) {
97529755
/* No usable names, either an empty list or one that opens with an
97539756
* empty element. Legal, and it advertises nothing. */
@@ -20291,6 +20294,17 @@ static int BuildUserAuthResponseKeyboard(WOLFSSH* ssh, byte* output, word32* idx
2029120294
#endif
2029220295

2029320296
#ifndef WOLFSSH_NO_RSA
20297+
#if defined(WOLFSSH_OSSH_CERTS) && !defined(WOLFSSH_NO_OSSH_CERT_RSA)
20298+
/* The rsa-sha2-* variant for an OpenSSH RSA certificate's name and hash.
20299+
* With no server-sig-algs, the canned order decides, as for ssh-rsa. */
20300+
static byte ClientOsshRsaCertSigId(WOLFSSH* ssh)
20301+
{
20302+
if (ssh->peerSigAlgsSeen)
20303+
return OsshRsaCertSigId(ssh->peerSigId, ssh->peerSigIdSz);
20304+
return OsshRsaCertSigId(cannedKeyAlgoClient, cannedKeyAlgoClientSz);
20305+
}
20306+
#endif
20307+
2029420308
static int PrepareUserAuthRequestRsa(WOLFSSH* ssh, word32* payloadSz,
2029520309
const WS_UserAuthData* authData, WS_KeySignature* keySig)
2029620310
{
@@ -20379,10 +20393,9 @@ static int BuildUserAuthRequestRsa(WOLFSSH* ssh,
2037920393

2038020394
#if defined(WOLFSSH_OSSH_CERTS) && !defined(WOLFSSH_NO_OSSH_CERT_RSA)
2038120395
/* An OpenSSH RSA certificate is dispatched by its cert id, but the
20382-
* signature itself is rsa-sha2-*. Use the strongest variant the server
20383-
* advertised. */
20396+
* signature itself is rsa-sha2-*. */
2038420397
if (keySig->sigId == ID_OSSH_CERT_RSA) {
20385-
effSigId = OsshRsaCertSigId(ssh->peerSigId, ssh->peerSigIdSz);
20398+
effSigId = ClientOsshRsaCertSigId(ssh);
2038620399
}
2038720400
#endif
2038820401

@@ -21942,9 +21955,17 @@ static int PrepareUserAuthRequestPublicKey(WOLFSSH* ssh, word32* payloadSz,
2194221955
algoId[algoIdSz++] = keySig->keyId;
2194321956
}
2194421957

21945-
/* Is that in the peerSigId list? */
21946-
matchId = MatchIdLists(WOLFSSH_ENDPOINT_CLIENT, algoId, algoIdSz,
21947-
ssh->peerSigId, ssh->peerSigIdSz);
21958+
/* Is that in the peerSigId list? With no server-sig-algs, RFC 8308
21959+
* section 2.2, use the canned order, which prefers rsa-sha2-* to
21960+
* ssh-rsa; RFC 8332 section 3.3 allows either. */
21961+
if (ssh->peerSigAlgsSeen) {
21962+
matchId = MatchIdLists(WOLFSSH_ENDPOINT_CLIENT, algoId, algoIdSz,
21963+
ssh->peerSigId, ssh->peerSigIdSz);
21964+
}
21965+
else {
21966+
matchId = MatchIdLists(WOLFSSH_ENDPOINT_CLIENT, algoId, algoIdSz,
21967+
cannedKeyAlgoClient, cannedKeyAlgoClientSz);
21968+
}
2194821969
if (matchId == ID_UNKNOWN) {
2194921970
ret = WS_MATCH_KEY_ALGO_E;
2195021971
}
@@ -21958,8 +21979,7 @@ static int PrepareUserAuthRequestPublicKey(WOLFSSH* ssh, word32* payloadSz,
2195821979
if (keySig->keyId == ID_OSSH_CERT_RSA) {
2195921980
#if !defined(WOLFSSH_NO_RSA_SHA2_256) && \
2196021981
!defined(WOLFSSH_NO_RSA_SHA2_512)
21961-
if (OsshRsaCertSigId(ssh->peerSigId, ssh->peerSigIdSz)
21962-
== ID_RSA_SHA2_512) {
21982+
if (ClientOsshRsaCertSigId(ssh) == ID_RSA_SHA2_512) {
2196321983
keySig->sigName = cannedKeyAlgoOsshRsaSha2_512CertName;
2196421984
}
2196521985
else {

‎tests/regress.c‎

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13761,6 +13761,223 @@ static void TestClientBuffersIdempotent(void)
1376113761
}
1376213762
#endif
1376313763

13764+
#if !defined(NO_WOLFSSH_CLIENT) && !defined(WOLFSSH_NO_PUBKEY_AUTH) \
13765+
&& ((!defined(WOLFSSH_NO_RSA) && !defined(WOLFSSH_NO_RSA_SHA2_256)) \
13766+
|| !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256))
13767+
/* When set, the key type the next publickey request claims for the loaded
13768+
* key. Only the algorithm choice runs before the key is parsed. */
13769+
static const char* pkCaseKeyType = NULL;
13770+
/* When set, a server-sig-algs EXT_INFO sent ahead of the case's own. */
13771+
static const char* pkCasePriorSigAlgs = NULL;
13772+
13773+
static int PkCaseUserAuth(byte authType, WS_UserAuthData* authData,
13774+
void* ctx)
13775+
{
13776+
int ret = ClientUserAuth(authType, authData, ctx);
13777+
13778+
if (ret == WOLFSSH_USERAUTH_SUCCESS && pkCaseKeyType != NULL
13779+
&& authType == WOLFSSH_USERAUTH_PUBLICKEY) {
13780+
authData->sf.publicKey.publicKeyType = (const byte*)pkCaseKeyType;
13781+
authData->sf.publicKey.publicKeyTypeSz =
13782+
(word32)WSTRLEN(pkCaseKeyType);
13783+
}
13784+
return ret;
13785+
}
13786+
13787+
/* Send one publickey USERAUTH_REQUEST from a fresh client session with the
13788+
* key pair ClientUsePubKey() and ClientSetPrivateKey() loaded. A non-NULL
13789+
* extName arrives first in an EXT_INFO with extValue. On success algName
13790+
* gets the public key algorithm name the request offered. */
13791+
static int RunUserAuthPkSigAlgsCase(const char* extName, const char* extValue,
13792+
char* algName, word32 algNameSz)
13793+
{
13794+
WOLFSSH_CTX* ctx;
13795+
WOLFSSH* ssh;
13796+
MemIo io;
13797+
byte out[2048];
13798+
int ret;
13799+
13800+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
13801+
AssertNotNull(ctx);
13802+
wolfSSH_SetIORecv(ctx, MemRecv);
13803+
wolfSSH_SetIOSend(ctx, MemSend);
13804+
wolfSSH_SetUserAuth(ctx, PkCaseUserAuth);
13805+
ssh = wolfSSH_new(ctx);
13806+
AssertNotNull(ssh);
13807+
AssertIntEQ(wolfSSH_SetUsername(ssh, "gretel"), WS_SUCCESS);
13808+
13809+
MemIoInit(&io, NULL, 0, out, (word32)sizeof(out));
13810+
wolfSSH_SetIOReadCtx(ssh, &io);
13811+
wolfSSH_SetIOWriteCtx(ssh, &io);
13812+
ssh->sessionIdSz = WC_SHA256_DIGEST_SIZE;
13813+
WMEMSET(ssh->sessionId, 0x33, ssh->sessionIdSz);
13814+
13815+
if (pkCasePriorSigAlgs != NULL) {
13816+
byte payload[128];
13817+
word32 payloadSz;
13818+
word32 idx = 0;
13819+
13820+
payloadSz = BuildExtInfoSigAlgs(payload, (word32)sizeof(payload),
13821+
pkCasePriorSigAlgs);
13822+
AssertIntEQ(wolfSSH_TestDoExtInfo(ssh, payload, payloadSz, &idx),
13823+
WS_SUCCESS);
13824+
}
13825+
13826+
if (extName != NULL) {
13827+
byte payload[128];
13828+
word32 payloadSz;
13829+
word32 idx = 0;
13830+
13831+
payloadSz = AppendUint32(payload, (word32)sizeof(payload), 0, 1);
13832+
payloadSz = AppendString(payload, (word32)sizeof(payload), payloadSz,
13833+
extName);
13834+
payloadSz = AppendString(payload, (word32)sizeof(payload), payloadSz,
13835+
extValue);
13836+
AssertIntEQ(wolfSSH_TestDoExtInfo(ssh, payload, payloadSz, &idx),
13837+
WS_SUCCESS);
13838+
}
13839+
13840+
algName[0] = '\0';
13841+
ret = SendUserAuthRequest(ssh, WOLFSSH_USERAUTH_PUBLICKEY, 1);
13842+
if (ret == WS_SUCCESS) {
13843+
static const char osshCertSuffix[] = "-cert-v01@openssh.com";
13844+
const byte* payload;
13845+
const byte* field;
13846+
const byte* sig;
13847+
word32 payloadSz;
13848+
word32 fieldSz;
13849+
word32 sigSz;
13850+
word32 nameSz;
13851+
word32 idx = 0;
13852+
byte hasSignature = 0;
13853+
int i;
13854+
13855+
AssertIntEQ(ParseMsgId(io.out, io.outSz), MSGID_USERAUTH_REQUEST);
13856+
payloadSz = ParsePayloadLen(io.out, io.outSz) - MSG_ID_SZ;
13857+
payload = io.out + UINT32_SZ + PAD_LENGTH_SZ + MSG_ID_SZ;
13858+
13859+
/* user name, service name, method name */
13860+
for (i = 0; i < 3; i++) {
13861+
AssertIntEQ(GetStringRef(&fieldSz, &field, payload, payloadSz,
13862+
&idx), WS_SUCCESS);
13863+
}
13864+
AssertIntEQ(GetBoolean(&hasSignature, payload, payloadSz, &idx),
13865+
WS_SUCCESS);
13866+
AssertIntEQ(hasSignature, 1);
13867+
AssertIntEQ(GetStringRef(&fieldSz, &field, payload, payloadSz, &idx),
13868+
WS_SUCCESS);
13869+
AssertTrue(fieldSz < algNameSz);
13870+
WMEMCPY(algName, field, fieldSz);
13871+
algName[fieldSz] = '\0';
13872+
13873+
/* public key blob, then the signature, which ends the payload */
13874+
AssertIntEQ(GetStringRef(&fieldSz, &field, payload, payloadSz, &idx),
13875+
WS_SUCCESS);
13876+
AssertIntEQ(GetStringRef(&sigSz, &sig, payload, payloadSz, &idx),
13877+
WS_SUCCESS);
13878+
AssertIntEQ(idx, payloadSz);
13879+
13880+
/* The signature names the offered algorithm, less a cert suffix. */
13881+
idx = 0;
13882+
AssertIntEQ(GetStringRef(&fieldSz, &field, sig, sigSz, &idx),
13883+
WS_SUCCESS);
13884+
nameSz = (word32)WSTRLEN(algName);
13885+
if (nameSz > sizeof(osshCertSuffix) - 1
13886+
&& WSTRCMP(algName + nameSz - (sizeof(osshCertSuffix) - 1),
13887+
osshCertSuffix) == 0) {
13888+
nameSz -= (word32)(sizeof(osshCertSuffix) - 1);
13889+
}
13890+
AssertIntEQ(fieldSz, nameSz);
13891+
AssertIntEQ(WMEMCMP(field, algName, nameSz), 0);
13892+
}
13893+
else {
13894+
AssertIntEQ(io.outSz, 0);
13895+
}
13896+
13897+
wolfSSH_free(ssh);
13898+
wolfSSH_CTX_free(ctx);
13899+
13900+
return ret;
13901+
}
13902+
13903+
/* With no server-sig-algs, RFC 8308 section 2.2, the client uses its own
13904+
* order; with one, only what it lists. */
13905+
static void TestUserAuthPkWithoutServerSigAlgs(void)
13906+
{
13907+
char algName[64];
13908+
13909+
#if !defined(WOLFSSH_NO_RSA) && !defined(WOLFSSH_NO_RSA_SHA2_256)
13910+
#ifndef WOLFSSH_NO_RSA_SHA2_512
13911+
const char* rsaFirst = "rsa-sha2-512";
13912+
#else
13913+
const char* rsaFirst = "rsa-sha2-256";
13914+
#endif
13915+
13916+
AssertIntEQ(ClientUsePubKey("keys/gretel-key-rsa.pub"), 0);
13917+
AssertIntEQ(ClientSetPrivateKey("keys/gretel-key-rsa.pem"), 0);
13918+
13919+
AssertIntEQ(RunUserAuthPkSigAlgsCase(NULL, NULL, algName,
13920+
sizeof(algName)), WS_SUCCESS);
13921+
AssertStrEQ(algName, rsaFirst);
13922+
13923+
/* An EXT_INFO without server-sig-algs lists nothing. */
13924+
AssertIntEQ(RunUserAuthPkSigAlgsCase("ping@openssh.com", "0", algName,
13925+
sizeof(algName)), WS_SUCCESS);
13926+
AssertStrEQ(algName, rsaFirst);
13927+
13928+
AssertIntEQ(RunUserAuthPkSigAlgsCase("server-sig-algs", "rsa-sha2-256",
13929+
algName, sizeof(algName)), WS_SUCCESS);
13930+
AssertStrEQ(algName, "rsa-sha2-256");
13931+
13932+
/* Listed but nothing usable is still a mismatch. */
13933+
AssertIntEQ(RunUserAuthPkSigAlgsCase("server-sig-algs", "bogus",
13934+
algName, sizeof(algName)), WS_MATCH_KEY_ALGO_E);
13935+
AssertIntEQ(RunUserAuthPkSigAlgsCase("server-sig-algs", "",
13936+
algName, sizeof(algName)), WS_MATCH_KEY_ALGO_E);
13937+
13938+
/* Nor when it replaces a usable one, RFC 8308 section 2.4. */
13939+
pkCasePriorSigAlgs = "rsa-sha2-256";
13940+
AssertIntEQ(RunUserAuthPkSigAlgsCase("server-sig-algs", "bogus",
13941+
algName, sizeof(algName)), WS_MATCH_KEY_ALGO_E);
13942+
AssertIntEQ(RunUserAuthPkSigAlgsCase("server-sig-algs", "",
13943+
algName, sizeof(algName)), WS_MATCH_KEY_ALGO_E);
13944+
pkCasePriorSigAlgs = NULL;
13945+
13946+
#if defined(WOLFSSH_CERTS) && !defined(WOLFSSH_NO_SSH_RSA_SHA1) \
13947+
&& !defined(WOLFSSH_NO_SHA1_SOFT_DISABLE)
13948+
/* Our own choice skips soft-disabled SHA-1. */
13949+
pkCaseKeyType = "x509v3-ssh-rsa";
13950+
AssertIntEQ(RunUserAuthPkSigAlgsCase(NULL, NULL, algName,
13951+
sizeof(algName)), WS_MATCH_KEY_ALGO_E);
13952+
pkCaseKeyType = NULL;
13953+
#endif
13954+
13955+
#if defined(WOLFSSH_OSSH_CERTS) && !defined(WOLFSSH_NO_OSSH_CERT_RSA)
13956+
/* An OpenSSH RSA certificate falls back the same way. */
13957+
pkCaseKeyType = "ssh-rsa-cert-v01@openssh.com";
13958+
AssertIntEQ(RunUserAuthPkSigAlgsCase(NULL, NULL, algName,
13959+
sizeof(algName)), WS_SUCCESS);
13960+
AssertIntEQ(WSTRNCMP(algName, rsaFirst, WSTRLEN(rsaFirst)), 0);
13961+
AssertStrEQ(algName + WSTRLEN(rsaFirst), "-cert-v01@openssh.com");
13962+
pkCaseKeyType = NULL;
13963+
#endif
13964+
13965+
ClientFreeBuffers();
13966+
#endif
13967+
13968+
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
13969+
AssertIntEQ(ClientUsePubKey("keys/gretel-key-ecc.pub"), 0);
13970+
AssertIntEQ(ClientSetPrivateKey("keys/gretel-key-ecc.pem"), 0);
13971+
13972+
AssertIntEQ(RunUserAuthPkSigAlgsCase(NULL, NULL, algName,
13973+
sizeof(algName)), WS_SUCCESS);
13974+
AssertStrEQ(algName, "ecdsa-sha2-nistp256");
13975+
13976+
ClientFreeBuffers();
13977+
#endif
13978+
}
13979+
#endif
13980+
1376413981
/* Windows has no /dev/null; the null device there is "NUL". Keyed on
1376513982
* _WIN32, like the arpa/inet.h/direct.h include guard above: this is a
1376613983
* libc-availability question, not a wolfSSH API-selection one. */
@@ -16283,6 +16500,8 @@ static void TestExtInfoSigAlgsReplace(void)
1628316500
WS_SUCCESS);
1628416501
AssertIntEQ(ssh->peerSigIdSz, 0);
1628516502
AssertNull(ssh->peerSigId);
16503+
/* Still seen, so no fallback to the canned list. */
16504+
AssertIntEQ(ssh->peerSigAlgsSeen, 1);
1628616505

1628716506
/* Same for an empty list, from a good one again. */
1628816507
idx = 0;
@@ -18621,6 +18840,11 @@ int main(int argc, char** argv)
1862118840
#endif
1862218841
#if !(defined(WOLFSSH_NO_RSA) && defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256))
1862318842
TestClientBuffersIdempotent();
18843+
#endif
18844+
#if !defined(NO_WOLFSSH_CLIENT) && !defined(WOLFSSH_NO_PUBKEY_AUTH) \
18845+
&& ((!defined(WOLFSSH_NO_RSA) && !defined(WOLFSSH_NO_RSA_SHA2_256)) \
18846+
|| !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256))
18847+
TestUserAuthPkWithoutServerSigAlgs();
1862418848
#endif
1862518849
TestPasswordEofNoCrash();
1862618850
#ifndef NO_WOLFSSH_CLIENT

‎wolfssh/internal.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1358,6 +1358,7 @@ struct WOLFSSH {
13581358
byte sendStrictKex; /* offer strict KEX on initial KEXINIT */
13591359
byte useStrictKex; /* strict KEX negotiated for this session */
13601360
byte initialKexDone; /* peer initial KEX finished (NEWKEYS arrived) */
1361+
byte peerSigAlgsSeen; /* peer sent server-sig-algs, even an unusable one */
13611362
byte* peerSigId;
13621363
word32 peerSigIdSz;
13631364

0 commit comments

Comments
 (0)