Skip to content

Commit c9c4c1f

Browse files
ejohnstownphilljj
authored andcommitted
Check the format name in host key blobs
The host key parsers compare the format identifier at the head of the key blob (RFC 4253 section 6.6) against the expected name and return WS_INVALID_ALGO_ID on a mismatch, as ParseECCPubKey() already does. RSA blobs must say "ssh-rsa", Ed25519 "ssh-ed25519", and ML-DSA the negotiated algorithm name. - add GetPubKeyFormat() and use it in place of GetSkip() - add the wolfSSH_TestParseMlDsaPubKey() hook - test a mismatched name for each key type Issue: F-15666
1 parent ff0b5e2 commit c9c4c1f

3 files changed

Lines changed: 163 additions & 6 deletions

File tree

‎src/internal.c‎

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7619,6 +7619,30 @@ static int CheckRsaKeyBits(RsaKey* key)
76197619
#endif
76207620

76217621

7622+
#if !defined(WOLFSSH_NO_RSA) || !defined(WOLFSSH_NO_ED25519) \
7623+
|| !defined(WOLFSSH_NO_MLDSA)
7624+
/* Compare the format identifier at the start of a public key blob
7625+
* against the expected name (RFC 4253 section 6.6). */
7626+
static int GetPubKeyFormat(const char* expected, const byte* pubKey,
7627+
word32 pubKeySz, word32* pubKeyIdx)
7628+
{
7629+
const byte* name = NULL;
7630+
word32 nameSz = 0;
7631+
int ret;
7632+
7633+
ret = GetStringRef(&nameSz, &name, pubKey, pubKeySz, pubKeyIdx);
7634+
if (ret == WS_SUCCESS) {
7635+
if (nameSz != (word32)WSTRLEN(expected)
7636+
|| WMEMCMP(name, expected, nameSz) != 0) {
7637+
ret = WS_INVALID_ALGO_ID;
7638+
}
7639+
}
7640+
7641+
return ret;
7642+
}
7643+
#endif
7644+
7645+
76227646
/* Parse out a RAW RSA public key from buffer */
76237647
static int ParseRSAPubKey(WOLFSSH *ssh,
76247648
struct wolfSSH_sigKeyBlock *sigKeyBlock_ptr, byte *pubKey, word32 pubKeySz)
@@ -7632,9 +7656,9 @@ static int ParseRSAPubKey(WOLFSSH *ssh,
76327656
word32 pubKeyIdx = 0;
76337657

76347658
ret = InitPubKey(sigKeyBlock_ptr, ssh);
7635-
/* Skip the algo name. */
7659+
/* All three RSA host key algorithms use the "ssh-rsa" format. */
76367660
if (ret == WS_SUCCESS)
7637-
ret = GetSkip(pubKey, pubKeySz, &pubKeyIdx);
7661+
ret = GetPubKeyFormat("ssh-rsa", pubKey, pubKeySz, &pubKeyIdx);
76387662
if (ret == WS_SUCCESS) {
76397663
ret = GetUint32(&eSz, pubKey, pubKeySz, &pubKeyIdx);
76407664
if (ret == WS_SUCCESS && eSz > pubKeySz - pubKeyIdx)
@@ -7775,9 +7799,8 @@ static int ParseEd25519PubKey(WOLFSSH *ssh,
77757799

77767800
ret = InitPubKey(sigKeyBlock_ptr, ssh);
77777801

7778-
/* Skip the algo name */
77797802
if (ret == WS_SUCCESS) {
7780-
ret = GetSkip(pubKey, pubKeySz, &pubKeyIdx);
7803+
ret = GetPubKeyFormat("ssh-ed25519", pubKey, pubKeySz, &pubKeyIdx);
77817804
}
77827805

77837806
if (ret == WS_SUCCESS) {
@@ -7859,8 +7882,7 @@ static int ParseMlDsaPubKey(WOLFSSH* ssh,
78597882
return WS_INVALID_ALGO_ID;
78607883
}
78617884

7862-
/* skip the algo name string */
7863-
ret = GetSkip(pubKey, pubKeySz, &pubKeyIdx);
7885+
ret = GetPubKeyFormat(IdToName(keyId), pubKey, pubKeySz, &pubKeyIdx);
78647886
if (ret == WS_SUCCESS)
78657887
ret = GetStringRef(&pubSz, &pub, pubKey, pubKeySz, &pubKeyIdx);
78667888
if (ret == WS_SUCCESS)
@@ -26874,6 +26896,20 @@ int wolfSSH_TestDoUserAuthRequestEd25519(WOLFSSH* ssh,
2687426896

2687526897
#ifndef WOLFSSH_NO_MLDSA
2687626898

26899+
int wolfSSH_TestParseMlDsaPubKey(WOLFSSH* ssh, byte* pubKey, word32 pubKeySz,
26900+
byte keyId)
26901+
{
26902+
struct wolfSSH_sigKeyBlock sigKeyBlock;
26903+
int ret;
26904+
26905+
WMEMSET(&sigKeyBlock, 0, sizeof(sigKeyBlock));
26906+
sigKeyBlock.useMlDsa = 1;
26907+
ret = ParseMlDsaPubKey(ssh, &sigKeyBlock, pubKey, pubKeySz, keyId);
26908+
FreePubKey(&sigKeyBlock);
26909+
26910+
return ret;
26911+
}
26912+
2687726913
WOLFSSH_API int wolfSSH_TestDoUserAuthRequestMlDsa(WOLFSSH* ssh,
2687826914
WS_UserAuthData* authData, word32 pubKeyBlobSz)
2687926915
{

‎tests/unit.c‎

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14898,6 +14898,15 @@ static int test_ParseEd25519PubKey(void)
1489814898
failures++;
1489914899
}
1490014900

14901+
/* format identifier other than "ssh-ed25519" */
14902+
blob[UINT32_SZ + keyTypeNameSz - 1] = '8';
14903+
ret = wolfSSH_TestParseEd25519PubKey(ssh, blob, blobSz);
14904+
if (ret != WS_INVALID_ALGO_ID) {
14905+
fprintf(stderr, "\t\"format mismatch\" FAIL: got %d, expected %d\n",
14906+
ret, WS_INVALID_ALGO_ID);
14907+
failures++;
14908+
}
14909+
1490114910
wolfSSH_free(ssh);
1490214911
wolfSSH_CTX_free(ctx);
1490314912

@@ -15931,6 +15940,99 @@ static int test_DoUserAuthRequestMlDsa(void)
1593115940
return 0;
1593215941
}
1593315942

15943+
#ifndef WOLFSSH_NO_MLDSA44
15944+
/* Host key blob parse for ssh-mldsa-44. The format identifier must match
15945+
* the negotiated algorithm (RFC 4253 section 6.6). */
15946+
static int test_ParseMlDsaPubKey(void)
15947+
{
15948+
static const char keyTypeName[] = "ssh-mldsa-44";
15949+
const word32 keyTypeNameSz = (word32)(sizeof(keyTypeName) - 1);
15950+
WOLFSSH_CTX* ctx = NULL;
15951+
WOLFSSH* ssh = NULL;
15952+
MlDsaKey key;
15953+
int keyInit = 0;
15954+
WC_RNG rng;
15955+
int rngInit = 0;
15956+
byte blob[UINT32_SZ * 2 + sizeof(keyTypeName) - 1
15957+
+ WC_MLDSA_44_PUB_KEY_SIZE];
15958+
word32 pubSz = WC_MLDSA_44_PUB_KEY_SIZE;
15959+
word32 blobSz;
15960+
int ret;
15961+
int result = 0;
15962+
15963+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
15964+
if (ctx == NULL)
15965+
return -780;
15966+
ssh = wolfSSH_new(ctx);
15967+
if (ssh == NULL) {
15968+
result = -781;
15969+
goto done;
15970+
}
15971+
15972+
if (wc_InitRng(&rng) != 0) {
15973+
result = -782;
15974+
goto done;
15975+
}
15976+
rngInit = 1;
15977+
if (wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID) != 0) {
15978+
result = -783;
15979+
goto done;
15980+
}
15981+
keyInit = 1;
15982+
if (wc_MlDsaKey_SetParams(&key, WC_ML_DSA_44) != 0
15983+
|| wc_MlDsaKey_MakeKey(&key, &rng) != 0) {
15984+
result = -784;
15985+
goto done;
15986+
}
15987+
15988+
/* string "ssh-mldsa-44" || string pubkey */
15989+
PutU32BE(blob, keyTypeNameSz);
15990+
WMEMCPY(blob + UINT32_SZ, keyTypeName, keyTypeNameSz);
15991+
if (wc_MlDsaKey_ExportPubRaw(&key,
15992+
blob + UINT32_SZ * 2 + keyTypeNameSz, &pubSz) != 0) {
15993+
result = -785;
15994+
goto done;
15995+
}
15996+
PutU32BE(blob + UINT32_SZ + keyTypeNameSz, pubSz);
15997+
blobSz = UINT32_SZ * 2 + keyTypeNameSz + pubSz;
15998+
15999+
ret = wolfSSH_TestParseMlDsaPubKey(ssh, blob, blobSz, ID_MLDSA44);
16000+
if (ret != WS_SUCCESS) {
16001+
fprintf(stderr, "\t\"valid\" FAIL: got %d, expected %d\n",
16002+
ret, WS_SUCCESS);
16003+
result = -786;
16004+
goto done;
16005+
}
16006+
16007+
ret = wolfSSH_TestParseMlDsaPubKey(ssh, blob, blobSz - 4, ID_MLDSA44);
16008+
if (ret == WS_SUCCESS) {
16009+
fprintf(stderr, "\t\"truncated\" FAIL: got %d\n", ret);
16010+
result = -787;
16011+
goto done;
16012+
}
16013+
16014+
/* format identifier other than "ssh-mldsa-44" */
16015+
blob[UINT32_SZ + keyTypeNameSz - 1] = '5';
16016+
ret = wolfSSH_TestParseMlDsaPubKey(ssh, blob, blobSz, ID_MLDSA44);
16017+
if (ret != WS_INVALID_ALGO_ID) {
16018+
fprintf(stderr, "\t\"format mismatch\" FAIL: got %d, expected %d\n",
16019+
ret, WS_INVALID_ALGO_ID);
16020+
result = -788;
16021+
goto done;
16022+
}
16023+
16024+
done:
16025+
if (keyInit)
16026+
wc_MlDsaKey_Free(&key);
16027+
if (rngInit)
16028+
wc_FreeRng(&rng);
16029+
if (ssh != NULL)
16030+
wolfSSH_free(ssh);
16031+
wolfSSH_CTX_free(ctx);
16032+
return result;
16033+
}
16034+
#endif /* !WOLFSSH_NO_MLDSA44 */
16035+
1593416036
/* tamperSig: 0 = no tampering, 1 = flip a byte in the trad-algorithm
1593516037
* signature component, 2 = flip a byte in the ML-DSA signature component,
1593616038
* 3 = claim a composite signature length shorter than the ML-DSA half
@@ -22717,6 +22819,7 @@ static const byte userAuthRsaSigBlob[] = {
2271722819
static const byte userAuthRsaSigLenOverrun[] = { 0x00, 0x00, 0x01, 0x01 };
2271822820

2271922821
/* Offsets of interest in userAuthRsaPubKeyBlob. */
22822+
#define RSA_PUB_BLOB_ALGO 4 /* "ssh-rsa" */
2272022823
#define RSA_PUB_BLOB_NLEN 18 /* length of the modulus mpint */
2272122824
#define RSA_PUB_BLOB_N 23 /* first byte of the modulus, past the pad */
2272222825

@@ -22883,6 +22986,16 @@ static int test_ParseRSAPubKey(void)
2288322986
failures++;
2288422987
}
2288522988

22989+
/* format identifier other than "ssh-rsa" */
22990+
WMEMCPY(blob, userAuthRsaPubKeyBlob, sizeof(blob));
22991+
WMEMCPY(blob + RSA_PUB_BLOB_ALGO, "ssh-dss", 7);
22992+
ret = wolfSSH_TestParseRSAPubKey(ssh, blob, (word32)sizeof(blob));
22993+
if (ret != WS_INVALID_ALGO_ID) {
22994+
fprintf(stderr, "\t\"format mismatch\" FAIL: got %d, expected %d\n",
22995+
ret, WS_INVALID_ALGO_ID);
22996+
failures++;
22997+
}
22998+
2288622999
wolfSSH_free(ssh);
2288723000
wolfSSH_CTX_free(ctx);
2288823001

@@ -26083,6 +26196,12 @@ int wolfSSH_UnitTest(int argc, char** argv)
2608326196
printf("DoUserAuthRequestMlDsa: %s (result=%d)\n",
2608426197
(unitResult == 0 ? "SUCCESS" : "FAILED"), unitResult);
2608526198
testResult = testResult || (unitResult != 0);
26199+
#ifndef WOLFSSH_NO_MLDSA44
26200+
unitResult = test_ParseMlDsaPubKey();
26201+
printf("ParseMlDsaPubKey: %s (result=%d)\n",
26202+
(unitResult == 0 ? "SUCCESS" : "FAILED"), unitResult);
26203+
testResult = testResult || (unitResult != 0);
26204+
#endif
2608626205
#ifndef WOLFSSH_NO_MLDSA_COMPOSITES
2608726206
unitResult = test_DoUserAuthRequestMlDsaComposite();
2608826207
printf("DoUserAuthRequestMlDsaComposite: %s (result=%d)\n",

‎wolfssh/internal.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2258,6 +2258,8 @@ enum WS_MessageIdLimits {
22582258
WS_UserAuthData* authData);
22592259
#endif /* !WOLFSSH_NO_ED25519 */
22602260
#ifndef WOLFSSH_NO_MLDSA
2261+
WOLFSSH_API int wolfSSH_TestParseMlDsaPubKey(WOLFSSH* ssh, byte* pubKey,
2262+
word32 pubKeySz, byte keyId);
22612263
WOLFSSH_API int wolfSSH_TestDoUserAuthRequestMlDsa(WOLFSSH* ssh,
22622264
WS_UserAuthData* authData, word32 pubKeyBlobSz);
22632265
WOLFSSH_API int wolfSSH_TestPrepareUserAuthRequestMlDsa(WOLFSSH* ssh,

0 commit comments

Comments
 (0)