From: Matt Caswell Date: Mon, 11 Nov 2019 14:37:02 +0000 (+0000) Subject: Teach the RSA implementation about TLS RSA Key Transport X-Git-Tag: openssl-3.0.0-alpha1~860 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=d9a75107478380641b6862acac74d0bb870a5374;p=oweals%2Fopenssl.git Teach the RSA implementation about TLS RSA Key Transport In TLSv1.2 a pre-master secret value is passed from the client to the server encrypted using RSA PKCS1 type 2 padding in a ClientKeyExchange message. As well as the normal formatting rules for RSA PKCA1 type 2 padding TLS imposes some additional rules about what constitutes a well formed key. Specifically it must be exactly the right length and encode the TLS version originally requested by the client (as opposed to the actual negotiated version) in its first two bytes. All of these checks need to be done in constant time and, if they fail, then the TLS implementation is supposed to continue anyway with a random key (and therefore the connection will fail later on). This avoids padding oracle type attacks. This commit implements this within the RSA padding code so that we keep all the constant time padding logic in one place. A later commit will remove it from libssl. Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/10411) --- diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt index e81c32fe4f..4baed5c48e 100644 --- a/crypto/err/openssl.txt +++ b/crypto/err/openssl.txt @@ -2684,9 +2684,11 @@ PROV_R_AES_KEY_SETUP_FAILED:101:aes key setup failed PROV_R_BAD_DECRYPT:100:bad decrypt PROV_R_BAD_ENCODING:141:bad encoding PROV_R_BAD_LENGTH:142:bad length +PROV_R_BAD_TLS_CLIENT_VERSION:161:bad tls client version PROV_R_BN_ERROR:160:bn error PROV_R_BOTH_MODE_AND_MODE_INT:127:both mode and mode int PROV_R_CIPHER_OPERATION_FAILED:102:cipher operation failed +PROV_R_FAILED_TO_DECRYPT:162:failed to decrypt PROV_R_FAILED_TO_GENERATE_KEY:121:failed to generate key PROV_R_FAILED_TO_GET_PARAMETER:103:failed to get parameter PROV_R_FAILED_TO_SET_PARAMETER:104:failed to set parameter diff --git a/crypto/rsa/rsa_pk1.c b/crypto/rsa/rsa_pk1.c index 0c77422404..007e9b8cd5 100644 --- a/crypto/rsa/rsa_pk1.c +++ b/crypto/rsa/rsa_pk1.c @@ -10,10 +10,13 @@ #include "internal/constant_time.h" #include -#include "internal/cryptlib.h" #include #include #include +/* Just for the SSL_MAX_MASTER_KEY_LENGTH value */ +#include +#include "internal/cryptlib.h" +#include "crypto/rsa.h" int RSA_padding_add_PKCS1_type_1(unsigned char *to, int tlen, const unsigned char *from, int flen) @@ -253,3 +256,123 @@ int RSA_padding_check_PKCS1_type_2(unsigned char *to, int tlen, return constant_time_select_int(good, mlen, -1); } + +/* + * 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 + * in the buffer pointed to by |to| which should be |tlen| bytes long. |tlen| + * must be at least SSL_MAX_MASTER_KEY_LENGTH. The original decrypted message + * should be stored in |from| which must be |flen| bytes in length and padded + * such that |flen == RSA_size()|. The TLS protocol version that the client + * originally requested should be passed in |client_version|. Some buggy clients + * can exist which use the negotiated version instead of the originally + * requested protocol version. If it is necessary to work around this bug then + * the negotiated protocol version can be passed in |alt_version|, otherwise 0 + * should be passed. + * + * If the passed message is publicly invalid or some other error that can be + * treated in non-constant time occurs then -1 is returned. On success the + * length of the decrypted data is returned. This will always be + * SSL_MAX_MASTER_KEY_LENGTH. If an error occurs that should be treated in + * constant time then this function will appear to return successfully, but the + * decrypted data will be randomly generated (as per + * https://tools.ietf.org/html/rfc5246#section-7.4.7.1). + */ +int rsa_padding_check_PKCS1_type_2_TLS(unsigned char *to, size_t tlen, + const unsigned char *from, size_t flen, + int client_version, int alt_version) +{ + unsigned int i, good, version_good; + unsigned char rand_premaster_secret[SSL_MAX_MASTER_KEY_LENGTH]; + + /* + * If these checks fail then either the message in publicly invalid, or + * we've been called incorrectly. We can fail immediately. + */ + if (flen < RSA_PKCS1_PADDING_SIZE + SSL_MAX_MASTER_KEY_LENGTH + || tlen < SSL_MAX_MASTER_KEY_LENGTH) { + ERR_raise(ERR_LIB_RSA, RSA_R_PKCS_DECODING_ERROR); + return -1; + } + + /* + * Generate a random premaster secret to use in the event that we fail + * to decrypt. + */ + if (RAND_priv_bytes(rand_premaster_secret, + sizeof(rand_premaster_secret)) <= 0) { + ERR_raise(ERR_LIB_RSA, ERR_R_INTERNAL_ERROR); + return -1; + } + + good = constant_time_is_zero(from[0]); + good &= constant_time_eq(from[1], 2); + + /* Check we have the expected padding data */ + for (i = 2; i < flen - SSL_MAX_MASTER_KEY_LENGTH - 1; i++) + good &= ~constant_time_is_zero_8(from[i]); + good &= constant_time_is_zero_8(from[flen - SSL_MAX_MASTER_KEY_LENGTH - 1]); + + + /* + * If the version in the decrypted pre-master secret is correct then + * version_good will be 0xff, otherwise it'll be zero. The + * Klima-Pokorny-Rosa extension of Bleichenbacher's attack + * (http://eprint.iacr.org/2003/052/) exploits the version number + * check as a "bad version oracle". Thus version checks are done in + * constant time and are treated like any other decryption error. + */ + version_good = + constant_time_eq(from[flen - SSL_MAX_MASTER_KEY_LENGTH], + (client_version >> 8) & 0xff); + version_good &= + constant_time_eq(from[flen - SSL_MAX_MASTER_KEY_LENGTH + 1], + client_version & 0xff); + + /* + * The premaster secret must contain the same version number as the + * ClientHello to detect version rollback attacks (strangely, the + * protocol does not offer such protection for DH ciphersuites). + * However, buggy clients exist that send the negotiated protocol + * version instead if the server does not support the requested + * protocol version. If SSL_OP_TLS_ROLLBACK_BUG is set then we tolerate + * such clients. In that case alt_version will be non-zero and set to + * the negotiated version. + */ + if (alt_version > 0) { + unsigned int workaround_good; + + workaround_good = + constant_time_eq(from[flen - SSL_MAX_MASTER_KEY_LENGTH], + (alt_version >> 8) & 0xff); + workaround_good &= + constant_time_eq(from[flen - SSL_MAX_MASTER_KEY_LENGTH + 1], + alt_version & 0xff); + version_good |= workaround_good; + } + + good &= version_good; + + + /* + * Now copy the result over to the to buffer if good, or random data if + * not good. + */ + for (i = 0; i < SSL_MAX_MASTER_KEY_LENGTH; i++) { + to[i] = + constant_time_select_8(good, + from[flen - SSL_MAX_MASTER_KEY_LENGTH + i], + rand_premaster_secret[i]); + } + + /* + * We must not leak whether a decryption failure occurs because of + * Bleichenbacher's attack on PKCS #1 v1.5 RSA padding (see RFC 2246, + * section 7.4.7.1). The code follows that advice of the TLS RFC and + * generates a random premaster secret for the case that the decrypt + * fails. See https://tools.ietf.org/html/rfc5246#section-7.4.7.1 + * So, whether we actually succeeded or not, return success. + */ + + return SSL_MAX_MASTER_KEY_LENGTH; +} diff --git a/include/crypto/rsa.h b/include/crypto/rsa.h index 6d2e7ffb53..29256371f6 100644 --- a/include/crypto/rsa.h +++ b/include/crypto/rsa.h @@ -18,4 +18,8 @@ int rsa_set0_all_params(RSA *r, const STACK_OF(BIGNUM) *primes, int rsa_get0_all_params(RSA *r, STACK_OF(BIGNUM_const) *primes, STACK_OF(BIGNUM_const) *exps, STACK_OF(BIGNUM_const) *coeffs); + +int rsa_padding_check_PKCS1_type_2_TLS(unsigned char *to, size_t tlen, + const unsigned char *from, size_t flen, + int client_version, int alt_version); #endif diff --git a/include/openssl/core_names.h b/include/openssl/core_names.h index 053432e0f0..e441ddf6c8 100644 --- a/include/openssl/core_names.h +++ b/include/openssl/core_names.h @@ -194,13 +194,15 @@ extern "C" { #define OSSL_SIGNATURE_PARAM_DIGEST_SIZE "digest-size" /* Asym cipher parameters */ -#define OSSL_ASYM_CIPHER_PARAM_PAD_MODE "pad-mode" -#define OSSL_ASYM_CIPHER_PARAM_OAEP_DIGEST OSSL_ALG_PARAM_DIGEST -#define OSSL_ASYM_CIPHER_PARAM_OAEP_DIGEST_PROPS "digest-props" -#define OSSL_ASYM_CIPHER_PARAM_MGF1_DIGEST "mgf1-digest" -#define OSSL_ASYM_CIPHER_PARAM_MGF1_DIGEST_PROPS "mgf1-digest-props" -#define OSSL_ASYM_CIPHER_PARAM_OAEP_LABEL "oaep-label" -#define OSSL_ASYM_CIPHER_PARAM_OAEP_LABEL_LEN "oaep-label-len" +#define OSSL_ASYM_CIPHER_PARAM_PAD_MODE "pad-mode" +#define OSSL_ASYM_CIPHER_PARAM_OAEP_DIGEST OSSL_ALG_PARAM_DIGEST +#define OSSL_ASYM_CIPHER_PARAM_OAEP_DIGEST_PROPS "digest-props" +#define OSSL_ASYM_CIPHER_PARAM_MGF1_DIGEST "mgf1-digest" +#define OSSL_ASYM_CIPHER_PARAM_MGF1_DIGEST_PROPS "mgf1-digest-props" +#define OSSL_ASYM_CIPHER_PARAM_OAEP_LABEL "oaep-label" +#define OSSL_ASYM_CIPHER_PARAM_OAEP_LABEL_LEN "oaep-label-len" +#define OSSL_ASYM_CIPHER_PARAM_TLS_CLIENT_VERSION "tls-client-version" +#define OSSL_ASYM_CIPHER_PARAM_TLS_NEGOTIATED_VERSION "tls-negotiated-version" /* * Serializer parameters diff --git a/include/openssl/rsa.h b/include/openssl/rsa.h index 02a02160b3..9753c22c2c 100644 --- a/include/openssl/rsa.h +++ b/include/openssl/rsa.h @@ -182,13 +182,15 @@ int EVP_PKEY_CTX_get0_rsa_oaep_label(EVP_PKEY_CTX *ctx, unsigned char **label); # define EVP_PKEY_CTRL_RSA_KEYGEN_PRIMES (EVP_PKEY_ALG_CTRL + 13) -# define RSA_PKCS1_PADDING 1 -# define RSA_SSLV23_PADDING 2 -# define RSA_NO_PADDING 3 -# define RSA_PKCS1_OAEP_PADDING 4 -# define RSA_X931_PADDING 5 +# define RSA_PKCS1_PADDING 1 +# define RSA_SSLV23_PADDING 2 +# define RSA_NO_PADDING 3 +# define RSA_PKCS1_OAEP_PADDING 4 +# define RSA_X931_PADDING 5 + /* EVP_PKEY_ only */ -# define RSA_PKCS1_PSS_PADDING 6 +# define RSA_PKCS1_PSS_PADDING 6 +# define RSA_PKCS1_WITH_TLS_PADDING 7 # define RSA_PKCS1_PADDING_SIZE 11 diff --git a/providers/common/include/prov/providercommonerr.h b/providers/common/include/prov/providercommonerr.h index 85e5856d30..d97b735ca9 100644 --- a/providers/common/include/prov/providercommonerr.h +++ b/providers/common/include/prov/providercommonerr.h @@ -53,9 +53,11 @@ int ERR_load_PROV_strings(void); # define PROV_R_BAD_DECRYPT 100 # define PROV_R_BAD_ENCODING 141 # define PROV_R_BAD_LENGTH 142 +# define PROV_R_BAD_TLS_CLIENT_VERSION 161 # define PROV_R_BN_ERROR 160 # define PROV_R_BOTH_MODE_AND_MODE_INT 127 # define PROV_R_CIPHER_OPERATION_FAILED 102 +# define PROV_R_FAILED_TO_DECRYPT 162 # define PROV_R_FAILED_TO_GENERATE_KEY 121 # define PROV_R_FAILED_TO_GET_PARAMETER 103 # define PROV_R_FAILED_TO_SET_PARAMETER 104 diff --git a/providers/common/provider_err.c b/providers/common/provider_err.c index 7cae969c79..eba3dbef5f 100644 --- a/providers/common/provider_err.c +++ b/providers/common/provider_err.c @@ -19,11 +19,14 @@ static const ERR_STRING_DATA PROV_str_reasons[] = { {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_BAD_DECRYPT), "bad decrypt"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_BAD_ENCODING), "bad encoding"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_BAD_LENGTH), "bad length"}, + {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_BAD_TLS_CLIENT_VERSION), + "bad tls client version"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_BN_ERROR), "bn error"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_BOTH_MODE_AND_MODE_INT), "both mode and mode int"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_CIPHER_OPERATION_FAILED), "cipher operation failed"}, + {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_FAILED_TO_DECRYPT), "failed to decrypt"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_FAILED_TO_GENERATE_KEY), "failed to generate key"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_FAILED_TO_GET_PARAMETER), diff --git a/providers/implementations/asymciphers/rsa_enc.c b/providers/implementations/asymciphers/rsa_enc.c index 9b17377783..53fc6de265 100644 --- a/providers/implementations/asymciphers/rsa_enc.c +++ b/providers/implementations/asymciphers/rsa_enc.c @@ -14,7 +14,10 @@ #include #include #include +/* Just for SSL_MAX_MASTER_KEY_LENGTH */ +#include #include "internal/constant_time.h" +#include "crypto/rsa.h" #include "prov/providercommonerr.h" #include "prov/provider_ctx.h" #include "prov/implementations.h" @@ -51,6 +54,9 @@ typedef struct { /* OAEP label */ unsigned char *oaep_label; size_t oaep_labellen; + /* TLS padding */ + unsigned int client_version; + unsigned int alt_version; } PROV_RSA_CTX; static void *rsa_newctx(void *provctx) @@ -130,38 +136,70 @@ static int rsa_decrypt(void *vprsactx, unsigned char *out, size_t *outlen, { PROV_RSA_CTX *prsactx = (PROV_RSA_CTX *)vprsactx; int ret; + size_t len = RSA_size(prsactx->rsa); - if (out == NULL) { - size_t len = RSA_size(prsactx->rsa); + if (prsactx->pad_mode == RSA_PKCS1_WITH_TLS_PADDING) { + if (out == NULL) { + *outlen = SSL_MAX_MASTER_KEY_LENGTH; + return 1; + } + if (outsize < SSL_MAX_MASTER_KEY_LENGTH) { + ERR_raise(ERR_LIB_PROV, PROV_R_BAD_LENGTH); + return 0; + } + } else { + if (out == NULL) { + if (len == 0) { + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KEY); + return 0; + } + *outlen = len; + return 1; + } - if (len == 0) { - ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KEY); + if (outsize < len) { + ERR_raise(ERR_LIB_PROV, PROV_R_BAD_LENGTH); return 0; } - *outlen = len; - return 1; } - if (prsactx->pad_mode == RSA_PKCS1_OAEP_PADDING) { - int rsasize = RSA_size(prsactx->rsa); + if (prsactx->pad_mode == RSA_PKCS1_OAEP_PADDING + || prsactx->pad_mode == RSA_PKCS1_WITH_TLS_PADDING) { unsigned char *tbuf; - if ((tbuf = OPENSSL_malloc(rsasize)) == NULL) { + if ((tbuf = OPENSSL_malloc(len)) == NULL) { PROVerr(0, ERR_R_MALLOC_FAILURE); return 0; } ret = RSA_private_decrypt(inlen, in, tbuf, prsactx->rsa, RSA_NO_PADDING); - if (ret <= 0) { + /* + * With no padding then, on success ret should be len, otherwise an + * error occurred (non-constant time) + */ + if (ret != (int)len) { OPENSSL_free(tbuf); + ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_DECRYPT); return 0; } - ret = RSA_padding_check_PKCS1_OAEP_mgf1(out, ret, tbuf, - ret, ret, - prsactx->oaep_label, - prsactx->oaep_labellen, - prsactx->oaep_md, - prsactx->mgf1_md); + if (prsactx->pad_mode == RSA_PKCS1_OAEP_PADDING) { + ret = RSA_padding_check_PKCS1_OAEP_mgf1(out, outsize, tbuf, + len, len, + prsactx->oaep_label, + prsactx->oaep_labellen, + prsactx->oaep_md, + prsactx->mgf1_md); + } else { + /* RSA_PKCS1_WITH_TLS_PADDING */ + if (prsactx->client_version <= 0) { + ERR_raise(ERR_LIB_PROV, PROV_R_BAD_TLS_CLIENT_VERSION); + return 0; + } + ret = rsa_padding_check_PKCS1_type_2_TLS(out, outsize, + tbuf, len, + prsactx->client_version, + prsactx->alt_version); + } OPENSSL_free(tbuf); } else { ret = RSA_private_decrypt(inlen, in, out, prsactx->rsa, @@ -252,6 +290,14 @@ static int rsa_get_ctx_params(void *vprsactx, OSSL_PARAM *params) if (p != NULL && !OSSL_PARAM_set_size_t(p, prsactx->oaep_labellen)) return 0; + p = OSSL_PARAM_locate(params, OSSL_ASYM_CIPHER_PARAM_TLS_CLIENT_VERSION); + if (p != NULL && !OSSL_PARAM_set_uint(p, prsactx->client_version)) + return 0; + + p = OSSL_PARAM_locate(params, OSSL_ASYM_CIPHER_PARAM_TLS_NEGOTIATED_VERSION); + if (p != NULL && !OSSL_PARAM_set_uint(p, prsactx->alt_version)) + return 0; + return 1; } @@ -262,6 +308,8 @@ static const OSSL_PARAM known_gettable_ctx_params[] = { OSSL_PARAM_DEFN(OSSL_ASYM_CIPHER_PARAM_OAEP_LABEL, OSSL_PARAM_OCTET_PTR, NULL, 0), OSSL_PARAM_size_t(OSSL_ASYM_CIPHER_PARAM_OAEP_LABEL_LEN, NULL), + OSSL_PARAM_uint(OSSL_ASYM_CIPHER_PARAM_TLS_CLIENT_VERSION, NULL), + OSSL_PARAM_uint(OSSL_ASYM_CIPHER_PARAM_TLS_NEGOTIATED_VERSION, NULL), OSSL_PARAM_END }; @@ -354,6 +402,24 @@ static int rsa_set_ctx_params(void *vprsactx, const OSSL_PARAM params[]) prsactx->oaep_labellen = tmp_labellen; } + p = OSSL_PARAM_locate_const(params, OSSL_ASYM_CIPHER_PARAM_TLS_CLIENT_VERSION); + if (p != NULL) { + unsigned int client_version; + + if (!OSSL_PARAM_get_uint(p, &client_version)) + return 0; + prsactx->client_version = client_version; + } + + p = OSSL_PARAM_locate_const(params, OSSL_ASYM_CIPHER_PARAM_TLS_NEGOTIATED_VERSION); + if (p != NULL) { + unsigned int alt_version; + + if (!OSSL_PARAM_get_uint(p, &alt_version)) + return 0; + prsactx->alt_version = alt_version; + } + return 1; } @@ -363,6 +429,8 @@ static const OSSL_PARAM known_settable_ctx_params[] = { OSSL_PARAM_utf8_string(OSSL_ASYM_CIPHER_PARAM_MGF1_DIGEST, NULL, 0), OSSL_PARAM_utf8_string(OSSL_ASYM_CIPHER_PARAM_MGF1_DIGEST_PROPS, NULL, 0), OSSL_PARAM_octet_string(OSSL_ASYM_CIPHER_PARAM_OAEP_LABEL, NULL, 0), + OSSL_PARAM_uint(OSSL_ASYM_CIPHER_PARAM_TLS_CLIENT_VERSION, NULL), + OSSL_PARAM_uint(OSSL_ASYM_CIPHER_PARAM_TLS_NEGOTIATED_VERSION, NULL), OSSL_PARAM_END };