@@ -14701,6 +14701,21 @@ static INLINE byte SigTypeForId(byte id)
1470114701
1470214702
1470314703#ifndef WOLFSSH_NO_RSA
14704+ /* Big-endian magnitude compare. Not memcmp(): Zephyr's minimal libc walks it
14705+ * as signed char, which orders a modulus's high byte backwards. */
14706+ static int BigEndianCompare(const byte* a, const byte* b, word32 sz)
14707+ {
14708+ word32 idx;
14709+
14710+ for (idx = 0; idx < sz; idx++) {
14711+ if (a[idx] != b[idx]) {
14712+ return (a[idx] > b[idx]) ? 1 : -1;
14713+ }
14714+ }
14715+ return 0;
14716+ }
14717+
14718+
1470414719/*
1470514720 * wolfSSH_RsaVerify
1470614721 * sig - signature to verify
@@ -14711,43 +14726,54 @@ static INLINE byte SigTypeForId(byte id)
1471114726 * heap - allocation heap
1471214727 * loc - calling function for logging
1471314728 *
14714- * Takes the provided digest of type digestId and converts it to an
14715- * encoded digest. Then verifies the signature, comparing the output
14716- * digest and compares it .
14729+ * Rebuilds the expected EMSA-PKCS1-v1_5 block from encDigest, applies the
14730+ * public key to the signature, and compares the two blocks whole. Per RFC
14731+ * 8332 section 3, the recovered value is never parsed .
1471714732 */
1471814733int wolfSSH_RsaVerify(const byte *sig, word32 sigSz,
1471914734 const byte* encDigest, word32 encDigestSz,
1472014735 RsaKey* key, void* heap, const char* loc)
1472114736{
14737+ byte* scratch = NULL;
1472214738 byte* checkSig = NULL;
14723- int checkDigestSz;
14739+ byte* checkBlock = NULL;
14740+ byte* encBlock = NULL;
14741+ byte* nBlock = NULL;
1472414742 word32 keySz;
14743+ int encryptSz;
1472514744 int ret = WS_SUCCESS;
14726- #ifdef WOLFSSH_SMALL_STACK
14727- byte* checkDigest = NULL;
14728- #else
14729- byte checkDigest[MAX_ENCODED_SIG_SZ];
14730- #endif
1473114745
1473214746 if (sig == NULL) {
1473314747 WLOG(WS_LOG_DEBUG, "%s: %s", loc, "Missing RSA signature");
1473414748 return WS_RSA_E;
1473514749 }
1473614750
14737- keySz = (word32)wc_RsaEncryptSize(key);
14751+ encryptSz = wc_RsaEncryptSize(key);
14752+ if (encryptSz <= 0) {
14753+ WLOG(WS_LOG_DEBUG, "%s: %s", loc, "Bad RSA key size");
14754+ return WS_RSA_E;
14755+ }
14756+ keySz = (word32)encryptSz;
1473814757
14739- if (ret == WS_SUCCESS) {
14740- checkSig = (byte*)WMALLOC(keySz, heap, DYNTYPE_TEMP);
14741- if (checkSig == NULL)
14742- ret = WS_MEMORY_E ;
14758+ /* Room for the header, the trailing zero, and eight pad bytes. */
14759+ if (keySz < encDigestSz + RSA_MIN_PAD_SZ) {
14760+ WLOG(WS_LOG_DEBUG, "%s: %s", loc, "RSA key too small for the digest");
14761+ return WS_RSA_E ;
1474314762 }
14744- #ifdef WOLFSSH_SMALL_STACK
14763+
14764+ /* One block each: normalized signature, recovered value, expected
14765+ * encoding, modulus. */
1474514766 if (ret == WS_SUCCESS) {
14746- checkDigest = (byte*)WMALLOC(MAX_ENCODED_SIG_SZ , heap, DYNTYPE_TEMP);
14747- if (checkDigest == NULL)
14767+ scratch = (byte*)WMALLOC(keySz * 4 , heap, DYNTYPE_TEMP);
14768+ if (scratch == NULL)
1474814769 ret = WS_MEMORY_E;
14770+ else {
14771+ checkSig = scratch;
14772+ checkBlock = scratch + keySz;
14773+ encBlock = scratch + (keySz * 2);
14774+ nBlock = scratch + (keySz * 3);
14775+ }
1474914776 }
14750- #endif
1475114777
1475214778 /* Normalize the peer's signature. Some SSH implementations remove
1475314779 * leading zeros on the signatures they encode. We need to pad the
@@ -14767,28 +14793,55 @@ int wolfSSH_RsaVerify(const byte *sig, word32 sigSz,
1476714793 WMEMCPY(checkSig + offset, sig, sigSz);
1476814794 }
1476914795
14796+ /* SP 800-56B wants 1 < s < n-1. wc_RsaFunction() only checks that for a
14797+ * decrypt, and the modexp would reduce s mod n, so s + n verifies as a
14798+ * second encoding of the same signature. Offload builds leave n empty and
14799+ * have nothing to compare against. */
14800+ if (ret == WS_SUCCESS && mp_count_bits(&key->n) != 0) {
14801+ word32 eSz = keySz, nSz = keySz;
14802+
14803+ /* checkBlock is scratch until the modexp below fills it. */
14804+ if (wc_RsaFlattenPublicKey(key, checkBlock, &eSz, nBlock, &nSz) != 0
14805+ || nSz != keySz) {
14806+ WLOG(WS_LOG_DEBUG, "%s: %s", loc, "Bad RSA public key");
14807+ ret = WS_RSA_E;
14808+ }
14809+ else if (BigEndianCompare(checkSig, nBlock, keySz) >= 0) {
14810+ WLOG(WS_LOG_DEBUG, "%s: %s", loc, "RSA signature out of range");
14811+ ret = WS_RSA_E;
14812+ }
14813+ }
14814+
14815+ /* Expected block: 0x00 0x01 || 0xFF pad || 0x00 || DigestInfo. */
1477014816 if (ret == WS_SUCCESS) {
14771- volatile int sizeCompare;
14772- volatile int compare;
14817+ word32 padSz = keySz - encDigestSz - 3;
14818+ word32 idx = 0;
14819+
14820+ encBlock[idx++] = 0x00;
14821+ encBlock[idx++] = 0x01;
14822+ WMEMSET(encBlock + idx, 0xFF, padSz);
14823+ idx += padSz;
14824+ encBlock[idx++] = 0x00;
14825+ WMEMCPY(encBlock + idx, encDigest, encDigestSz);
14826+ }
1477314827
14774- checkDigestSz = wc_RsaSSL_Verify(checkSig, keySz,
14775- checkDigest, MAX_ENCODED_SIG_SZ, key);
14828+ /* RFC 8332 section 3: compare whole blocks, don't parse. */
14829+ if (ret == WS_SUCCESS) {
14830+ word32 checkBlockSz = keySz;
14831+ int rsaRet;
1477614832
14777- sizeCompare = checkDigestSz > 0 && encDigestSz != (word32)checkDigestSz;
14778- compare = ConstantCompare(encDigest, checkDigest, encDigestSz );
14833+ rsaRet = wc_RsaFunction(checkSig, keySz, checkBlock, &checkBlockSz,
14834+ RSA_PUBLIC_ENCRYPT, key, NULL );
1477914835
14780- if (checkDigestSz < 0 || sizeCompare || compare) {
14836+ if (rsaRet != 0 || checkBlockSz != keySz
14837+ || ConstantCompare(checkBlock, encBlock, keySz) != 0) {
1478114838 WLOG(WS_LOG_DEBUG, "%s: %s", loc, "Bad RSA Verify");
1478214839 ret = WS_RSA_E;
1478314840 }
1478414841 }
1478514842
14786- #ifdef WOLFSSH_SMALL_STACK
14787- if (checkDigest)
14788- WFREE(checkDigest, heap, DYNTYPE_TEMP);
14789- #endif
14790- if (checkSig)
14791- WFREE(checkSig, heap, DYNTYPE_TEMP);
14843+ if (scratch)
14844+ WFREE(scratch, heap, DYNTYPE_TEMP);
1479214845 WOLFSSH_UNUSED(loc); /* Unused when WLOG is not defined */
1479314846 return ret;
1479414847}
0 commit comments