diff options
author | Joel Sing <jsing@cvs.openbsd.org> | 2019-10-17 14:31:57 +0000 |
---|---|---|
committer | Joel Sing <jsing@cvs.openbsd.org> | 2019-10-17 14:31:57 +0000 |
commit | 05786a6fdd8e7e3668147685860562a1d9607b0e (patch) | |
tree | fd63b8f90fb2657dc145693523eea0d241f35693 /lib/libcrypto/rsa/rsa_oaep.c | |
parent | 0604a23779830343a0cd63f5582f0faf642b4e12 (diff) |
Sync RSA_padding_check_PKCS1_OAEP_mgf1().
Update RSA_padding_check_PKCS1_OAEP_mgf1() with code from OpenSSL 1.1.1d
(with some improvements/corrections to comments).
This brings in code to make the padding check constant time.
ok inoguchi@ tb@
Diffstat (limited to 'lib/libcrypto/rsa/rsa_oaep.c')
-rw-r--r-- | lib/libcrypto/rsa/rsa_oaep.c | 175 |
1 files changed, 111 insertions, 64 deletions
diff --git a/lib/libcrypto/rsa/rsa_oaep.c b/lib/libcrypto/rsa/rsa_oaep.c index 6b1760da60d..e54600b0943 100644 --- a/lib/libcrypto/rsa/rsa_oaep.c +++ b/lib/libcrypto/rsa/rsa_oaep.c @@ -1,4 +1,4 @@ -/* $OpenBSD: rsa_oaep.c,v 1.32 2019/10/09 16:17:59 jsing Exp $ */ +/* $OpenBSD: rsa_oaep.c,v 1.33 2019/10/17 14:31:56 jsing Exp $ */ /* * Copyright 1999-2018 The OpenSSL Project Authors. All Rights Reserved. * @@ -79,6 +79,7 @@ #include <openssl/rsa.h> #include <openssl/sha.h> +#include "constant_time_locl.h" #include "rsa_locl.h" int @@ -169,13 +170,11 @@ RSA_padding_check_PKCS1_OAEP_mgf1(unsigned char *to, int tlen, const unsigned char *from, int flen, int num, const unsigned char *param, int plen, const EVP_MD *md, const EVP_MD *mgf1md) { - int i, dblen, mlen = -1; - const unsigned char *maskeddb; - int lzero; - unsigned char *db = NULL; + int i, dblen = 0, mlen = -1, one_index = 0, msg_index; + unsigned int good = 0, found_one_byte, mask; + const unsigned char *maskedseed, *maskeddb; unsigned char seed[EVP_MAX_MD_SIZE], phash[EVP_MAX_MD_SIZE]; - unsigned char *padded_from; - int bad = 0; + unsigned char *db = NULL, *em = NULL; int mdlen; if (md == NULL) @@ -184,88 +183,136 @@ RSA_padding_check_PKCS1_OAEP_mgf1(unsigned char *to, int tlen, mgf1md = md; if ((mdlen = EVP_MD_size(md)) <= 0) - goto err; + return -1; - if (--num < 2 * mdlen + 1) - /* - * 'num' is the length of the modulus, i.e. does not depend - * on the particular ciphertext. - */ - goto decoding_err; + if (tlen <= 0 || flen <= 0) + return -1; - lzero = num - flen; - if (lzero < 0) { - /* - * signalling this error immediately after detection might allow - * for side-channel attacks (e.g. timing if 'plen' is huge - * -- cf. James H. Manger, "A Chosen Ciphertext Attack on RSA - * Optimal Asymmetric Encryption Padding (OAEP) [...]", - * CRYPTO 2001), so we use a 'bad' flag - */ - bad = 1; - lzero = 0; - flen = num; /* don't overflow the memcpy to padded_from */ + /* + * |num| is the length of the modulus; |flen| is the length of the + * encoded message. Therefore, for any |from| that was obtained by + * decrypting a ciphertext, we must have |flen| <= |num|. Similarly, + * |num| >= 2 * |mdlen| + 2 must hold for the modulus irrespective + * of the ciphertext, see PKCS #1 v2.2, section 7.1.2. + * This does not leak any side-channel information. + */ + if (num < flen || num < 2 * mdlen + 2) { + RSAerror(RSA_R_OAEP_DECODING_ERROR); + return -1; } - dblen = num - mdlen; - if ((db = malloc(dblen + num)) == NULL) { + dblen = num - mdlen - 1; + if ((db = malloc(dblen)) == NULL) { RSAerror(ERR_R_MALLOC_FAILURE); - return -1; + goto cleanup; + } + if ((em = malloc(num)) == NULL) { + RSAerror(ERR_R_MALLOC_FAILURE); + goto cleanup; + } + + /* + * Caller is encouraged to pass zero-padded message created with + * BN_bn2binpad. Trouble is that since we can't read out of |from|'s + * bounds, it's impossible to have an invariant memory access pattern + * in case |from| was not zero-padded in advance. + */ + for (from += flen, em += num, i = 0; i < num; i++) { + mask = ~constant_time_is_zero(flen); + flen -= 1 & mask; + from -= 1 & mask; + *--em = *from & mask; } + from = em; /* - * Always do this zero-padding copy (even when lzero == 0) - * to avoid leaking timing info about the value of lzero. + * The first byte must be zero, however we must not leak if this is + * true. See James H. Manger, "A Chosen Ciphertext Attack on RSA + * Optimal Asymmetric Encryption Padding (OAEP) [...]", CRYPTO 2001). */ - padded_from = db + dblen; - memset(padded_from, 0, lzero); - memcpy(padded_from + lzero, from, flen); + good = constant_time_is_zero(from[0]); - maskeddb = padded_from + mdlen; + maskedseed = from + 1; + maskeddb = from + 1 + mdlen; if (PKCS1_MGF1(seed, mdlen, maskeddb, dblen, mgf1md)) - goto err; + goto cleanup; for (i = 0; i < mdlen; i++) - seed[i] ^= padded_from[i]; + seed[i] ^= maskedseed[i]; + if (PKCS1_MGF1(db, dblen, seed, mdlen, mgf1md)) - goto err; + goto cleanup; for (i = 0; i < dblen; i++) db[i] ^= maskeddb[i]; if (!EVP_Digest((void *)param, plen, phash, NULL, md, NULL)) - goto err; + goto cleanup; - if (timingsafe_memcmp(db, phash, mdlen) != 0 || bad) - goto decoding_err; - else { - for (i = mdlen; i < dblen; i++) - if (db[i] != 0x00) - break; - if (i == dblen || db[i] != 0x01) - goto decoding_err; - else { - /* everything looks OK */ - - mlen = dblen - ++i; - if (tlen < mlen) { - RSAerror(RSA_R_DATA_TOO_LARGE); - mlen = -1; - } else - memcpy(to, db + i, mlen); - } + good &= constant_time_is_zero(timingsafe_memcmp(db, phash, mdlen)); + + found_one_byte = 0; + for (i = mdlen; i < dblen; i++) { + /* + * Padding consists of a number of 0-bytes, followed by a 1. + */ + unsigned int equals1 = constant_time_eq(db[i], 1); + unsigned int equals0 = constant_time_is_zero(db[i]); + + one_index = constant_time_select_int(~found_one_byte & equals1, + i, one_index); + found_one_byte |= equals1; + good &= (found_one_byte | equals0); + } + + good &= found_one_byte; + + /* + * At this point |good| is zero unless the plaintext was valid, + * so plaintext-awareness ensures timing side-channels are no longer a + * concern. + */ + msg_index = one_index + 1; + mlen = dblen - msg_index; + + /* + * For good measure, do this check in constant time as well. + */ + good &= constant_time_ge(tlen, mlen); + + /* + * Even though we can't fake result's length, we can pretend copying + * |tlen| bytes where |mlen| bytes would be real. The last |tlen| of + * |dblen| bytes are viewed as a circular buffer starting at |tlen|-|mlen'|, + * where |mlen'| is the "saturated" |mlen| value. Deducing information + * about failure or |mlen| would require an attacker to observe + * memory access patterns with byte granularity *as it occurs*. It + * should be noted that failure is indistinguishable from normal + * operation if |tlen| is fixed by protocol. + */ + tlen = constant_time_select_int(constant_time_lt(dblen, tlen), dblen, tlen); + msg_index = constant_time_select_int(good, msg_index, dblen - tlen); + mlen = dblen - msg_index; + for (from = db + msg_index, mask = good, i = 0; i < tlen; i++) { + unsigned int equals = constant_time_eq(i, mlen); + + from -= dblen & equals; /* if (i == mlen) rewind */ + mask &= mask ^ equals; /* if (i == mlen) mask = 0 */ + to[i] = constant_time_select_8(mask, from[i], to[i]); } - free(db); - return mlen; - decoding_err: /* * To avoid chosen ciphertext attacks, the error message should not - * reveal which kind of decoding error happened + * reveal which kind of decoding error happened. */ RSAerror(RSA_R_OAEP_DECODING_ERROR); - err: - free(db); - return -1; + err_clear_last_constant_time(1 & good); + + cleanup: + explicit_bzero(seed, sizeof(seed)); + freezero(db, dblen); + freezero(em, num); + + return constant_time_select_int(good, mlen, -1); } int |