From b9e1d344df1f850a9b15bce6294f72c1620d0b45 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Veronika=20Hanul=C3=ADkov=C3=A1?= <vhanulik@redhat.com>
Date: Mon, 13 Nov 2023 13:54:54 +0100
Subject: [PATCH 1/8] Reimplement removing of PKCS#1 v1.5 padding to be time
 constant

---
 src/common/Makefile.am      |   6 +-
 src/common/constant-time.h  | 128 ++++++++++++++++++++++++++++++++++++
 src/libopensc/internal.h    |   4 +-
 src/libopensc/padding.c     | 102 +++++++++++++++++++---------
 src/libopensc/pkcs15-sec.c  |   5 +-
 src/minidriver/minidriver.c |   4 +-
 6 files changed, 210 insertions(+), 39 deletions(-)
 create mode 100644 src/common/constant-time.h

Index: opensc-0.13.0/src/common/constant-time.h
===================================================================
--- /dev/null
+++ opensc-0.13.0/src/common/constant-time.h
@@ -0,0 +1,128 @@
+/* Original source: https://github.com/openssl/openssl/blob/9890cc42daff5e2d0cad01ac4bf78c391f599a6e/include/internal/constant_time.h */
+
+#ifndef CONSTANT_TIME_H
+#define CONSTANT_TIME_H
+
+#include <stdlib.h>
+#include <string.h>
+
+#if !defined(inline)
+#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+#define constant_inline inline
+#elif defined(__GNUC__) && __GNUC__ >= 2
+#elif defined(__GNUC__) && __GNUC__ >= 2
+#elif defined(_MSC_VER)
+#define constant_inline __inline
+#else
+#define constant_inline
+#endif
+#else			       /* use what caller wants as inline  may be from config.h */
+#define constant_inline inline /* inline */
+#endif
+
+/*-
+ * The boolean methods return a bitmask of all ones (0xff...f) for true
+ * and 0 for false. For example,
+ *      if (a < b) {
+ *        c = a;
+ *      } else {
+ *        c = b;
+ *      }
+ * can be written as
+ *      unsigned int lt = constant_time_lt(a, b);
+ *      c = constant_time_select(lt, a, b);
+ */
+
+static constant_inline unsigned int
+value_barrier(unsigned int a)
+{
+	volatile unsigned int r = a;
+	return r;
+}
+
+static constant_inline size_t
+value_barrier_s(size_t a)
+{
+	volatile size_t r = a;
+	return r;
+}
+
+/* MSB */
+static constant_inline size_t
+constant_time_msb_s(size_t a)
+{
+	return 0 - (a >> (sizeof(a) * 8 - 1));
+}
+
+static constant_inline unsigned int
+constant_time_msb(unsigned int a)
+{
+	return 0 - (a >> (sizeof(a) * 8 - 1));
+}
+
+/* Select */
+static constant_inline unsigned int
+constant_time_select(unsigned int mask, unsigned int a, unsigned int b)
+{
+	return (value_barrier(mask) & a) | (value_barrier(~mask) & b);
+}
+
+static constant_inline unsigned char
+constant_time_select_8(unsigned char mask, unsigned char a, unsigned char b)
+{
+	return (unsigned char)constant_time_select(mask, a, b);
+}
+
+static constant_inline size_t
+constant_time_select_s(size_t mask, size_t a, size_t b)
+{
+	return (value_barrier_s(mask) & a) | (value_barrier_s(~mask) & b);
+}
+
+/* Zero */
+static constant_inline unsigned int
+constant_time_is_zero(unsigned int a)
+{
+	return constant_time_msb(~a & (a - 1));
+}
+
+static constant_inline size_t
+constant_time_is_zero_s(size_t a)
+{
+	return constant_time_msb_s(~a & (a - 1));
+}
+
+/* Comparison*/
+static constant_inline size_t
+constant_time_lt_s(size_t a, size_t b)
+{
+	return constant_time_msb_s(a ^ ((a ^ b) | ((a - b) ^ b)));
+}
+
+static constant_inline unsigned int
+constant_time_lt(unsigned int a, unsigned int b)
+{
+	return constant_time_msb(a ^ ((a ^ b) | ((a - b) ^ b)));
+}
+
+static constant_inline unsigned int
+constant_time_ge(unsigned int a, unsigned int b)
+{
+	return ~constant_time_lt(a, b);
+}
+
+/* Equality*/
+
+static constant_inline unsigned int
+constant_time_eq(unsigned int a, unsigned int b)
+{
+	return constant_time_is_zero(a ^ b);
+}
+
+static constant_inline size_t
+constant_time_eq_s(size_t a, size_t b)
+{
+	return constant_time_is_zero_s(a ^ b);
+}
+
+#endif /* CONSTANT_TIME_H */
Index: opensc-0.13.0/src/libopensc/padding.c
===================================================================
--- opensc-0.13.0.orig/src/libopensc/padding.c
+++ opensc-0.13.0/src/libopensc/padding.c
@@ -24,10 +24,13 @@
 #include <string.h>
 #include <stdlib.h>
 
+#include "common/constant-time.h"
 #include "internal.h"
 
 /* TODO doxygen comments */
 
+#define SC_PKCS1_PADDING_MIN_SIZE 11
+
 /*
  * Prefixes for pkcs-v1 signatures
  */
Index: opensc-0.13.0/src/tests/unittests/strip_pkcs1_2_padding.c
===================================================================
--- /dev/null
+++ opensc-0.13.0/src/tests/unittests/strip_pkcs1_2_padding.c
@@ -0,0 +1,204 @@
+#include "common/compat_strlcpy.c"
+#include "libopensc/log.c"
+#include "libopensc/padding.c"
+#include "torture.h"
+#include <cmocka.h>
+
+static void
+torture_long_output_buffer(void **state)
+{
+	unsigned int n = 14;
+	unsigned int in_len = 14;
+	unsigned char in[] = {0x00, 0x02,
+			0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+			0x00,
+			'm', 's', 'g'};
+	unsigned int out_len = 3;
+	unsigned char *out = malloc(out_len * sizeof(unsigned char));
+	unsigned char result_msg[] = {'m', 's', 'g'};
+	int r = sc_pkcs1_strip_02_padding_constant_time(NULL, n, in, in_len, out, &out_len);
+	assert_int_equal(r, 3);
+	assert_memory_equal(out, result_msg, r);
+	free(out);
+}
+
+static void
+torture_short_output_buffer(void **state)
+{
+	unsigned int n = 14;
+	unsigned int in_len = 14;
+	unsigned char in[] = {0x00, 0x02,
+			0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+			0x00,
+			'm', 's', 'g'};
+	unsigned int out_len = 1;
+	unsigned char *out = malloc(out_len * sizeof(unsigned char));
+	int r = sc_pkcs1_strip_02_padding_constant_time(NULL, n, in, in_len, out, &out_len);
+	assert_int_equal(r, SC_ERROR_WRONG_PADDING);
+	free(out);
+}
+
+static void
+torture_short_message_correct_padding(void **state)
+{
+	unsigned int n = 14;
+	unsigned int in_len = 14;
+	unsigned char in[] = {0x00, 0x02,
+			0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+			0x00,
+			'm', 's', 'g'};
+	unsigned int out_len = 3;
+	unsigned char *out = malloc(out_len * sizeof(unsigned char));
+	unsigned char result_msg[] = {'m', 's', 'g'};
+	int r = sc_pkcs1_strip_02_padding_constant_time(NULL, n, in, in_len, out, &out_len);
+	assert_int_equal(r, 3);
+	assert_memory_equal(out, result_msg, r);
+	free(out);
+}
+
+static void
+torture_missing_first_zero(void **state)
+{
+	unsigned int n = 13;
+	unsigned int in_len = 13;
+	unsigned char in[] = {0x02,
+			0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+			0x00,
+			'm', 's', 'g'};
+	unsigned int out_len = 10;
+	unsigned char *out = malloc(out_len * sizeof(unsigned char));
+	int r = sc_pkcs1_strip_02_padding_constant_time(NULL, n, in, in_len, out, &out_len);
+	assert_int_equal(r, SC_ERROR_WRONG_PADDING);
+	free(out);
+}
+
+static void
+torture_missing_two(void **state)
+{
+	unsigned int n = 13;
+	unsigned int in_len = 13;
+	unsigned char in[] = {0x00,
+			0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+			0x00,
+			'm', 's', 'g'};
+	unsigned int out_len = 10;
+	unsigned char *out = malloc(out_len * sizeof(unsigned char));
+	int r = sc_pkcs1_strip_02_padding_constant_time(NULL, n, in, in_len, out, &out_len);
+	assert_int_equal(r, SC_ERROR_WRONG_PADDING);
+	free(out);
+}
+
+static void
+torture_short_padding(void **state)
+{
+	unsigned int n = 13;
+	unsigned int in_len = 13;
+	unsigned char in[] = {0x00, 0x02,
+			0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+			0x00,
+			'm', 's', 'g'};
+	unsigned int out_len = 10;
+	unsigned char *out = malloc(out_len * sizeof(unsigned char));
+	int r = sc_pkcs1_strip_02_padding_constant_time(NULL, n, in, in_len, out, &out_len);
+	assert_int_equal(r, SC_ERROR_WRONG_PADDING);
+	free(out);
+}
+
+static void
+torture_missing_second_zero(void **state)
+{
+	unsigned int n = 13;
+	unsigned int in_len = 13;
+	unsigned char in[] = {0x00, 0x02,
+			0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+			'm', 's', 'g'};
+	unsigned int out_len = 10;
+	unsigned char *out = malloc(out_len * sizeof(unsigned char));
+	int r = sc_pkcs1_strip_02_padding_constant_time(NULL, n, in, in_len, out, &out_len);
+	assert_int_equal(r, SC_ERROR_WRONG_PADDING);
+	free(out);
+}
+
+static void
+torture_missing_message(void **state)
+{
+	unsigned int n = 20;
+	unsigned int in_len = 11;
+	unsigned char in[] = {0x00, 0x02,
+			0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+			0x00};
+	unsigned int out_len = 11;
+	unsigned char *out = malloc(out_len * sizeof(unsigned char));
+	int r = sc_pkcs1_strip_02_padding_constant_time(NULL, n, in, in_len, out, &out_len);
+	assert_int_equal(r, SC_ERROR_WRONG_PADDING);
+	free(out);
+}
+
+static void
+torture_one_byte_message(void **state)
+{
+	unsigned int n = 12;
+	unsigned int in_len = 12;
+	unsigned char in[] = {0x00, 0x02,
+			0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+			0x00,
+			'm'};
+	unsigned int out_len = 1;
+	unsigned char *out = malloc(out_len * sizeof(unsigned char));
+	unsigned char result_msg[] = {'m'};
+	int r = sc_pkcs1_strip_02_padding_constant_time(NULL, n, in, in_len, out, &out_len);
+	assert_int_equal(r, 1);
+	assert_memory_equal(out, result_msg, r);
+	free(out);
+}
+
+static void
+torture_longer_padding(void **state)
+{
+	unsigned int n = 26;
+	unsigned int in_len = 26;
+	unsigned char in[] = {0x00, 0x02,
+			0x0e, 0x38, 0x97, 0x18, 0x16, 0x57, 0x9e, 0x30, 0xb6, 0xa5, 0x78, 0x13, 0x20, 0xca, 0x11,
+			0x00,
+			0x9d, 0x98, 0x3d, 0xca, 0xa9, 0xa7, 0x11, 0x0a};
+	unsigned int out_len = 8;
+	unsigned char *out = malloc(out_len * sizeof(unsigned char));
+	unsigned char result_msg[] = {0x9d, 0x98, 0x3d, 0xca, 0xa9, 0xa7, 0x11, 0x0a};
+	int r = sc_pkcs1_strip_02_padding_constant_time(NULL, n, in, in_len, out, &out_len);
+	assert_int_equal(r, 8);
+	assert_memory_equal(out, result_msg, r);
+	free(out);
+}
+
+static void
+torture_empty_message(void **state)
+{
+	unsigned int n = 18;
+	unsigned int in_len = 18;
+	unsigned char in[] = {0x00, 0x02,
+			0x0e, 0x38, 0x97, 0x18, 0x16, 0x57, 0x9e, 0x30, 0xb6, 0xa5, 0x78, 0x13, 0x20, 0xca, 0x11,
+			0x00};
+	unsigned int out_len = 8;
+	unsigned char *out = malloc(out_len * sizeof(unsigned char));
+	int r = sc_pkcs1_strip_02_padding_constant_time(NULL, n, in, in_len, out, &out_len);
+	assert_int_equal(r, 0);
+	free(out);
+}
+
+int
+main(void)
+{
+	const struct CMUnitTest tests[] = {
+			cmocka_unit_test(torture_long_output_buffer),
+			cmocka_unit_test(torture_short_output_buffer),
+			cmocka_unit_test(torture_short_message_correct_padding),
+			cmocka_unit_test(torture_missing_first_zero),
+			cmocka_unit_test(torture_missing_two),
+			cmocka_unit_test(torture_short_padding),
+			cmocka_unit_test(torture_missing_second_zero),
+			cmocka_unit_test(torture_missing_message),
+			cmocka_unit_test(torture_one_byte_message),
+			cmocka_unit_test(torture_longer_padding),
+			cmocka_unit_test(torture_empty_message)};
+	return cmocka_run_group_tests(tests, NULL, NULL);
+}
Index: opensc-0.13.0/src/pkcs11/framework-pkcs15.c
===================================================================
--- opensc-0.13.0.orig/src/pkcs11/framework-pkcs15.c
+++ opensc-0.13.0/src/pkcs11/framework-pkcs15.c
@@ -18,6 +18,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+#include "common/constant-time.h"
 #include "config.h"
 #include "libopensc/log.h"
 #include "libopensc/asn1.h"
@@ -3449,6 +3450,7 @@ pkcs15_prkey_decrypt(struct sc_pkcs11_se
 	struct pkcs15_prkey_object *prkey;
 	unsigned char decrypted[256]; /* FIXME: Will not work for keys above 2048 bits */
 	int	buff_too_small, rv, flags = 0, prkey_has_path = 0;
+	CK_ULONG mask, good, rv_pkcs11;
 
 	sc_log(context, "Initiating decryption.");
 
@@ -3485,27 +3487,53 @@ pkcs15_prkey_decrypt(struct sc_pkcs11_se
 	rv = sc_pkcs15_decipher(fw_data->p15_card, prkey->prv_p15obj, flags,
 			pEncryptedData, ulEncryptedDataLen, decrypted, sizeof(decrypted));
 
-	if (rv < 0 && !sc_pkcs11_conf.lock_login && !prkey_has_path)
+	/* skip for PKCS#1 v1.5 padding prevent side channel attack */
+	if (!(flags & SC_ALGORITHM_RSA_PAD_PKCS1) &&
+			rv < 0 && !sc_pkcs11_conf.lock_login && !prkey_has_path)
 		if (reselect_app_df(fw_data->p15_card) == SC_SUCCESS)
 			rv = sc_pkcs15_decipher(fw_data->p15_card, prkey->prv_p15obj, flags,
 					pEncryptedData, ulEncryptedDataLen, decrypted, sizeof(decrypted));
 
 	sc_unlock(p11card->card);
 
-	sc_log(context, "Decryption complete. Result %d.", rv);
+	sc_log(context, "Decryption complete.");
 
-	if (rv < 0)
-		return sc_to_cryptoki_error(rv, "C_Decrypt");
+	/* Handle following code in constant-time
+	 * to prevent Marvin attack for PKCS#1 v1.5 padding. */
 
-	buff_too_small = (*pulDataLen < (CK_ULONG)rv);
-	*pulDataLen = rv;
-	if (pData == NULL_PTR)
-		return CKR_OK;
-	if (buff_too_small)
-		return CKR_BUFFER_TOO_SMALL;
-	memcpy(pData, decrypted, *pulDataLen);
+	/* only padding error must be handled in constant-time way,
+	 * other error can be returned straight away */
+	if ((~constant_time_eq_s(rv, SC_ERROR_WRONG_PADDING) & constant_time_lt_s(sizeof(decrypted), rv)))
+		return sc_to_cryptoki_error(rv, "C_Decrypt");
 
-	return CKR_OK;
+	/* check rv for padding error */
+	good = ~constant_time_eq_s(rv, SC_ERROR_WRONG_PADDING);
+	rv_pkcs11 = sc_to_cryptoki_error(SC_ERROR_WRONG_PADDING, "C_Decrypt");
+	rv_pkcs11 = constant_time_select_s(good, CKR_OK, rv_pkcs11);
+
+	if (pData == NULL_PTR) {
+		/* set length only if no error */
+		*pulDataLen = constant_time_select_s(good, rv, *pulDataLen);
+		/* return error only if original rv < 0 */
+		return rv_pkcs11;
+	}
+
+	/* check whether *pulDataLen < rv and set return value for small output buffer */
+	mask = good & constant_time_lt_s(*pulDataLen, rv);
+	rv_pkcs11 = constant_time_select_s(mask, CKR_BUFFER_TOO_SMALL, rv_pkcs11);
+	good &= ~mask;
+
+	/* move everything from decrypted into out buffer constant-time, if rv is ok */
+	for (CK_ULONG i = 0; i < *pulDataLen; i++) { /* iterate over whole pData to not disclose real depadded length */
+		CK_ULONG msg_index;
+		mask = good & constant_time_lt_s(i, sizeof(decrypted));		    /* i should be in the bounds of decrypted */
+		mask &= constant_time_lt_s(i, constant_time_select_s(good, rv, 0)); /* check that is in bounds of depadded message */
+		msg_index = constant_time_select_s(mask, i, 0);
+		pData[i] = constant_time_select_8(mask, decrypted[msg_index], pData[i]);
+	}
+	*pulDataLen = constant_time_select_s(good, rv, *pulDataLen);
+	/* do not log error code to prevent side channel attack */
+	return rv_pkcs11;
 }
 
 
Index: opensc-0.13.0/src/pkcs11/mechanism.c
===================================================================
--- opensc-0.13.0.orig/src/pkcs11/mechanism.c
+++ opensc-0.13.0/src/pkcs11/mechanism.c
@@ -9,6 +9,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "common/constant-time.h"
 #include "sc-pkcs11.h"
 
 /* Also used for verification data */
@@ -754,9 +755,12 @@ sc_pkcs11_decr(struct sc_pkcs11_session
 	rv = op->type->decrypt(op, pEncryptedData, ulEncryptedDataLen,
 	                       pData, pulDataLen);
 
-	if (rv != CKR_BUFFER_TOO_SMALL && pData != NULL)
+	/* terminate session for any return value except CKR_BUFFER_TOO_SMALL,
+	 * perform check in time side-channel free way to prevent Marvin attack */
+	if (~constant_time_eq_s(rv, CKR_OK) & ~constant_time_eq_s(rv, CKR_BUFFER_TOO_SMALL))
 		session_stop_operation(session, SC_PKCS11_OPERATION_DECRYPT);
 
+	/* do not log error code to prevent side channel attack */
 	return rv;
 }
 
Index: opensc-0.13.0/src/pkcs11/pkcs11-object.c
===================================================================
--- opensc-0.13.0.orig/src/pkcs11/pkcs11-object.c
+++ opensc-0.13.0/src/pkcs11/pkcs11-object.c
@@ -923,7 +923,8 @@ CK_RV C_Decrypt(CK_SESSION_HANDLE hSessi
 		rv = sc_pkcs11_decr(session, pEncryptedData, ulEncryptedDataLen,
 				pData, pulDataLen);
 
-	sc_log(context, "C_Decrypt() = %s", lookup_enum ( RV_T, rv ));
+	/* do not log error code to prevent side channel attack */
+	SC_LOG("C_Decrypt()");
 	sc_pkcs11_unlock();
 	return rv;
 }
@@ -1281,7 +1282,8 @@ CK_RV C_VerifyUpdate(CK_SESSION_HANDLE h
 	if (rv == CKR_OK)
 		rv = sc_pkcs11_verif_update(session, pPart, ulPartLen);
 
-	sc_log(context, "C_VerifyUpdate() = %s", lookup_enum ( RV_T, rv ));
+	/* do not log error code to prevent side channel attack */
+	SC_LOG("C_DecryptUpdate()");
 	sc_pkcs11_unlock();
 	return rv;
 #endif
@@ -1305,7 +1307,8 @@ CK_RV C_VerifyFinal(CK_SESSION_HANDLE hS
 	if (rv == CKR_OK)
 		rv = sc_pkcs11_verif_final(session, pSignature, ulSignatureLen);
 
-	sc_log(context, "C_VerifyFinal() = %s", lookup_enum ( RV_T, rv ));
+	/* do not log error code to prevent side channel attack */
+	SC_LOG("C_DecryptFinal()");
 	sc_pkcs11_unlock();
 	return rv;
 #endif
Index: opensc-0.13.0/src/pkcs11/misc.c
===================================================================
--- opensc-0.13.0.orig/src/pkcs11/misc.c
+++ opensc-0.13.0/src/pkcs11/misc.c
@@ -23,6 +23,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "common/constant-time.h"
 #include "sc-pkcs11.h"
 
 #define DUMP_TEMPLATE_MAX	32
