diff --git a/src/internal.c b/src/internal.c index 11d0b0f2a..22c9989ee 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8458,6 +8458,28 @@ static int DoUserAuthRequestPassword(WOLFSSH* ssh, WS_UserAuthData* authData, } #ifndef WOLFSSH_NO_RSA +/* Utility for the RSA user auth paths. */ +/* returns WS_SUCCESS when the key clears WOLFSSH_RSA_MIN_KEY_BITS. */ +static int CheckRsaKeyBits(RsaKey* key) +{ + int ret = WS_SUCCESS; + int keyBits; + + /* The encrypt size rounds up to a byte; offload builds leave n empty. */ + keyBits = mp_count_bits(&key->n); + if (keyBits == 0) { + keyBits = wc_RsaEncryptSize(key) * 8; + } + + if (keyBits < WOLFSSH_RSA_MIN_KEY_BITS) { + WLOG(WS_LOG_DEBUG, "RSA auth key too small (%d bits)", keyBits); + ret = WS_CERT_KEY_SIZE_E; + } + + return ret; +} + + /* Utility for DoUserAuthRequestPublicKey() */ /* returns negative for error, positive is size of digest. */ static int DoUserAuthRequestRsa(WOLFSSH* ssh, WS_UserAuthData_PublicKey* pk, @@ -8546,6 +8568,10 @@ static int DoUserAuthRequestRsa(WOLFSSH* ssh, WS_UserAuthData_PublicKey* pk, } } + if (ret == WS_SUCCESS) { + ret = CheckRsaKeyBits(key); + } + if (ret == WS_SUCCESS) { i = 0; /* Check that the signature's pubkey type matches the expected one. */ @@ -8705,11 +8731,7 @@ static int DoUserAuthRequestRsaCert(WOLFSSH* ssh, WS_UserAuthData_PublicKey* pk, } if (ret == WS_SUCCESS) { - int keySz = wc_RsaEncryptSize(key) * 8; - if (keySz < 2048) { - WLOG(WS_LOG_DEBUG, "Key size too small (%d)", keySz); - ret = WS_CERT_KEY_SIZE_E; - } + ret = CheckRsaKeyBits(key); } if (ret == WS_SUCCESS) { diff --git a/tests/unit.c b/tests/unit.c index 9dcdfe182..d62c0d6ad 100644 --- a/tests/unit.c +++ b/tests/unit.c @@ -10650,23 +10650,39 @@ static const byte userAuthRsaSigBlob[] = { /* 257: one past the actual signature size */ static const byte userAuthRsaSigLenOverrun[] = { 0x00, 0x00, 0x01, 0x01 }; +/* Offsets of interest in userAuthRsaPubKeyBlob. */ +#define RSA_PUB_BLOB_NLEN 18 /* length of the modulus mpint */ +#define RSA_PUB_BLOB_N 23 /* first byte of the modulus, past the pad */ + +/* 129: the mpint pad byte plus a 1024 bit modulus */ +static const byte userAuthRsaPubKeyNLen1024[] = { 0x00, 0x00, 0x00, 0x81 }; +/* Clears the modulus' top bit, leaving 2047 bits in 256 bytes. */ +static const byte userAuthRsaPubKeyMsb2047[] = { 0x7F }; + typedef struct { const char* name; word32 patchIdx; const byte* patch; word32 patchSz; /* 0 = no patch */ word32 flipIdx; /* XOR 0x01 into this index; 0 = none */ + word32 pubPatchIdx; + const byte* pubPatch; + word32 pubPatchSz; /* 0 = no patch */ int expected; } UserAuthRsaTestVector; static const UserAuthRsaTestVector userAuthRsaTestVectors[] = { - { "high bit signature accepted", 0, NULL, 0, 0, WS_SUCCESS }, + { "high bit signature accepted", 0, NULL, 0, 0, 0, NULL, 0, WS_SUCCESS }, { "corrupt signature rejected", 0, NULL, 0, RSA_SIG_BLOB_SIG + 128, - WS_RSA_E }, + 0, NULL, 0, WS_RSA_E }, { "signature length overrun", RSA_SIG_BLOB_LEN, userAuthRsaSigLenOverrun, - 4, 0, WS_BUFFER_E }, + 4, 0, 0, NULL, 0, WS_BUFFER_E }, { "signature algo name mismatch", RSA_SIG_BLOB_ALGO, - (const byte*)"ssh-dss", 7, 0, WS_INVALID_ALGO_ID }, + (const byte*)"ssh-dss", 7, 0, 0, NULL, 0, WS_INVALID_ALGO_ID }, + { "1024 bit key rejected", 0, NULL, 0, 0, + RSA_PUB_BLOB_NLEN, userAuthRsaPubKeyNLen1024, 4, WS_CERT_KEY_SIZE_E }, + { "2047 bit key rejected", 0, NULL, 0, 0, + RSA_PUB_BLOB_N, userAuthRsaPubKeyMsb2047, 1, WS_CERT_KEY_SIZE_E }, }; static int test_DoUserAuthRequestRsa(void) @@ -10688,6 +10704,15 @@ static int test_DoUserAuthRequestRsa(void) return 1; } + /* The key size vectors patch the modulus in place, so the offsets have + * to still point at a 257 byte mpint holding a 2048 bit modulus. */ + if (userAuthRsaPubKeyBlob[RSA_PUB_BLOB_NLEN + 2] != 0x01 + || userAuthRsaPubKeyBlob[RSA_PUB_BLOB_NLEN + 3] != 0x01 + || (userAuthRsaPubKeyBlob[RSA_PUB_BLOB_N] & 0x80) == 0) { + fprintf(stderr, "\tuserAuthRsaPubKeyBlob offsets are stale\n"); + return 1; + } + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); if (ctx == NULL) return 1; @@ -10699,21 +10724,25 @@ static int test_DoUserAuthRequestRsa(void) for (i = 0, tv = userAuthRsaTestVectors; i < tc; i++, tv++) { byte sigBlob[sizeof(userAuthRsaSigBlob)]; + byte pubBlob[sizeof(userAuthRsaPubKeyBlob)]; byte digest[sizeof(userAuthRsaDigest)]; WS_UserAuthData_PublicKey pk; WMEMCPY(sigBlob, userAuthRsaSigBlob, sizeof(sigBlob)); + WMEMCPY(pubBlob, userAuthRsaPubKeyBlob, sizeof(pubBlob)); WMEMCPY(digest, userAuthRsaDigest, sizeof(digest)); if (tv->patchSz > 0) WMEMCPY(sigBlob + tv->patchIdx, tv->patch, tv->patchSz); if (tv->flipIdx > 0) sigBlob[tv->flipIdx] ^= 0x01; + if (tv->pubPatchSz > 0) + WMEMCPY(pubBlob + tv->pubPatchIdx, tv->pubPatch, tv->pubPatchSz); WMEMSET(&pk, 0, sizeof(pk)); pk.publicKeyType = (const byte*)"ssh-rsa"; pk.publicKeyTypeSz = 7; - pk.publicKey = userAuthRsaPubKeyBlob; - pk.publicKeySz = (word32)sizeof(userAuthRsaPubKeyBlob); + pk.publicKey = pubBlob; + pk.publicKeySz = (word32)sizeof(pubBlob); pk.hasSignature = 1; pk.signature = sigBlob; pk.signatureSz = (word32)sizeof(sigBlob); @@ -10900,6 +10929,11 @@ static int test_DoUserAuthRequestRsaCert(void) byte digest[sizeof(userAuthRsaDigest)]; WS_UserAuthData_PublicKey pk; + /* Vectors that patch the raw public key blob don't apply here; the + * cert path takes its key from a fixed certificate. */ + if (tv->pubPatchSz > 0) + continue; + WMEMCPY(sigBlob, userAuthRsaSigBlob, sizeof(sigBlob)); WMEMCPY(digest, userAuthRsaDigest, sizeof(digest)); if (tv->patchSz > 0) diff --git a/wolfssh/internal.h b/wolfssh/internal.h index 41996031e..8a876bd0c 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -566,6 +566,13 @@ enum NameIdType { #ifndef WOLFSSH_DEFAULT_GEXDH_MIN #define WOLFSSH_DEFAULT_GEXDH_MIN 2048 #endif + +#ifndef WOLFSSH_RSA_MIN_KEY_BITS + /* Minimum accepted RSA public-key size (bits) for user authentication. + * Per NIST SP 800-131A; override at build time if a smaller key must be + * accepted. */ + #define WOLFSSH_RSA_MIN_KEY_BITS 2048 +#endif #ifndef WOLFSSH_DEFAULT_GEXDH_PREFERRED #define WOLFSSH_DEFAULT_GEXDH_PREFERRED 3072 #endif