Skip to content

Commit 2aee1ac

Browse files
committed
RSA OAEP unpad: make separator search constant time
The separator scan in RsaUnPad_OAEP() used a 0/1 mask that Clang turns into a branch on each byte being zero on PowerPC, Thumb-1 and x86_64 when optimizing. This leaked which bytes are zero and, through the loop timing, the separator position. The separator byte was then loaded by its index, putting the separator position on the address bus. Find the separator with one masked pass that records its index and value, so no load depends on the separator position. Pass the byte and the per-byte mask through volatiles so the compiler can't tell the byte is less than 256 or that the mask is either 0 or all ones, which is what let it turn the masking back into a branch. Clear the volatile copies after the scan so the last decoded byte does not outlive the block. Reject blocks longer than RSA_MAX_SIZE bits in RsaUnPad_OAEP(), as RsaUnPad_PSS() already does. Without WOLFSSL_SMALL_STACK the scratch buffer is sized for RSA_MAX_SIZE, and wc_RsaUnPad_ex() could be given a larger block. Add tests for the separator at every position, malformed padding, labels, zero-length results with and without WOLFSSL_RSA_DECRYPT_TO_0_LEN, and the block length limit.
1 parent 80c58da commit 2aee1ac

3 files changed

Lines changed: 406 additions & 11 deletions

File tree

‎tests/api/test_rsa.c‎

Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2819,3 +2819,360 @@ int test_wc_FreeRsaKey_CryptoCb(void)
28192819
#endif
28202820
return EXPECT_RESULT();
28212821
}
2822+
2823+
/* WOLFSSL_NO_RSA_SW_PAD is derived inside rsa.c, so check WOLFSSL_NO_RSA_SW:
2824+
* these tests need the software OAEP and raw RSA paths. */
2825+
#if !defined(NO_RSA) && !defined(WC_NO_RSA_OAEP) && \
2826+
!defined(WOLFSSL_NO_RSA_SW) && !defined(NO_HASH_WRAPPER) && \
2827+
!defined(NO_SHA256) && !defined(WOLFSSL_RSA_VERIFY_ONLY) && \
2828+
!defined(HAVE_FIPS) && !defined(HAVE_SELFTEST)
2829+
#define TEST_RSA_OAEP_CRAFT
2830+
#define TEST_RSA_OAEP_HLEN WC_SHA256_DIGEST_SIZE
2831+
2832+
/* The vectors are 2048-bit blocks. Unpadding works in a scratch buffer sized
2833+
* for RSA_MAX_SIZE, so skip when the build can't hold a 2048-bit block. */
2834+
#if RSA_MAX_SIZE >= 2048
2835+
#define TEST_RSA_OAEP_SEPARATOR
2836+
#endif
2837+
/* Key generation and the raw RSA operation need software RSA, or with
2838+
* callback-only RSA, the wc_swdev device to do them. */
2839+
#if defined(WOLFSSL_KEY_GEN) && !defined(WC_NO_RNG) && \
2840+
!defined(WOLFSSL_RSA_PUBLIC_ONLY) && \
2841+
(!defined(WOLF_CRYPTO_CB_ONLY_RSA) || defined(WOLFSSL_SWDEV))
2842+
#define TEST_RSA_OAEP_ZEROLEN
2843+
#endif
2844+
#endif /* TEST_RSA_OAEP_CRAFT conditions */
2845+
2846+
/* Only compile the helpers when a test uses them. */
2847+
#if defined(TEST_RSA_OAEP_SEPARATOR) || defined(TEST_RSA_OAEP_ZEROLEN)
2848+
2849+
/* MGF1 with SHA-256 (RFC 8017, B.2.1), written independently of the library
2850+
* so that hand-built encoded messages don't depend on the code under test. */
2851+
static int rsa_oaep_test_mgf1(const byte* seed, word32 seedLen, byte* out,
2852+
word32 outLen)
2853+
{
2854+
byte buf[512 + 4];
2855+
byte h[TEST_RSA_OAEP_HLEN];
2856+
word32 cnt;
2857+
word32 o = 0;
2858+
word32 n;
2859+
int ret = 0;
2860+
2861+
if (seedLen > 512)
2862+
return BAD_FUNC_ARG;
2863+
XMEMCPY(buf, seed, seedLen);
2864+
for (cnt = 0; (ret == 0) && (o < outLen); cnt++) {
2865+
buf[seedLen + 0] = (byte)(cnt >> 24);
2866+
buf[seedLen + 1] = (byte)(cnt >> 16);
2867+
buf[seedLen + 2] = (byte)(cnt >> 8);
2868+
buf[seedLen + 3] = (byte)cnt;
2869+
ret = wc_Sha256Hash(buf, seedLen + 4, h);
2870+
n = outLen - o;
2871+
if (n > TEST_RSA_OAEP_HLEN)
2872+
n = TEST_RSA_OAEP_HLEN;
2873+
if (ret == 0)
2874+
XMEMCPY(out + o, h, n);
2875+
o += n;
2876+
}
2877+
return ret;
2878+
}
2879+
2880+
/* Build an OAEP-SHA256 encoded message Y | maskedSeed | maskedDB from a
2881+
* caller-chosen DB, so that malformed DBs can be produced.
2882+
* db is blockLen - hLen - 1 bytes. */
2883+
static int rsa_oaep_test_encode(const byte* db, byte* block, word32 blockLen,
2884+
byte y)
2885+
{
2886+
byte seed[TEST_RSA_OAEP_HLEN];
2887+
byte mask[512];
2888+
word32 dbLen = blockLen - TEST_RSA_OAEP_HLEN - 1;
2889+
word32 i;
2890+
int ret;
2891+
2892+
if (blockLen > sizeof(mask))
2893+
return BAD_FUNC_ARG;
2894+
XMEMSET(seed, 0x3c, sizeof(seed));
2895+
block[0] = y;
2896+
ret = rsa_oaep_test_mgf1(seed, TEST_RSA_OAEP_HLEN, mask, dbLen);
2897+
for (i = 0; (ret == 0) && (i < dbLen); i++)
2898+
block[1 + TEST_RSA_OAEP_HLEN + i] = db[i] ^ mask[i];
2899+
if (ret == 0) {
2900+
ret = rsa_oaep_test_mgf1(block + 1 + TEST_RSA_OAEP_HLEN, dbLen, mask,
2901+
TEST_RSA_OAEP_HLEN);
2902+
}
2903+
for (i = 0; (ret == 0) && (i < TEST_RSA_OAEP_HLEN); i++)
2904+
block[1 + i] = seed[i] ^ mask[i];
2905+
return ret;
2906+
}
2907+
2908+
/* Fill DB as lHash | zeros, then place the separator byte at offset sepOff
2909+
* of DB (sepOff == 0 means no byte is placed) and the message after it. */
2910+
static void rsa_oaep_test_db(byte* db, word32 dbLen, const byte* lHash,
2911+
word32 sepOff, byte sepByte, const byte* msg)
2912+
{
2913+
XMEMSET(db, 0, dbLen);
2914+
XMEMCPY(db, lHash, TEST_RSA_OAEP_HLEN);
2915+
if (sepOff != 0) {
2916+
db[sepOff] = sepByte;
2917+
XMEMCPY(db + sepOff + 1, msg, dbLen - sepOff - 1);
2918+
}
2919+
}
2920+
#endif /* TEST_RSA_OAEP_SEPARATOR || TEST_RSA_OAEP_ZEROLEN */
2921+
2922+
/*
2923+
* Testing that OAEP unpadding finds the separator at every position and
2924+
* rejects malformed padding with a zero length and an end-of-block pointer.
2925+
*/
2926+
int test_wc_RsaUnPad_OaepSeparator(void)
2927+
{
2928+
EXPECT_DECLS;
2929+
#ifdef TEST_RSA_OAEP_SEPARATOR
2930+
/* 2048-bit block. DB is lHash (32) | PS | 0x01 | M, from offset 33. */
2931+
enum {
2932+
BLK = 256,
2933+
HL = TEST_RSA_OAEP_HLEN,
2934+
DBLEN = BLK - HL - 1,
2935+
DBOFF = HL + 1
2936+
};
2937+
byte label[] = { 'l', 'a', 'b', 'e', 'l' };
2938+
byte badLabel[] = { 'l', 'a', 'b', 'e', 'L' };
2939+
byte lHash[HL];
2940+
byte lHashLabel[HL];
2941+
byte msg[DBLEN];
2942+
byte db[DBLEN];
2943+
byte block[BLK];
2944+
byte* out;
2945+
word32 sepOff;
2946+
word32 i;
2947+
int ret;
2948+
2949+
ExpectIntEQ(wc_Sha256Hash(NULL, 0, lHash), 0);
2950+
ExpectIntEQ(wc_Sha256Hash(label, sizeof(label), lHashLabel), 0);
2951+
2952+
/* Message with zero bytes and extra 0x01 bytes, including at its start, so
2953+
* only the first non-zero byte after lHash may be taken as separator. */
2954+
for (i = 0; i < DBLEN; i++)
2955+
msg[i] = (byte)(((i % 3) == 0) ? 0x00 : (((i % 3) == 1) ? 0x01 : i));
2956+
2957+
/* Separator at every position, from right after lHash (PS empty, longest
2958+
* message) to the last byte of the block (empty message). */
2959+
for (sepOff = HL; EXPECT_SUCCESS() && (sepOff < DBLEN); sepOff++) {
2960+
word32 mLen = DBLEN - sepOff - 1;
2961+
2962+
rsa_oaep_test_db(db, DBLEN, lHash, sepOff, 0x01, msg);
2963+
ExpectIntEQ(rsa_oaep_test_encode(db, block, BLK, 0x00), 0);
2964+
out = NULL;
2965+
ret = wc_RsaUnPad_ex(block, BLK, &out, RSA_BLOCK_TYPE_2,
2966+
WC_RSA_OAEP_PAD, WC_HASH_TYPE_SHA256, WC_MGF1SHA256, NULL, 0, 0,
2967+
BLK * 8, HEAP_HINT);
2968+
ExpectIntEQ(ret, (int)mLen);
2969+
ExpectPtrEq(out, block + BLK - mLen);
2970+
if (mLen > 0) {
2971+
ExpectBufEQ(out, msg, mLen);
2972+
}
2973+
}
2974+
2975+
/* Same encoding with a matching non-empty label. */
2976+
rsa_oaep_test_db(db, DBLEN, lHashLabel, 100, 0x01, msg);
2977+
ExpectIntEQ(rsa_oaep_test_encode(db, block, BLK, 0x00), 0);
2978+
out = NULL;
2979+
ExpectIntEQ(wc_RsaUnPad_ex(block, BLK, &out, RSA_BLOCK_TYPE_2,
2980+
WC_RSA_OAEP_PAD, WC_HASH_TYPE_SHA256, WC_MGF1SHA256, label,
2981+
sizeof(label), 0, BLK * 8, HEAP_HINT), DBLEN - 101);
2982+
ExpectPtrEq(out, block + DBOFF + 101);
2983+
ExpectBufEQ(out, msg, DBLEN - 101);
2984+
2985+
/* Each malformed block must give length 0 and a pointer to the end of the
2986+
* block, the same as each other, so the failures look alike. Unpadding
2987+
* demasks the block in place, so each case encodes it again. */
2988+
#define TEST_OAEP_BAD(lbl, lblLen) do { \
2989+
out = NULL; \
2990+
ExpectIntEQ(wc_RsaUnPad_ex(block, BLK, &out, RSA_BLOCK_TYPE_2, \
2991+
WC_RSA_OAEP_PAD, WC_HASH_TYPE_SHA256, WC_MGF1SHA256, (lbl), \
2992+
(lblLen), 0, BLK * 8, HEAP_HINT), 0); \
2993+
ExpectPtrEq(out, block + BLK); \
2994+
} while (0)
2995+
2996+
/* No separator: only zeros after lHash. */
2997+
rsa_oaep_test_db(db, DBLEN, lHash, 0, 0, NULL);
2998+
ExpectIntEQ(rsa_oaep_test_encode(db, block, BLK, 0x00), 0);
2999+
TEST_OAEP_BAD(NULL, 0);
3000+
3001+
/* First non-zero byte is not 0x01, with a 0x01 right after it and at the
3002+
* last byte - neither may be taken as the separator. */
3003+
for (i = 0; i < 3; i++) {
3004+
static const byte badSep[3] = { 0x02, 0x80, 0xff };
3005+
3006+
rsa_oaep_test_db(db, DBLEN, lHash, 100, badSep[i], msg);
3007+
db[101] = 0x01;
3008+
db[DBLEN - 1] = 0x01;
3009+
ExpectIntEQ(rsa_oaep_test_encode(db, block, BLK, 0x00), 0);
3010+
TEST_OAEP_BAD(NULL, 0);
3011+
}
3012+
3013+
/* Invalid first byte of the last position: 0x02 instead of 0x01. */
3014+
rsa_oaep_test_db(db, DBLEN, lHash, DBLEN - 1, 0x02, msg);
3015+
ExpectIntEQ(rsa_oaep_test_encode(db, block, BLK, 0x00), 0);
3016+
TEST_OAEP_BAD(NULL, 0);
3017+
3018+
/* Valid separator at the first and last positions, but Y is not zero. */
3019+
rsa_oaep_test_db(db, DBLEN, lHash, HL, 0x01, msg);
3020+
ExpectIntEQ(rsa_oaep_test_encode(db, block, BLK, 0x01), 0);
3021+
TEST_OAEP_BAD(NULL, 0);
3022+
rsa_oaep_test_db(db, DBLEN, lHash, DBLEN - 1, 0x01, msg);
3023+
ExpectIntEQ(rsa_oaep_test_encode(db, block, BLK, 0x80), 0);
3024+
TEST_OAEP_BAD(NULL, 0);
3025+
3026+
/* Valid separator but lHash does not match: one bit flipped, and a block
3027+
* encoded for a label but decoded with none or with another label. */
3028+
rsa_oaep_test_db(db, DBLEN, lHash, 100, 0x01, msg);
3029+
db[HL - 1] ^= 0x01;
3030+
ExpectIntEQ(rsa_oaep_test_encode(db, block, BLK, 0x00), 0);
3031+
TEST_OAEP_BAD(NULL, 0);
3032+
rsa_oaep_test_db(db, DBLEN, lHashLabel, 100, 0x01, msg);
3033+
ExpectIntEQ(rsa_oaep_test_encode(db, block, BLK, 0x00), 0);
3034+
TEST_OAEP_BAD(NULL, 0);
3035+
/* Unpadding demasks in place, so encode again before each attempt. */
3036+
ExpectIntEQ(rsa_oaep_test_encode(db, block, BLK, 0x00), 0);
3037+
TEST_OAEP_BAD(badLabel, sizeof(badLabel));
3038+
#undef TEST_OAEP_BAD
3039+
#else
3040+
return TEST_SKIPPED;
3041+
#endif /* TEST_RSA_OAEP_SEPARATOR */
3042+
return EXPECT_RESULT();
3043+
}
3044+
3045+
/*
3046+
* Testing the length RSA-OAEP private decrypt returns for an empty message
3047+
* and for invalid padding: 0 with WOLFSSL_RSA_DECRYPT_TO_0_LEN, otherwise
3048+
* RSA_BUFFER_E. In both builds the two cases must give the same result.
3049+
*/
3050+
int test_wc_RsaPrivateDecrypt_OaepZeroLen(void)
3051+
{
3052+
EXPECT_DECLS;
3053+
#ifdef TEST_RSA_OAEP_ZEROLEN
3054+
enum {
3055+
BLK = TEST_RSA_BYTES,
3056+
HL = TEST_RSA_OAEP_HLEN,
3057+
DBLEN = BLK - HL - 1
3058+
};
3059+
RsaKey key;
3060+
WC_RNG rng;
3061+
byte lHash[HL];
3062+
byte msg[DBLEN];
3063+
byte db[DBLEN];
3064+
byte block[BLK];
3065+
byte cipher[BLK];
3066+
byte plain[BLK];
3067+
byte* outPtr;
3068+
word32 cipherLen;
3069+
int i;
3070+
#ifdef WOLFSSL_RSA_DECRYPT_TO_0_LEN
3071+
const int zeroRet = 0;
3072+
#else
3073+
const int zeroRet = WC_NO_ERR_TRACE(RSA_BUFFER_E);
3074+
#endif
3075+
3076+
XMEMSET(&key, 0, sizeof(key));
3077+
XMEMSET(&rng, 0, sizeof(rng));
3078+
XMEMSET(msg, 0x5a, sizeof(msg));
3079+
ExpectIntEQ(wc_InitRsaKey(&key, HEAP_HINT), 0);
3080+
ExpectIntEQ(wc_InitRng(&rng), 0);
3081+
ExpectIntEQ(MAKE_RSA_KEY(&key, TEST_RSA_BITS, WC_RSA_EXPONENT, &rng), 0);
3082+
#ifdef WC_RSA_BLINDING
3083+
ExpectIntEQ(wc_RsaSetRNG(&key, &rng), 0);
3084+
#endif
3085+
ExpectIntEQ(wc_Sha256Hash(NULL, 0, lHash), 0);
3086+
3087+
/* 0: empty message (separator in the last byte), 1: no separator,
3088+
* 2: invalid separator byte, 3: non-empty message as a control. */
3089+
for (i = 0; i < 4; i++) {
3090+
if (i == 0)
3091+
rsa_oaep_test_db(db, DBLEN, lHash, DBLEN - 1, 0x01, msg);
3092+
else if (i == 1)
3093+
rsa_oaep_test_db(db, DBLEN, lHash, 0, 0, NULL);
3094+
else if (i == 2)
3095+
rsa_oaep_test_db(db, DBLEN, lHash, DBLEN - 9, 0x02, msg);
3096+
else
3097+
rsa_oaep_test_db(db, DBLEN, lHash, DBLEN - 9, 0x01, msg);
3098+
ExpectIntEQ(rsa_oaep_test_encode(db, block, BLK, 0x00), 0);
3099+
/* Raw RSA public operation: the public encrypt API rejects an empty
3100+
* message and only produces valid padding. */
3101+
cipherLen = sizeof(cipher);
3102+
ExpectIntEQ(wc_RsaFunction(block, BLK, cipher, &cipherLen,
3103+
RSA_PUBLIC_ENCRYPT, &key, &rng), 0);
3104+
ExpectIntEQ(cipherLen, BLK);
3105+
3106+
XMEMSET(plain, 0xa5, sizeof(plain));
3107+
ExpectIntEQ(wc_RsaPrivateDecrypt_ex(cipher, cipherLen, plain,
3108+
sizeof(plain), &key, WC_RSA_OAEP_PAD, WC_HASH_TYPE_SHA256,
3109+
WC_MGF1SHA256, NULL, 0), (i == 3) ? 8 : zeroRet);
3110+
if (i == 3) {
3111+
ExpectBufEQ(plain, msg, 8);
3112+
}
3113+
3114+
outPtr = NULL;
3115+
ExpectIntEQ(wc_RsaPrivateDecryptInline_ex(cipher, cipherLen, &outPtr,
3116+
&key, WC_RSA_OAEP_PAD, WC_HASH_TYPE_SHA256, WC_MGF1SHA256, NULL,
3117+
0), (i == 3) ? 8 : zeroRet);
3118+
if (i == 3) {
3119+
ExpectPtrEq(outPtr, cipher + BLK - 8);
3120+
ExpectBufEQ(outPtr, msg, 8);
3121+
}
3122+
}
3123+
3124+
DoExpectIntEQ(wc_FreeRsaKey(&key), 0);
3125+
DoExpectIntEQ(wc_FreeRng(&rng), 0);
3126+
#else
3127+
return TEST_SKIPPED;
3128+
#endif
3129+
return EXPECT_RESULT();
3130+
}
3131+
3132+
/*
3133+
* Testing that OAEP unpadding accepts a block of RSA_MAX_SIZE bits, and that a
3134+
* longer block is rejected when the scratch buffer is on stack instead of
3135+
* overrunning it, and is handled when the scratch buffer is allocated.
3136+
*/
3137+
int test_wc_RsaUnPad_OaepBlockLen(void)
3138+
{
3139+
EXPECT_DECLS;
3140+
#ifdef TEST_RSA_OAEP_CRAFT
3141+
const word32 maxLen = RSA_MAX_SIZE / 8;
3142+
byte* block = NULL;
3143+
byte* out;
3144+
3145+
ExpectNotNull(block = (byte*)XMALLOC(maxLen + 1, HEAP_HINT,
3146+
DYNAMIC_TYPE_TMP_BUFFER));
3147+
if (block != NULL) {
3148+
/* Largest block: processed, and all zeros is invalid padding. */
3149+
XMEMSET(block, 0, maxLen + 1);
3150+
out = NULL;
3151+
ExpectIntEQ(wc_RsaUnPad_ex(block, maxLen, &out, RSA_BLOCK_TYPE_2,
3152+
WC_RSA_OAEP_PAD, WC_HASH_TYPE_SHA256, WC_MGF1SHA256, NULL, 0, 0,
3153+
(int)maxLen * 8, HEAP_HINT), 0);
3154+
ExpectPtrEq(out, block + maxLen);
3155+
3156+
XMEMSET(block, 0, maxLen + 1);
3157+
out = NULL;
3158+
#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC)
3159+
/* Scratch buffer is allocated per block, so any length is handled. */
3160+
ExpectIntEQ(wc_RsaUnPad_ex(block, maxLen + 1, &out, RSA_BLOCK_TYPE_2,
3161+
WC_RSA_OAEP_PAD, WC_HASH_TYPE_SHA256, WC_MGF1SHA256, NULL, 0, 0,
3162+
(int)(maxLen + 1) * 8, HEAP_HINT), 0);
3163+
ExpectPtrEq(out, block + maxLen + 1);
3164+
#else
3165+
/* Scratch buffer is on stack: one byte more is rejected before the
3166+
* block is touched. */
3167+
ExpectIntEQ(wc_RsaUnPad_ex(block, maxLen + 1, &out, RSA_BLOCK_TYPE_2,
3168+
WC_RSA_OAEP_PAD, WC_HASH_TYPE_SHA256, WC_MGF1SHA256, NULL, 0, 0,
3169+
(int)(maxLen + 1) * 8, HEAP_HINT), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
3170+
ExpectNull(out);
3171+
#endif
3172+
}
3173+
XFREE(block, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
3174+
#else
3175+
return TEST_SKIPPED;
3176+
#endif
3177+
return EXPECT_RESULT();
3178+
}

‎tests/api/test_rsa.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ int test_wc_RsaKeyToPublicDer(void);
4242
int test_wc_RsaPublicEncryptDecrypt(void);
4343
int test_wc_RsaPublicEncryptDecrypt_ex(void);
4444
int test_wc_RsaOaep_EncryptFailureWipe(void);
45+
int test_wc_RsaUnPad_OaepSeparator(void);
46+
int test_wc_RsaPrivateDecrypt_OaepZeroLen(void);
47+
int test_wc_RsaUnPad_OaepBlockLen(void);
4548
int test_wc_RsaEncryptSize(void);
4649
int test_wc_RsaSSL_SignVerify(void);
4750
int test_wc_RsaFlattenPublicKey(void);
@@ -74,6 +77,9 @@ int test_wc_FreeRsaKey_CryptoCb(void);
7477
TEST_DECL_GROUP("rsa", test_wc_RsaPublicEncryptDecrypt), \
7578
TEST_DECL_GROUP("rsa", test_wc_RsaPublicEncryptDecrypt_ex), \
7679
TEST_DECL_GROUP("rsa", test_wc_RsaOaep_EncryptFailureWipe), \
80+
TEST_DECL_GROUP("rsa", test_wc_RsaUnPad_OaepSeparator), \
81+
TEST_DECL_GROUP("rsa", test_wc_RsaPrivateDecrypt_OaepZeroLen), \
82+
TEST_DECL_GROUP("rsa", test_wc_RsaUnPad_OaepBlockLen), \
7783
TEST_DECL_GROUP("rsa", test_wc_RsaEncryptSize), \
7884
TEST_DECL_GROUP("rsa", test_wc_RsaSSL_SignVerify), \
7985
TEST_DECL_GROUP("rsa", test_wc_RsaFlattenPublicKey), \

0 commit comments

Comments
 (0)