From c26e049bf40d656bc51429bad190b82fbf63f0c7 Mon Sep 17 00:00:00 2001
From: Ingo Franzki <ifranzki@linux.ibm.com>
Date: Mon, 15 Jan 2024 12:53:37 +0100
Subject: [PATCH] common: Add support for implicit rejection for RSA PKCS#1
 v1.5 de-padding

Implicit rejection returns a pseudo random message in case the RSA PKCS#1 v1.5
padding is incorrect, but returns no error. The pseudo random message is based
on static secret data (the private exponent) and the provided ciphertext, so
that the attacker cannot determine that the returned value is randomly generated
instead of the result of decryption and de-padding.

The implicit rejection algorithm is the same as used by OpenSSL.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
---
 COPYRIGHTS                    |  14 +-
 usr/lib/common/h_extern.h     |  11 +-
 usr/lib/common/mech_openssl.c | 402 +++++++++++++++++++++++++++++++++-
 usr/lib/common/mech_rsa.c     | 147 ++++++++-----
 4 files changed, 515 insertions(+), 59 deletions(-)

diff --git a/COPYRIGHTS b/COPYRIGHTS
index 2bb3dffe7..21b6b702e 100644
--- a/COPYRIGHTS
+++ b/COPYRIGHTS
@@ -12,19 +12,29 @@ For code originating from OpenSSL:
  *   Note that in OpenSSL the file crypto/bn/rsa_sup_mul.c does no longer
  *   exist, it was removed with commit https://github.com/openssl/openssl/commit/4209ce68d8fe8b1506494efa03d378d05baf9ff8
  * - usr/lib/common/constant_time.h: Copied unchanged from OpenSSL from
-     include/internal/constant_time.h
+ *   include/internal/constant_time.h
  * - The implementation of function rsa_parse_block_type_2() in 
  *   usr/lib/common/mech_rsa.c is copied from OpenSSL's function
  *   ossl_rsa_padding_check_PKCS1_type_2() in crypto/rsa/rsa_pk1.c
  *   and is slightly modified to fit to the OpenCryptoki environment.
  *   See comment in function rsa_parse_block_type_2() for a list of changes.
+ * - The implementation of function openssl_specific_rsa_derive_kdk() in
+ *   usr/lib/common/mech_openssl.c is copied from OpenSSL's function
+ *   derive_kdk() in crypto/rsa/rsa_ossl.c and is slightly modified to fit to 
+ *   the OpenCryptoki environment. See comment in function 
+ *   openssl_specific_rsa_derive_kdk() for a list of changes.
+ * - The implementation of function openssl_specific_rsa_prf() in
+ *   usr/lib/common/mech_openssl.c is copied from OpenSSL's function
+ *   ossl_rsa_prf() in crypto/rsa/rsapk1.c and is slightly modified to fit to 
+ *   the OpenCryptoki environment. See comment in function 
+ *   openssl_specific_rsa_prf() for a list of changes.
  * - The implementation of function decode_eme_oaep() in
  *   usr/lib/common/mech_rsa.c is copied from OpenSSL's function
  *   RSA_padding_check_PKCS1_OAEP_mgf1() in crypto/rsa/rsa_oaep.c and is
  *   slightly modified to fit to the OpenCryptoki environment. See comment in
  *   function decode_eme_oaep() for a list of changes.
  *
- * Copyright 1999-2023 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 1999-2024 The OpenSSL Project Authors. All Rights Reserved.
  *
  * The OpenSSL code is licensed under the Apache License 2.0 (the "License"). 
  * You can obtain a copy in the file LICENSE in the OpenSSL source distribution
diff --git a/usr/lib/common/h_extern.h b/usr/lib/common/h_extern.h
index 7400c6dba..1d79a4f71 100644
--- a/usr/lib/common/h_extern.h
+++ b/usr/lib/common/h_extern.h
@@ -731,7 +731,8 @@ CK_RV rsa_format_block(STDLL_TokData_t *tokdata,
 CK_RV rsa_parse_block(CK_BYTE *in_data,
                       CK_ULONG in_data_len,
                       CK_BYTE *out_data,
-                      CK_ULONG *out_data_len, CK_ULONG type);
+                      CK_ULONG *out_data_len, CK_ULONG type,
+                      CK_BYTE *kdk, CK_ULONG kdklen);
 
 CK_RV get_mgf_mech(CK_RSA_PKCS_MGF_TYPE mgf, CK_MECHANISM_TYPE *mech);
 
@@ -3182,6 +3183,14 @@ CK_RV openssl_specific_hmac_update(SIGN_VERIFY_CONTEXT *ctx, CK_BYTE *in_data,
 CK_RV openssl_specific_hmac_final(SIGN_VERIFY_CONTEXT *ctx, CK_BYTE *signature,
                                   CK_ULONG *sig_len, CK_BBOOL sign);
 
+CK_RV openssl_specific_rsa_derive_kdk(STDLL_TokData_t *tokdata, OBJECT *key_obj,
+                                      const CK_BYTE *in, CK_ULONG inlen,
+                                      CK_BYTE *kdk, CK_ULONG kdklen);
+CK_RV openssl_specific_rsa_prf(CK_BYTE *out, CK_ULONG outlen,
+                               const char *label, CK_ULONG labellen,
+                               const CK_BYTE *kdk, CK_ULONG kdklen,
+                               uint16_t bitlen);
+
 #include "tok_spec_struct.h"
 extern token_spec_t token_specific;
 
diff --git a/usr/lib/common/mech_openssl.c b/usr/lib/common/mech_openssl.c
index 9983fcb3b..da5152896 100644
--- a/usr/lib/common/mech_openssl.c
+++ b/usr/lib/common/mech_openssl.c
@@ -1154,6 +1154,7 @@ CK_RV openssl_specific_rsa_pkcs_decrypt(STDLL_TokData_t *tokdata,
     CK_RV rc;
     CK_BYTE out[MAX_RSA_KEYLEN];
     CK_ULONG modulus_bytes;
+    unsigned char kdk[SHA256_HASH_SIZE] = { 0 };
 
     modulus_bytes = in_data_len;
 
@@ -1163,7 +1164,16 @@ CK_RV openssl_specific_rsa_pkcs_decrypt(STDLL_TokData_t *tokdata,
         goto done;
     }
 
-    rc = rsa_parse_block(out, modulus_bytes, out_data, out_data_len, PKCS_BT_2);
+    rc = openssl_specific_rsa_derive_kdk(tokdata, key_obj,
+                                         in_data, in_data_len,
+                                         kdk, sizeof(kdk));
+    if (rc != CKR_OK) {
+        TRACE_DEVEL("openssl_specific_rsa_derive_kdk failed\n");
+        goto done;
+    }
+
+    rc = rsa_parse_block(out, modulus_bytes, out_data, out_data_len, PKCS_BT_2,
+                         kdk, sizeof(kdk));
 
 done:
     OPENSSL_cleanse(out, sizeof(out));
@@ -1254,7 +1264,7 @@ CK_RV openssl_specific_rsa_pkcs_verify(STDLL_TokData_t *tokdata, SESSION *sess,
     }
 
     rc = rsa_parse_block(out, modulus_bytes, out_data, &out_data_len,
-                         PKCS_BT_1);
+                         PKCS_BT_1, NULL, 0);
     if (rc == CKR_ENCRYPTED_DATA_INVALID) {
         TRACE_ERROR("%s\n", ock_err(ERR_SIGNATURE_INVALID));
         return CKR_SIGNATURE_INVALID;
@@ -1318,7 +1328,8 @@ CK_RV openssl_specific_rsa_pkcs_verify_recover(STDLL_TokData_t *tokdata,
         return rc;
     }
 
-    rc = rsa_parse_block(out, modulus_bytes, out_data, out_data_len, PKCS_BT_1);
+    rc = rsa_parse_block(out, modulus_bytes, out_data, out_data_len, PKCS_BT_1,
+                         NULL, 0);
     if (rc == CKR_ENCRYPTED_DATA_INVALID) {
         TRACE_ERROR("%s\n", ock_err(ERR_SIGNATURE_INVALID));
         return CKR_SIGNATURE_INVALID;
@@ -4983,3 +4994,388 @@ CK_RV openssl_specific_hmac_final(SIGN_VERIFY_CONTEXT *ctx, CK_BYTE *signature,
     ctx->context = NULL;
     return rv;
 }
+
+static CK_RV calc_rsa_priv_exp(STDLL_TokData_t *tokdata, OBJECT *key_obj,
+                               CK_BYTE *priv_exp, CK_ULONG priv_exp_len)
+{
+    CK_ATTRIBUTE *modulus = NULL, *pub_exp = NULL;
+    CK_ATTRIBUTE *prime1 = NULL, *prime2 = NULL;
+    BN_CTX *bn_ctx;
+    BIGNUM *n, *e, *p, *q, *d;
+    CK_RV rc;
+
+    UNUSED(tokdata);
+
+    bn_ctx = BN_CTX_secure_new();
+    if (bn_ctx == NULL) {
+        TRACE_ERROR("BN_CTX_secure_new failed\n");
+        return CKR_FUNCTION_FAILED;
+    }
+
+    /* Get modulus a BIGNUM */
+    rc = template_attribute_get_non_empty(key_obj->template, CKA_MODULUS,
+                                          &modulus);
+    if (rc != CKR_OK) {
+        TRACE_ERROR("Failed to get CKA_MODULUS\n");
+        goto done;
+    }
+
+    n = BN_CTX_get(bn_ctx);
+    if (n == NULL ||
+        BN_bin2bn(modulus->pValue, modulus->ulValueLen, n) == NULL) {
+        TRACE_ERROR("BN_CTX_get/BN_bin2bn failed for modulus\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto done;
+    }
+    BN_set_flags(n, BN_FLG_CONSTTIME);
+
+    /* Get public exponent a BIGNUM */
+    rc = template_attribute_get_non_empty(key_obj->template,
+                                          CKA_PUBLIC_EXPONENT, &pub_exp);
+    if (rc != CKR_OK) {
+        TRACE_ERROR("Failed to get CKA_PUBLIC_EXPONENT\n");
+        goto done;
+    }
+
+    e = BN_CTX_get(bn_ctx);
+    if (e == NULL ||
+        BN_bin2bn(pub_exp->pValue, pub_exp->ulValueLen, e) == NULL) {
+        TRACE_ERROR("BN_CTX_get/BN_bin2bn failed for public exponent\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto done;
+    }
+    BN_set_flags(e, BN_FLG_CONSTTIME);
+
+    /* Get prime1 a BIGNUM */
+    rc = template_attribute_get_non_empty(key_obj->template, CKA_PRIME_1,
+                                          &prime1);
+    if (rc != CKR_OK) {
+        TRACE_ERROR("Failed to get CKA_PRIME_1\n");
+        goto done;
+    }
+
+    p = BN_CTX_get(bn_ctx);
+    if (p == NULL ||
+        BN_bin2bn(prime1->pValue, prime1->ulValueLen, p) == NULL) {
+        TRACE_ERROR("BN_CTX_get/BN_bin2bn failed for prime1\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto done;
+    }
+    BN_set_flags(p, BN_FLG_CONSTTIME);
+
+    /* Get prime2 a BIGNUM */
+    rc = template_attribute_get_non_empty(key_obj->template, CKA_PRIME_2,
+                                          &prime2);
+    if (rc != CKR_OK) {
+        TRACE_ERROR("Failed to get CKA_PRIME_2\n");
+        goto done;
+    }
+
+    q = BN_CTX_get(bn_ctx);
+    if (q == NULL ||
+        BN_bin2bn(prime2->pValue, prime2->ulValueLen, q) == NULL) {
+        TRACE_ERROR("BN_CTX_get/BN_bin2bn failed for prime2\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto done;
+    }
+    BN_set_flags(q, BN_FLG_CONSTTIME);
+
+    d = BN_CTX_get(bn_ctx);
+    if (d == NULL) {
+        TRACE_ERROR("BN_CTX_get failed to get d\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto done;
+    }
+    BN_set_flags(d, BN_FLG_CONSTTIME);
+
+    /*
+     * phi(n) = (p - 1 )(q - 1) = n - p - q + 1
+     * d = e ^{-1} mod phi(n).
+     */
+    if (BN_copy(d, n) == NULL ||
+        BN_sub(d, d, p) == 0 ||
+        BN_sub(d, d, q) == 0 ||
+        BN_add_word(d, 1) == 0 ||
+        BN_mod_inverse(d, e, d, bn_ctx) == NULL) {
+        TRACE_ERROR("Failed to calculate private key part d\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto done;
+    }
+
+    if (BN_bn2binpad(d, priv_exp, priv_exp_len) <= 0) {
+        TRACE_ERROR("BN_bn2binpad failed\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto done;
+    }
+
+done:
+    BN_CTX_free(bn_ctx);
+
+    return rc;
+}
+
+CK_RV openssl_specific_rsa_derive_kdk(STDLL_TokData_t *tokdata, OBJECT *key_obj,
+                                      const CK_BYTE *in, CK_ULONG inlen,
+                                      CK_BYTE *kdk, CK_ULONG kdklen)
+{
+    CK_ATTRIBUTE *priv_exp_attr = NULL, *modulus = NULL;
+    CK_BYTE *priv_exp = NULL, *buf = NULL;
+    EVP_PKEY *pkey = NULL;
+    EVP_MD_CTX *mdctx = NULL;
+    const EVP_MD *md = NULL;
+    size_t md_len;
+    unsigned char d_hash[SHA256_HASH_SIZE] = { 0 };
+    CK_RV rc;
+
+    /*
+     * The implementation of this function is copied from OpenSSL's function
+     * derive_kdk() in crypto/rsa/rsa_ossl.c and is slightly modified to fit to
+     * the OpenCryptoki environment.
+     * Changes include:
+     * - Different variable and define names.
+     * - Usage of TRACE_ERROR to report errors and issue debug messages.
+     * - Different return codes.
+     * - Different code to get the private key component 'd'.
+     * - Use of the EVP APIs instead of the internal APIs for Digest and HMAC
+     *   operations.
+     */
+
+    if (kdklen != SHA256_HASH_SIZE) {
+        TRACE_ERROR("KDK length is wrong\n");
+        return CKR_ARGUMENTS_BAD;
+    }
+
+    rc = template_attribute_get_non_empty(key_obj->template, CKA_MODULUS,
+                                          &modulus);
+    if (rc != CKR_OK) {
+        TRACE_ERROR("Failed to get CKA_MODULUS\n");
+        return rc;
+    }
+
+    buf = calloc(1, modulus->ulValueLen);
+    if (buf == NULL) {
+        TRACE_ERROR("Failed to allocate a buffer for private exponent\n");
+        return CKR_HOST_MEMORY;
+    }
+
+    rc = template_attribute_get_non_empty(key_obj->template,
+                                          CKA_PRIVATE_EXPONENT, &priv_exp_attr);
+    if (rc != CKR_OK && rc != CKR_TEMPLATE_INCOMPLETE) {
+        TRACE_ERROR("Failed to get CKA_PRIVATE_EXPONENT\n");
+        goto out;
+    }
+
+    if (priv_exp_attr == NULL) {
+        rc = calc_rsa_priv_exp(tokdata, key_obj, buf, modulus->ulValueLen);
+        if (rc != CKR_OK) {
+            TRACE_ERROR("calc_rsa_priv_exp failed\n");
+            goto out;
+        }
+        priv_exp = buf;
+    } else {
+        if (priv_exp_attr->ulValueLen < modulus->ulValueLen) {
+            memcpy(buf + modulus->ulValueLen - priv_exp_attr->ulValueLen,
+                   priv_exp_attr->pValue, priv_exp_attr->ulValueLen);
+            priv_exp = buf;
+        } else {
+            priv_exp = (CK_BYTE *)priv_exp_attr->pValue +
+                            priv_exp_attr->ulValueLen - modulus->ulValueLen;
+        }
+    }
+
+    /*
+     * 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
+     * synthetically generated, which means that the padding check failed
+     */
+    md = EVP_sha256();
+    if (md == NULL) {
+        TRACE_ERROR("EVP_sha256 failed\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto out;
+    }
+
+    if (EVP_Digest(priv_exp, modulus->ulValueLen, d_hash, NULL,
+                   md, NULL) <= 0) {
+        TRACE_ERROR("EVP_Digest failed\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto out;
+    }
+
+    pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, d_hash, sizeof(d_hash));
+    if (pkey == NULL) {
+        TRACE_ERROR("EVP_PKEY_new_mac_key() failed.\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto out;
+    }
+
+    mdctx = EVP_MD_CTX_create();
+    if (mdctx == NULL) {
+        TRACE_ERROR("EVP_MD_CTX_create() failed.\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto out;
+    }
+
+    if (EVP_DigestSignInit(mdctx, NULL, md, NULL, pkey) != 1) {
+        TRACE_ERROR("EVP_DigestSignInit failed\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto out;
+    }
+
+    if (inlen < modulus->ulValueLen) {
+        memset(buf, 0, modulus->ulValueLen - inlen);
+        if (EVP_DigestSignUpdate(mdctx, buf, modulus->ulValueLen - inlen)!= 1) {
+            TRACE_ERROR("EVP_DigestSignUpdate failed\n");
+            rc = CKR_FUNCTION_FAILED;
+            goto out;
+        }
+    }
+    if (EVP_DigestSignUpdate(mdctx, in, inlen) != 1) {
+        TRACE_ERROR("EVP_DigestSignUpdate failed\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto out;
+    }
+
+    md_len = kdklen;
+    if (EVP_DigestSignFinal(mdctx, kdk, &md_len) != 1 ||
+        md_len != kdklen) {
+        TRACE_ERROR("EVP_DigestSignFinal failed\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto out;
+    }
+
+    rc = CKR_OK;
+
+out:
+    if (buf != NULL)
+        free(buf);
+    if (pkey != NULL)
+        EVP_PKEY_free(pkey);
+    if (mdctx != NULL)
+        EVP_MD_CTX_free(mdctx);
+
+    return rc;
+}
+
+CK_RV openssl_specific_rsa_prf(CK_BYTE *out, CK_ULONG outlen,
+                               const char *label, CK_ULONG labellen,
+                               const CK_BYTE *kdk, CK_ULONG kdklen,
+                               uint16_t bitlen)
+{
+    CK_RV rc;
+    CK_ULONG pos;
+    uint16_t iter = 0;
+    unsigned char be_iter[sizeof(iter)];
+    unsigned char be_bitlen[sizeof(bitlen)];
+    EVP_PKEY *pkey = NULL;
+    EVP_MD_CTX *mdctx = NULL;
+    unsigned char hmac_out[SHA256_HASH_SIZE];
+    size_t md_len;
+
+    /*
+     * The implementation of this function is copied from OpenSSL's function
+     * ossl_rsa_prf() in crypto/rsa/rsapk1.c and is slightly modified to fit to
+     * the providers environment.
+     * Changes include:
+     * - Different variable and define names.
+     * - Usage of TRACE_ERROR report errors and issue debug messages.
+     * - Different return codes.
+     * - Use of the EVP API instead of the internal APIs for HMAC operations.
+     */
+
+    if (kdklen != SHA256_HASH_SIZE) {
+        TRACE_ERROR("invalid kdklen\n");
+        return CKR_ARGUMENTS_BAD;
+    }
+    if (outlen * 8 != bitlen) {
+        TRACE_ERROR("invalid outlen\n");
+        return CKR_ARGUMENTS_BAD;
+    }
+
+    be_bitlen[0] = (bitlen >> 8) & 0xff;
+    be_bitlen[1] = bitlen & 0xff;
+
+    pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, kdk, kdklen);
+    if (pkey == NULL) {
+        TRACE_ERROR("EVP_PKEY_new_mac_key() failed.\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto out;
+    }
+
+    mdctx = EVP_MD_CTX_create();
+    if (mdctx == NULL) {
+        TRACE_ERROR("EVP_MD_CTX_create() failed.\n");
+        rc = CKR_FUNCTION_FAILED;
+        goto out;
+    }
+
+    /*
+     * 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
+     * synthetically generated, which means that the padding check failed
+     */
+    for (pos = 0; pos < outlen; pos += SHA256_HASH_SIZE, iter++) {
+        if (EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1) {
+            TRACE_ERROR("EVP_DigestSignInit failed\n");
+            rc = CKR_FUNCTION_FAILED;
+            goto out;
+        }
+
+        be_iter[0] = (iter >> 8) & 0xff;
+        be_iter[1] = iter & 0xff;
+
+        if (EVP_DigestSignUpdate(mdctx, be_iter, sizeof(be_iter)) != 1) {
+            TRACE_ERROR("EVP_DigestSignUpdate failed\n");
+            rc = CKR_FUNCTION_FAILED;
+            goto out;
+        }
+        if (EVP_DigestSignUpdate(mdctx, (unsigned char *)label, labellen) != 1) {
+            TRACE_ERROR("EVP_DigestSignUpdate failed\n");
+            rc = CKR_FUNCTION_FAILED;
+            goto out;
+        }
+        if (EVP_DigestSignUpdate(mdctx, be_bitlen, sizeof(be_bitlen)) != 1) {
+            TRACE_ERROR("EVP_DigestSignUpdate failed\n");
+            rc = CKR_FUNCTION_FAILED;
+            goto out;
+        }
+
+        /*
+         * 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_HASH_SIZE;
+        if (pos + SHA256_HASH_SIZE > outlen) {
+            md_len = sizeof(hmac_out);
+            if (EVP_DigestSignFinal(mdctx, hmac_out, &md_len) != 1) {
+                TRACE_ERROR("EVP_DigestSignFinal failed\n");
+                rc = CKR_FUNCTION_FAILED;
+                goto out;
+            }
+            memcpy(out + pos, hmac_out, outlen - pos);
+        } else {
+            md_len = outlen - pos;
+            if (EVP_DigestSignFinal(mdctx, out + pos, &md_len) != 1) {
+                TRACE_ERROR("EVP_DigestSignFinal failed\n");
+                rc = CKR_FUNCTION_FAILED;
+                goto out;
+            }
+        }
+    }
+
+    rc = CKR_OK;
+
+out:
+    if (pkey != NULL)
+        EVP_PKEY_free(pkey);
+    if (mdctx != NULL)
+        EVP_MD_CTX_free(mdctx);
+
+    return rc;
+}
+
diff --git a/usr/lib/common/mech_rsa.c b/usr/lib/common/mech_rsa.c
index 7bab1a84f..7dc9589a2 100644
--- a/usr/lib/common/mech_rsa.c
+++ b/usr/lib/common/mech_rsa.c
@@ -289,21 +289,34 @@ static CK_RV rsa_parse_block_type_1(CK_BYTE *in_data,
     return rc;
 }
 
+#define MAX_LEN_GEN_TRIES 128
+
 static CK_RV rsa_parse_block_type_2(CK_BYTE *in_data,
                                     CK_ULONG in_data_len,
                                     CK_BYTE *out_data,
-                                    CK_ULONG *out_data_len)
+                                    CK_ULONG *out_data_len,
+                                    CK_BYTE *kdk, CK_ULONG kdklen)
 {
-    int i;
-    unsigned char *em = NULL;
-    unsigned int good, found_zero_byte, mask, equals0;
-    int zero_index = 0, msg_index, mlen = -1;
-    int out_len = *out_data_len;
-    int rsa_size = in_data_len;
+    unsigned int good = 0, found_zero_byte, equals0;
+    size_t zero_index = 0, msg_index;
+    unsigned char *synthetic = NULL;
+    int synthetic_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;
+    size_t i, j;
+    CK_RV rc;
+
+    if (kdk == NULL || kdklen == 0) {
+        TRACE_DEVEL("%s\n", ock_err(ERR_ARGUMENTS_BAD));
+        return CKR_ARGUMENTS_BAD;
+    }
 
     /*
      * The implementation of this function is copied from OpenSSL's function
-     * RSA_padding_check_PKCS1_type_2() in crypto/rsa/rsa_pk1.c
+     * ossl_rsa_padding_check_PKCS1_type_2() in crypto/rsa/rsa_pk1.c
      * and is slightly modified to fit to the OpenCryptoki environment.
      *
      * The OpenSSL code is licensed under the Apache License 2.0.
@@ -328,27 +341,67 @@ static CK_RV rsa_parse_block_type_2(CK_BYTE *in_data,
      * PKCS#1 v1.5 decryption. See "PKCS #1 v2.2: RSA Cryptography Standard",
      * section 7.2.2.
      */
-    if (rsa_size < RSA_PKCS1_PADDING_SIZE) {
+    if (in_data_len < RSA_PKCS1_PADDING_SIZE) {
         TRACE_DEVEL("%s\n", ock_err(ERR_FUNCTION_FAILED));
         return CKR_FUNCTION_FAILED;
     }
 
-    em = malloc(rsa_size);
-    if (em == NULL) {
-        TRACE_DEVEL("%s\n", ock_err(ERR_HOST_MEMORY));
+    /* Generate a random message to return in case the padding checks fail. */
+    synthetic = calloc(1, in_data_len);
+    if (synthetic == NULL) {
+        TRACE_ERROR("Failed to allocate synthetic buffer");
         return CKR_HOST_MEMORY;
     }
 
-    /* in_data_len is always equal to rsa_size */
-    memcpy(em, in_data, rsa_size);
+    rc = openssl_specific_rsa_prf(synthetic, in_data_len, "message", 7,
+                                  kdk, kdklen, in_data_len * 8);
+    if (rc != CKR_OK)
+        goto out;
+
+    /* decide how long the random message should be */
+    rc = openssl_specific_rsa_prf(candidate_lengths,
+                                  sizeof(candidate_lengths),
+                                  "length", 6, kdk, kdklen,
+                                  MAX_LEN_GEN_TRIES *
+                                              sizeof(len_candidate) * 8);
+    if (rc != CKR_OK)
+        goto out;
 
-    good = constant_time_is_zero(em[0]);
-    good &= constant_time_eq(em[1], 2);
+    /*
+     * max message size is the size of the modulus size minus 2 bytes for
+     * version and padding type and a minimum of 8 bytes padding
+     */
+    len_mask = max_sep_offset = in_data_len - 2 - 8;
+    /*
+     * we want a mask so let's 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;
+
+    synthetic_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;
+
+        synthetic_length = constant_time_select_int(
+                        constant_time_lt(len_candidate, max_sep_offset),
+                                         len_candidate, synthetic_length);
+    }
+
+    synth_msg_index = in_data_len - synthetic_length;
+
+    good = constant_time_is_zero(in_data[0]);
+    good &= constant_time_eq(in_data[1], 2);
 
     /* scan over padding data */
     found_zero_byte = 0;
-    for (i = 2; i < rsa_size; i++) {
-        equals0 = constant_time_is_zero(em[i]);
+    for (i = 2; i < in_data_len; i++) {
+        equals0 = constant_time_is_zero(in_data[i]);
 
         zero_index = constant_time_select_int(~found_zero_byte & equals0,
                                               i, zero_index);
@@ -356,7 +409,7 @@ static CK_RV rsa_parse_block_type_2(CK_BYTE *in_data,
     }
 
     /*
-     * PS must be at least 8 bytes long, and it starts two bytes into |em|.
+     * PS must be at least 8 bytes long, and it starts two bytes into |in_data|.
      * If we never found a 0-byte, then |zero_index| is 0 and the check
      * also fails.
      */
@@ -367,53 +420,41 @@ static CK_RV rsa_parse_block_type_2(CK_BYTE *in_data,
      * but in this case we also do not copy the message out.
      */
     msg_index = zero_index + 1;
-    mlen = rsa_size - msg_index;
 
     /*
-     * For good measure, do this check in constant time as well.
+     * old code returned an error in case the decrypted message wouldn't fit
+     * into the |out_data|, since that would leak information, return the
+     * synthetic message instead
      */
-    good &= constant_time_ge(out_len, mlen);
+    good &= constant_time_ge(*out_data_len, in_data_len - msg_index);
+
+    msg_index = constant_time_select_int(good, msg_index, synth_msg_index);
 
     /*
-     * Move the result in-place by |rsa_size|-RSA_PKCS1_PADDING_SIZE-|mlen|
-     * bytes to the left.
-     * Then if |good| move |mlen| bytes from |em|+RSA_PKCS1_PADDING_SIZE to
-     * |out_data|. Otherwise leave |out_data| unchanged.
-     * Copy the memory back in a way that does not reveal the size of
-     * the data being copied via a timing side channel. This requires copying
-     * parts of the buffer multiple times based on the bits set in the real
-     * length. Clear bits do a non-copy with identical access pattern.
-     * The loop below has overall complexity of O(N*log(N)).
+     * 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|
      */
-    out_len = constant_time_select_int(
-            constant_time_lt(rsa_size - RSA_PKCS1_PADDING_SIZE, out_len),
-            rsa_size - RSA_PKCS1_PADDING_SIZE,
-            out_len);
-    for (msg_index = 1; msg_index < rsa_size - RSA_PKCS1_PADDING_SIZE;
-                                                            msg_index <<= 1) {
-        mask = ~constant_time_eq(
-                    msg_index & (rsa_size - RSA_PKCS1_PADDING_SIZE - mlen), 0);
-        for (i = RSA_PKCS1_PADDING_SIZE; i < rsa_size - msg_index; i++)
-            em[i] = constant_time_select_8(mask, em[i + msg_index], em[i]);
-    }
-    for (i = 0; i < out_len; i++) {
-        mask = good & constant_time_lt(i, mlen);
-        out_data[i] = constant_time_select_8(
-                        mask, em[i + RSA_PKCS1_PADDING_SIZE], out_data[i]);
-    }
+    for (i = msg_index, j = 0; i < in_data_len && j < *out_data_len;
+                                                                i++, j++)
+        out_data[j] = constant_time_select_8(good, in_data[i], synthetic[i]);
 
-    OPENSSL_cleanse(em, rsa_size);
-    free(em);
+    *out_data_len = j;
 
-    *out_data_len = constant_time_select_int(good, mlen, 0);
+out:
+    if (synthetic != NULL)
+        free(synthetic);
 
-    return constant_time_select_int(good, CKR_OK, CKR_ENCRYPTED_DATA_INVALID);
+    return rc;
 }
 
 CK_RV rsa_parse_block(CK_BYTE *in_data,
                       CK_ULONG in_data_len,
                       CK_BYTE *out_data,
-                      CK_ULONG *out_data_len, CK_ULONG type)
+                      CK_ULONG *out_data_len, CK_ULONG type,
+                      CK_BYTE *kdk, CK_ULONG kdklen)
 {
     switch (type) {
     case PKCS_BT_1:
@@ -421,7 +462,7 @@ CK_RV rsa_parse_block(CK_BYTE *in_data,
                                         out_data, out_data_len);
     case PKCS_BT_2:
         return rsa_parse_block_type_2(in_data, in_data_len,
-                                       out_data, out_data_len);
+                                       out_data, out_data_len, kdk, kdklen);
     }
 
     return CKR_ARGUMENTS_BAD;
