diff --git a/crypto/rsa/rsa_ossl.c b/crypto/rsa/rsa_ossl.c index 54e2a1c61caeb..2b25dad893509 100644 --- a/crypto/rsa/rsa_ossl.c +++ b/crypto/rsa/rsa_ossl.c @@ -17,6 +17,9 @@ #include "crypto/bn.h" #include "rsa_local.h" #include "internal/constant_time.h" +#include +#include +#include static int rsa_ossl_public_encrypt(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding); @@ -372,8 +375,13 @@ static int rsa_ossl_private_decrypt(int flen, const unsigned char *from, BIGNUM *f, *ret; int j, num = 0, r = -1; unsigned char *buf = NULL; + unsigned char d_hash[SHA256_DIGEST_LENGTH] = {0}; + HMAC_CTX *hmac = NULL; + unsigned int md_len = SHA256_DIGEST_LENGTH; + unsigned char kdk[SHA256_DIGEST_LENGTH] = {0}; BN_CTX *ctx = NULL; int local_blinding = 0; + EVP_MD *md = NULL; /* * Used only if the blinding structure is shared. A non-NULL unblind * instructs rsa_blinding_convert() and rsa_blinding_invert() to store @@ -405,6 +413,11 @@ static int rsa_ossl_private_decrypt(int flen, const unsigned char *from, goto err; } + if (flen < 1) { + ERR_raise(ERR_LIB_RSA, RSA_R_DATA_TOO_SMALL); + goto err; + } + /* make data into a big number */ if (BN_bin2bn(from, (int)flen, f) == NULL) goto err; @@ -471,13 +484,91 @@ static int rsa_ossl_private_decrypt(int flen, const unsigned char *from, if (!rsa_blinding_invert(blinding, ret, unblind, ctx)) goto err; + /* + * derive the Key Derivation Key from private exponent and public + * ciphertext + */ + if (!(rsa->flags & RSA_FLAG_EXT_PKEY)) { + /* + * because we use d as a handle to rsa->d we need to keep it local and + * free before any further use of rsa->d + */ + BIGNUM *d = BN_new(); + if (d == NULL) { + ERR_raise(ERR_LIB_RSA, ERR_R_MALLOC_FAILURE); + goto err; + } + if (rsa->d == NULL) { + ERR_raise(ERR_LIB_RSA, RSA_R_MISSING_PRIVATE_KEY); + BN_free(d); + goto err; + } + BN_with_flags(d, rsa->d, BN_FLG_CONSTTIME); + if (BN_bn2binpad(d, buf, num) < 0) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + BN_free(d); + goto err; + } + BN_free(d); + + /* + * we use hardcoded hash so that migrating between versions that use + * different hash doesn't provide a Bleichenbacher oracle: + * if the attacker can see that different versions return different + * messages for the same ciphertext, they'll know that the message is + * syntethically generated, which means that the padding check failed + */ + md = EVP_MD_fetch(rsa->libctx, "sha256", NULL); + if (md == NULL) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + goto err; + } + + if (EVP_Digest(buf, num, d_hash, NULL, md, NULL) <= 0) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + goto err; + } + + hmac = HMAC_CTX_new(); + if (hmac == NULL) { + ERR_raise(ERR_LIB_RSA, ERR_R_MALLOC_FAILURE); + goto err; + } + + if (HMAC_Init_ex(hmac, d_hash, sizeof(d_hash), md, NULL) <= 0) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + goto err; + } + + if (flen < num) { + memset(buf, 0, num - flen); + if (HMAC_Update(hmac, buf, num - flen) <= 0) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + goto err; + } + } + if (HMAC_Update(hmac, from, flen) <= 0) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + goto err; + } + + md_len = SHA256_DIGEST_LENGTH; + if (HMAC_Final(hmac, kdk, &md_len) <= 0) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + goto err; + } + } + j = BN_bn2binpad(ret, buf, num); if (j < 0) goto err; switch (padding) { case RSA_PKCS1_PADDING: - r = RSA_padding_check_PKCS1_type_2(to, num, buf, j, num); + if (rsa->flags & RSA_FLAG_EXT_PKEY) + r = RSA_padding_check_PKCS1_type_2(to, num, buf, j, num); + else + r = ossl_rsa_padding_check_PKCS1_type_2(rsa->libctx, to, num, buf, j, num, kdk); break; case RSA_PKCS1_OAEP_PADDING: r = RSA_padding_check_PKCS1_OAEP(to, num, buf, j, num, NULL, 0); @@ -500,6 +591,8 @@ static int rsa_ossl_private_decrypt(int flen, const unsigned char *from, #endif err: + HMAC_CTX_free(hmac); + EVP_MD_free(md); BN_CTX_end(ctx); BN_CTX_free(ctx); OPENSSL_clear_free(buf, num); diff --git a/crypto/rsa/rsa_pk1.c b/crypto/rsa/rsa_pk1.c index 5f72fe1735df0..04fb0e4ed5e69 100644 --- a/crypto/rsa/rsa_pk1.c +++ b/crypto/rsa/rsa_pk1.c @@ -21,10 +21,14 @@ #include /* Just for the SSL_MAX_MASTER_KEY_LENGTH value */ #include +#include +#include +#include #include "internal/cryptlib.h" #include "crypto/rsa.h" #include "rsa_local.h" + int RSA_padding_add_PKCS1_type_1(unsigned char *to, int tlen, const unsigned char *from, int flen) { @@ -271,6 +275,254 @@ int RSA_padding_check_PKCS1_type_2(unsigned char *to, int tlen, return constant_time_select_int(good, mlen, -1); } + +static int ossl_rsa_prf(OSSL_LIB_CTX *ctx, + unsigned char *to, int tlen, + const char *label, int llen, + const unsigned char *kdk, + uint16_t bitlen) +{ + int pos; + int ret = -1; + uint16_t iter = 0; + unsigned char be_iter[sizeof(iter)]; + unsigned char be_bitlen[sizeof(bitlen)]; + HMAC_CTX *hmac = NULL; + EVP_MD *md = NULL; + unsigned char hmac_out[SHA256_DIGEST_LENGTH]; + unsigned int md_len; + + if (tlen * 8 != bitlen) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + return ret; + } + + be_bitlen[0] = (bitlen >> 8) & 0xff; + be_bitlen[1] = bitlen & 0xff; + + hmac = HMAC_CTX_new(); + if (hmac == NULL) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + goto err; + } + + /* + * we use hardcoded hash so that migrating between versions that use + * different hash doesn't provide a Bleichenbacher oracle: + * if the attacker can see that different versions return different + * messages for the same ciphertext, they'll know that the message is + * syntethically generated, which means that the padding check failed + */ + md = EVP_MD_fetch(ctx, "sha256", NULL); + if (md == NULL) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + goto err; + } + + if (HMAC_Init_ex(hmac, kdk, SHA256_DIGEST_LENGTH, md, NULL) <= 0) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + goto err; + } + + for (pos = 0; pos < tlen; pos += SHA256_DIGEST_LENGTH, iter++) { + if (HMAC_Init_ex(hmac, NULL, 0, NULL, NULL) <= 0) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + goto err; + } + + be_iter[0] = (iter >> 8) & 0xff; + be_iter[1] = iter & 0xff; + + if (HMAC_Update(hmac, be_iter, sizeof(be_iter)) <= 0) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + goto err; + } + if (HMAC_Update(hmac, (unsigned char *)label, llen) <= 0) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + goto err; + } + if (HMAC_Update(hmac, be_bitlen, sizeof(be_bitlen)) <= 0) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + goto err; + } + + /* + * HMAC_Final requires the output buffer to fit the whole MAC + * value, so we need to use the intermediate buffer for the last + * unaligned block + */ + md_len = SHA256_DIGEST_LENGTH; + if (pos + SHA256_DIGEST_LENGTH > tlen) { + if (HMAC_Final(hmac, hmac_out, &md_len) <= 0) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + goto err; + } + memcpy(to + pos, hmac_out, tlen - pos); + } else { + if (HMAC_Final(hmac, to + pos, &md_len) <= 0) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + goto err; + } + } + } + + ret = 0; + +err: + HMAC_CTX_free(hmac); + EVP_MD_free(md); + return ret; +} + +/* + * ossl_rsa_padding_check_PKCS1_type_2() checks and removes the PKCS#1 type 2 + * padding from a decrypted RSA message. Unlike the + * RSA_padding_check_PKCS1_type_2() it will not return an error in case it + * detects a padding error, rather it will return a deterministically generated + * random message. In other words it will perform an implicit rejection + * of an invalid padding. This means that the returned value does not indicate + * if the padding of the encrypted message was correct or not, making + * side channel attacks like the ones described by Bleichenbacher impossible + * without access to the full decrypted value and a brute-force search of + * remaining padding bytes + */ +int ossl_rsa_padding_check_PKCS1_type_2(OSSL_LIB_CTX *ctx, + unsigned char *to, int tlen, + const unsigned char *from, int flen, + int num, unsigned char *kdk) +{ +/* + * We need to generate a random length for the synthethic message, to avoid + * bias towards zero and avoid non-constant timeness of DIV, we prepare + * 128 values to check if they are not too large for the used key size, + * and use 0 in case none of them are small enough, as 2^-128 is a good enough + * safety margin + */ +#define MAX_LEN_GEN_TRIES 128 + unsigned char *synthetic = NULL; + int synthethic_length; + uint16_t len_candidate; + unsigned char candidate_lengths[MAX_LEN_GEN_TRIES * sizeof(len_candidate)]; + uint16_t len_mask; + uint16_t max_sep_offset; + int synth_msg_index = 0; + int ret = -1; + int i, j; + unsigned int good, found_zero_byte; + int zero_index = 0, msg_index; + + /* + * If these checks fail then either the message in publicly invalid, or + * we've been called incorrectly. We can fail immediately. + * Since this code is called only internally by openssl, those are just + * sanity checks + */ + if (num != flen || tlen <= 0 || flen <= 0) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + return -1; + } + + /* Generate a random message to return in case the padding checks fail */ + synthetic = OPENSSL_malloc(flen); + if (synthetic == NULL) { + ERR_raise(ERR_LIB_RSA, ERR_R_MALLOC_FAILURE); + return -1; + } + + if (ossl_rsa_prf(ctx, synthetic, flen, "message", 7, kdk, flen * 8) < 0) + goto err; + + /* decide how long the random message should be */ + if (ossl_rsa_prf(ctx, candidate_lengths, sizeof(candidate_lengths), + "length", 6, kdk, + MAX_LEN_GEN_TRIES * sizeof(len_candidate) * 8) < 0) + goto err; + + /* + * max message size is the size of the modulus size less 2 bytes for + * version and padding type and a minimum of 8 bytes padding + */ + len_mask = max_sep_offset = flen - 2 - 8; + /* + * we want a mask so lets propagate the high bit to all positions less + * significant than it + */ + len_mask |= len_mask >> 1; + len_mask |= len_mask >> 2; + len_mask |= len_mask >> 4; + len_mask |= len_mask >> 8; + + synthethic_length = 0; + for (i = 0; i < MAX_LEN_GEN_TRIES * (int)sizeof(len_candidate); + i += sizeof(len_candidate)) { + len_candidate = (candidate_lengths[i] << 8) | candidate_lengths[i + 1]; + len_candidate &= len_mask; + + synthethic_length = constant_time_select_int( + constant_time_lt(len_candidate, max_sep_offset), + len_candidate, synthethic_length); + } + + synth_msg_index = flen - synthethic_length; + + /* we have alternative message ready, check the real one */ + good = constant_time_is_zero(from[0]); + good &= constant_time_eq(from[1], 2); + + /* then look for the padding|message separator (the first zero byte) */ + found_zero_byte = 0; + for (i = 2; i < flen; i++) { + unsigned int equals0 = constant_time_is_zero(from[i]); + zero_index = constant_time_select_int(~found_zero_byte & equals0, + i, zero_index); + found_zero_byte |= equals0; + } + + /* + * padding must be at least 8 bytes long, and it starts two bytes into + * |from|. If we never found a 0-byte, then |zero_index| is 0 and the check + * also fails. + */ + good &= constant_time_ge(zero_index, 2 + 8); + + /* + * Skip the zero byte. This is incorrect if we never found a zero-byte + * but in this case we also do not copy the message out. + */ + msg_index = zero_index + 1; + + /* + * old code returned an error in case the decrypted message wouldn't fit + * into the |to|, since that would leak information, return the synthethic + * message instead + */ + good &= constant_time_ge(tlen, num - msg_index); + + msg_index = constant_time_select_int(good, msg_index, synth_msg_index); + + /* + * since at this point the |msg_index| does not provide the signal + * indicating if the padding check failed or not, we don't have to worry + * about leaking the length of returned message, we still need to ensure + * that we read contents of both buffers so that cache accesses don't leak + * the value of |good| + */ + for (i = msg_index, j = 0; i < flen && j < tlen; i++, j++) + to[j] = constant_time_select_8(good, from[i], synthetic[i]); + ret = j; + +err: + /* + * the only time ret < 0 is when the ciphertext is publicly invalid + * or we were called with invalid parameters, so we don't have to perform + * a side-channel secure raising of the error + */ + if (ret < 0) + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + OPENSSL_free(synthetic); + return ret; +} + /* * ossl_rsa_padding_check_PKCS1_type_2_TLS() checks and removes the PKCS1 type 2 * padding from a decrypted RSA message in a TLS signature. The result is stored diff --git a/doc/man1/openssl-pkeyutl.pod.in b/doc/man1/openssl-pkeyutl.pod.in index b0054ead66f33..b7c45caa23fda 100644 --- a/doc/man1/openssl-pkeyutl.pod.in +++ b/doc/man1/openssl-pkeyutl.pod.in @@ -240,6 +240,11 @@ signed or verified directly instead of using a B structure. If a digest is set then the a B structure is used and its the length must correspond to the digest type. +Note, for B padding, as a protection against Bleichenbacher attack, +the decryption will not fail in case of padding check failures. Use B +and manual inspection of the decrypted message to verify if the decrypted +value has correct PKCS#1 v1.5 padding. + For B mode only encryption and decryption is supported. For B if the digest type is set it is used to format the block data diff --git a/doc/man1/openssl-rsautl.pod.in b/doc/man1/openssl-rsautl.pod.in index 186e49e5e49b8..eab34979de38b 100644 --- a/doc/man1/openssl-rsautl.pod.in +++ b/doc/man1/openssl-rsautl.pod.in @@ -105,6 +105,11 @@ The padding to use: PKCS#1 v1.5 (the default), PKCS#1 OAEP, ANSI X9.31, or no padding, respectively. For signatures, only B<-pkcs> and B<-raw> can be used. +Note: because of protection against Bleichenbacher attacks, decryption +using PKCS#1 v1.5 mode will not return errors in case padding check failed. +Use B<-raw> and inspect the returned value manually to check if the +padding is correct. + =item B<-hexdump> Hex dump the output data. diff --git a/doc/man3/EVP_PKEY_CTX_ctrl.pod b/doc/man3/EVP_PKEY_CTX_ctrl.pod index 9b96f42dbc9ed..12026174a586d 100644 --- a/doc/man3/EVP_PKEY_CTX_ctrl.pod +++ b/doc/man3/EVP_PKEY_CTX_ctrl.pod @@ -393,6 +393,13 @@ this behaviour should be tolerated then OSSL_ASYM_CIPHER_PARAM_TLS_NEGOTIATED_VERSION should be set to the actual negotiated protocol version. Otherwise it should be left unset. +Similarly to the B above, since OpenSSL version +3.1.0, the use of B will return a randomly generated message +instead of padding errors in case padding checks fail. Applications that +want to remain secure while using earlier versions of OpenSSL, still need to +handle both the error code from the RSA decryption operation and the +returned message in a side channel secure manner. + =head2 DSA parameters EVP_PKEY_CTX_set_dsa_paramgen_bits() sets the number of bits used for DSA diff --git a/doc/man3/EVP_PKEY_decrypt.pod b/doc/man3/EVP_PKEY_decrypt.pod index 0cd1a6548d056..462265c5a6740 100644 --- a/doc/man3/EVP_PKEY_decrypt.pod +++ b/doc/man3/EVP_PKEY_decrypt.pod @@ -51,6 +51,18 @@ return 1 for success and 0 or a negative value for failure. In particular a return value of -2 indicates the operation is not supported by the public key algorithm. +=head1 WARNINGS + +In OpenSSL versions before 3.1.0, when used in PKCS#1 v1.5 padding, +both the return value from the EVP_PKEY_decrypt() and the B provided +information useful in mounting a Bleichenbacher attack against the +used private key. They had to processed in a side-channel free way. + +Since version 3.1.0, the EVP_PKEY_decrypt() method when used with PKCS#1 +v1.5 padding doesn't return an error in case it detects an error in padding, +instead it returns a pseudo-randomly generated message, removing the need +of side-channel secure code from applications using OpenSSL. + =head1 EXAMPLES Decrypt data using OAEP (for RSA keys): diff --git a/doc/man3/RSA_padding_add_PKCS1_type_1.pod b/doc/man3/RSA_padding_add_PKCS1_type_1.pod index 9f7025c49755d..36ae18563f214 100644 --- a/doc/man3/RSA_padding_add_PKCS1_type_1.pod +++ b/doc/man3/RSA_padding_add_PKCS1_type_1.pod @@ -121,8 +121,8 @@ L. =head1 WARNINGS -The result of RSA_padding_check_PKCS1_type_2() is a very sensitive -information which can potentially be used to mount a Bleichenbacher +The result of RSA_padding_check_PKCS1_type_2() is exactly the +information which is used to mount a classical Bleichenbacher padding oracle attack. This is an inherent weakness in the PKCS #1 v1.5 padding design. Prefer PKCS1_OAEP padding. If that is not possible, the result of RSA_padding_check_PKCS1_type_2() should be @@ -137,6 +137,9 @@ as this would create a small timing side channel which could be used to mount a Bleichenbacher attack against any padding mode including PKCS1_OAEP. +You should prefer the use of EVP PKEY APIs for PKCS#1 v1.5 decryption +as they implement the necessary workarounds internally. + =head1 SEE ALSO L, diff --git a/doc/man3/RSA_public_encrypt.pod b/doc/man3/RSA_public_encrypt.pod index 1d38073aeada9..bd3f835ac6dfa 100644 --- a/doc/man3/RSA_public_encrypt.pod +++ b/doc/man3/RSA_public_encrypt.pod @@ -52,8 +52,8 @@ Encrypting user data directly with RSA is insecure. =back -B must not be more than RSA_size(B) - 11 for the PKCS #1 v1.5 -based padding modes, not more than RSA_size(B) - 42 for +When encrypting B must not be more than RSA_size(B) - 11 for the +PKCS #1 v1.5 based padding modes, not more than RSA_size(B) - 42 for RSA_PKCS1_OAEP_PADDING and exactly RSA_size(B) for RSA_NO_PADDING. When a padding mode other than RSA_NO_PADDING is in use, then RSA_public_encrypt() will include some random bytes into the ciphertext @@ -92,6 +92,13 @@ which can potentially be used to mount a Bleichenbacher padding oracle attack. This is an inherent weakness in the PKCS #1 v1.5 padding design. Prefer RSA_PKCS1_OAEP_PADDING. +In OpenSSL before version 3.1.0, both the return value and the length of +returned value could be used to mount the Bleichenbacher attack. +Since version 3.1.0, OpenSSL does not return an error in case of padding +checks failed. Instead it generates a random message based on used private +key and provided ciphertext so that application code doesn't have to implement +a side-channel secure error handling. + =head1 CONFORMING TO SSL, PKCS #1 v2.0 diff --git a/include/crypto/rsa.h b/include/crypto/rsa.h index 949873d0ee38b..f267e5d9d1c8d 100644 --- a/include/crypto/rsa.h +++ b/include/crypto/rsa.h @@ -83,6 +83,10 @@ int ossl_rsa_param_decode(RSA *rsa, const X509_ALGOR *alg); RSA *ossl_rsa_key_from_pkcs8(const PKCS8_PRIV_KEY_INFO *p8inf, OSSL_LIB_CTX *libctx, const char *propq); +int ossl_rsa_padding_check_PKCS1_type_2(OSSL_LIB_CTX *ctx, + unsigned char *to, int tlen, + const unsigned char *from, int flen, + int num, unsigned char *kdk); int ossl_rsa_padding_check_PKCS1_type_2_TLS(OSSL_LIB_CTX *ctx, unsigned char *to, size_t tlen, const unsigned char *from, diff --git a/test/recipes/30-test_evp_data/evppkey_rsa_common.txt b/test/recipes/30-test_evp_data/evppkey_rsa_common.txt index b8d8bb2993eb5..080c4d02af2d4 100644 --- a/test/recipes/30-test_evp_data/evppkey_rsa_common.txt +++ b/test/recipes/30-test_evp_data/evppkey_rsa_common.txt @@ -254,10 +254,10 @@ Input = 550AF55A2904E7B9762352F8FB7FA235A9CB053AACB2D5FCB8CA48453CB2EE3619746C70 Output = "Hello World" # Corrupted ciphertext +# Note: output is generated synthethically by the Bleichenbacher workaround Decrypt = RSA-2048 Input = 550AF55A2904E7B9762352F8FB7FA235A9CB053AACB2D5FCB8CA48453CB2EE3619746C701ABF2D4CC67003471A187900B05AA812BD25ED05C675DFC8C97A24A7BF49BD6214992CAD766D05A9A2B57B74F26A737E0237B8B76C45F1F226A836D7CFBC75BA999BDBE48DBC09227AA46C88F21DCCBA7840141AD5A5D71FD122E6BD6AC3E564780DFE623FC1CA9B995A6037BF0BBD43B205A84AC5444F34202C05CE9113087176432476576DE6FFFF9A52EA57C08BE3EC2F49676CB8E12F762AC71FA3C321E00AC988910C85FF52F93825666CE0D40FFAA0592078919D4493F46D95CCF76364C6D57760DD0B64805F9AFC76A2365A5575CA301D5103F0EA76CB9A79 -Output = "Hello World" -Result = KEYOP_ERROR +Output = 4cbb988d6a46228379132b0b5f8c249b3860043848c93632fb982c807c7c82fffc7a9ef83f4908f890373ac181ffea6381e103bcaa27e65638b6ecebef38b59ed4226a9d12af675cfcb634d8c40e7a7aff # OAEP padding Decrypt = RSA-2048