Teach the RSA implementation about TLS RSA Key Transport
authorMatt Caswell <matt@openssl.org>
Mon, 11 Nov 2019 14:37:02 +0000 (14:37 +0000)
committerMatt Caswell <matt@openssl.org>
Thu, 5 Dec 2019 16:12:18 +0000 (16:12 +0000)
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 <tmraz@fedoraproject.org>
(Merged from https://github.com/openssl/openssl/pull/10411)

crypto/err/openssl.txt
crypto/rsa/rsa_pk1.c
include/crypto/rsa.h
include/openssl/core_names.h
include/openssl/rsa.h
providers/common/include/prov/providercommonerr.h
providers/common/provider_err.c
providers/implementations/asymciphers/rsa_enc.c

index e81c32fe4ff16af3b7ba6137830e5397b58e067c..4baed5c48e8fec426a50c0e5016bfc1755eaae64 100644 (file)
@@ -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
index 0c77422404eb7f863d5154301014eb0028af4595..007e9b8cd533e46f7034d1c9a60cda68c42e0beb 100644 (file)
 #include "internal/constant_time.h"
 
 #include <stdio.h>
-#include "internal/cryptlib.h"
 #include <openssl/bn.h>
 #include <openssl/rsa.h>
 #include <openssl/rand.h>
+/* Just for the SSL_MAX_MASTER_KEY_LENGTH value */
+#include <openssl/ssl.h>
+#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;
+}
index 6d2e7ffb5326258cdbe0e9ec6503e829d99d1c72..29256371f6580947e8903f7ade37451a6e1caefc 100644 (file)
@@ -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
index 053432e0f0607229b4cdc9361a170e07afbdd0d4..e441ddf6c8b5fee256926cb6438dacb8d3d0eabc 100644 (file)
@@ -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
index 02a02160b3141217521a9d0f7d2230906dd42261..9753c22c2c830a9d4f436eb5e182fdb0828d9ff2 100644 (file)
@@ -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
 
index 85e5856d30b6fb4ad48630decea97a13a7116197..d97b735ca9c97e09aa2e9b959fd4b50356df957a 100644 (file)
@@ -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
index 7cae969c79ac24517b771f53ed4744b8a4e58da8..eba3dbef5ff91a5d1fc0c5e5d29d6eea807961a5 100644 (file)
@@ -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),
index 9b173777835ec5b4033df1110bcad83e3cc9b98d..53fc6de26554e888b4153eda1e355bf683542b9a 100644 (file)
 #include <openssl/rsa.h>
 #include <openssl/params.h>
 #include <openssl/err.h>
+/* Just for SSL_MAX_MASTER_KEY_LENGTH */
+#include <openssl/ssl.h>
 #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
 };