@@ -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
0 commit comments