Skip to content

Commit

Permalink
Reimplement removing of PKCS#1 v1.5 padding to be time constant
Browse files Browse the repository at this point in the history
  • Loading branch information
xhanulik authored and Jakuje committed Feb 5, 2024
1 parent f39c9d2 commit e8883b1
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 39 deletions.
6 changes: 4 additions & 2 deletions src/common/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ dist_noinst_DATA = \
LICENSE.compat_getopt compat_getopt.txt \
compat_getopt_main.c \
README.compat_strlcpy compat_strlcpy.3
noinst_HEADERS = compat_strlcat.h compat_strlcpy.h compat_strnlen.h compat_getpass.h compat_getopt.h simclist.h libpkcs11.h libscdl.h compat_overflow.h
noinst_HEADERS = compat_strlcat.h compat_strlcpy.h compat_strnlen.h compat_getpass.h \
compat_getopt.h simclist.h libpkcs11.h libscdl.h compat_overflow.h constant-time.h

AM_CPPFLAGS = -I$(top_srcdir)/src

Expand Down Expand Up @@ -43,7 +44,8 @@ TIDY_FILES = \
compat___iob_func.c \
compat_overflow.h compat_overflow.c \
simclist.c simclist.h \
libpkcs11.c libscdl.c
libpkcs11.c libscdl.c \
constant-time.h

check-local:
if [ -x "$(CLANGTIDY)" ]; then clang-tidy -config='' --checks='$(TIDY_CHECKS)' --warnings-as-errors='$(TIDY_CHECKS)' -header-filter=.* $(addprefix $(srcdir)/,$(TIDY_FILES)) -- $(TIDY_FLAGS); fi
128 changes: 128 additions & 0 deletions src/common/constant-time.h
Original file line number Diff line number Diff line change
@@ -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 */
4 changes: 2 additions & 2 deletions src/libopensc/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ int _sc_card_add_xeddsa_alg(struct sc_card *card, size_t key_length,

int sc_pkcs1_strip_01_padding(struct sc_context *ctx, const u8 *in_dat, size_t in_len,
u8 *out_dat, size_t *out_len);
int sc_pkcs1_strip_02_padding(struct sc_context *ctx, const u8 *data, size_t len,
u8 *out_dat, size_t *out_len);
int sc_pkcs1_strip_02_padding_constant_time(sc_context_t *ctx, unsigned int n, const u8 *data,
unsigned int data_len, u8 *out, unsigned int *out_len);
int sc_pkcs1_strip_digest_info_prefix(unsigned int *algorithm,
const u8 *in_dat, size_t in_len, u8 *out_dat, size_t *out_len);
#ifdef ENABLE_OPENSSL
Expand Down
102 changes: 71 additions & 31 deletions src/libopensc/padding.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,13 @@
#include <string.h>
#include <stdlib.h>

#include "common/constant-time.h"
#include "internal.h"
#include "pkcs11/pkcs11.h"
/* TODO doxygen comments */

#define SC_PKCS1_PADDING_MIN_SIZE 11

/*
* Prefixes for pkcs-v1 signatures
*/
Expand Down Expand Up @@ -143,45 +146,82 @@ sc_pkcs1_strip_01_padding(struct sc_context *ctx, const u8 *in_dat, size_t in_le
return SC_SUCCESS;
}


/* remove pkcs1 BT02 padding (adding BT02 padding is currently not
* needed/implemented) */
/* Remove pkcs1 BT02 padding (adding BT02 padding is currently not
* needed/implemented) in constant-time.
* Original source: https://github.com/openssl/openssl/blob/9890cc42daff5e2d0cad01ac4bf78c391f599a6e/crypto/rsa/rsa_pk1.c#L171 */
int
sc_pkcs1_strip_02_padding(sc_context_t *ctx, const u8 *data, size_t len, u8 *out, size_t *out_len)
sc_pkcs1_strip_02_padding_constant_time(sc_context_t *ctx, unsigned int n, const u8 *data, unsigned int data_len, u8 *out, unsigned int *out_len)
{
unsigned int n = 0;

unsigned int i = 0;
u8 *msg, *msg_orig = NULL;
unsigned int good, found_zero_byte, mask;
unsigned int zero_index = 0, msg_index, mlen = -1, len = 0;
LOG_FUNC_CALLED(ctx);
if (data == NULL || len < 3)

if (data == NULL || data_len <= 0 || data_len > n || n < SC_PKCS1_PADDING_MIN_SIZE)
LOG_FUNC_RETURN(ctx, SC_ERROR_INTERNAL);

/* skip leading zero byte */
if (*data == 0) {
data++;
len--;
msg = msg_orig = calloc(n, sizeof(u8));
if (msg == NULL)
LOG_FUNC_RETURN(ctx, SC_ERROR_INTERNAL);

/*
* We can not check length of input data straight away and still we need to read
* from input even when the input is not as long as needed to keep the time constant.
* If data has wrong size, it is padded by zeroes from left and the following checks
* do not pass.
*/
len = data_len;
for (data += len, msg += n, i = 0; i < n; i++) {
mask = ~constant_time_is_zero(len);
len -= 1 & mask;
data -= 1 & mask;
*--msg = *data & mask;
}
// check first byte to be 0x00
good = constant_time_is_zero(msg[0]);
// check second byte to be 0x02
good &= constant_time_eq(msg[1], 2);

// find zero byte after random data in padding
found_zero_byte = 0;
for (i = 2; i < n; i++) {
unsigned int equals0 = constant_time_is_zero(msg[i]);
zero_index = constant_time_select(~found_zero_byte & equals0, i, zero_index);
found_zero_byte |= equals0;
}
if (data[0] != 0x02)
LOG_FUNC_RETURN(ctx, SC_ERROR_WRONG_PADDING);
/* skip over padding bytes */
for (n = 1; n < len && data[n]; n++)
;
/* Must be at least 8 pad bytes */
if (n >= len || n < 9)
LOG_FUNC_RETURN(ctx, SC_ERROR_WRONG_PADDING);
n++;
if (out == NULL)
/* just check the padding */
LOG_FUNC_RETURN(ctx, SC_SUCCESS);

/* Now move decrypted contents to head of buffer */
if (*out_len < len - n)
LOG_FUNC_RETURN(ctx, SC_ERROR_INTERNAL);
*out_len = len - n;
memmove(out, data + n, *out_len);
// zero_index stands for index of last found zero
good &= constant_time_ge(zero_index, 2 + 8);

// start of the actual message in data
msg_index = zero_index + 1;

// length of message
mlen = data_len - msg_index;

// check that message fits into out buffer
good &= constant_time_ge(*out_len, mlen);

// move the result in-place by |num|-SC_PKCS1_PADDING_MIN_SIZE-|mlen| bytes to the left.
*out_len = constant_time_select(constant_time_lt(n - SC_PKCS1_PADDING_MIN_SIZE, *out_len),
n - SC_PKCS1_PADDING_MIN_SIZE, *out_len);
for (msg_index = 1; msg_index < n - SC_PKCS1_PADDING_MIN_SIZE; msg_index <<= 1) {
mask = ~constant_time_eq(msg_index & (n - SC_PKCS1_PADDING_MIN_SIZE - mlen), 0);
for (i = SC_PKCS1_PADDING_MIN_SIZE; i < n - msg_index; i++)
msg[i] = constant_time_select_8(mask, msg[i + msg_index], msg[i]);
}
// move message into out buffer, if good
for (i = 0; i < *out_len; i++) {
unsigned int msg_index;
// when out is longer than message in data, use some bogus index in msg
mask = good & constant_time_lt(i, mlen);
msg_index = constant_time_select(mask, i + SC_PKCS1_PADDING_MIN_SIZE, 0); // to now overflow msg buffer
out[i] = constant_time_select_8(mask, msg[msg_index], out[i]);
}

sc_log(ctx, "stripped output(%"SC_FORMAT_LEN_SIZE_T"u): %s", len - n,
sc_dump_hex(out, len - n));
LOG_FUNC_RETURN(ctx, (int)(len - n));
free(msg_orig);
return constant_time_select(good, mlen, SC_ERROR_WRONG_PADDING);
}

#ifdef ENABLE_OPENSSL
Expand Down
5 changes: 3 additions & 2 deletions src/libopensc/pkcs15-sec.c
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,9 @@ int sc_pkcs15_decipher(struct sc_pkcs15_card *p15card,

/* Strip any padding */
if (pad_flags & SC_ALGORITHM_RSA_PAD_PKCS1) {
size_t s = r;
r = sc_pkcs1_strip_02_padding(ctx, out, s, out, &s);
int s = r;
int key_size = alg_info->key_length;
r = sc_pkcs1_strip_02_padding_constant_time(ctx, key_size / 8, out, s, out, &s);
LOG_TEST_RET(ctx, r, "Invalid PKCS#1 padding");
}
#ifdef ENABLE_OPENSSL
Expand Down
4 changes: 2 additions & 2 deletions src/minidriver/minidriver.c
Original file line number Diff line number Diff line change
Expand Up @@ -4653,9 +4653,9 @@ DWORD WINAPI CardRSADecrypt(__in PCARD_DATA pCardData,
"sc_pkcs15_decipher: DECRYPT-INFO dwVersion=%lu\n",
(unsigned long)pInfo->dwVersion);
if (pInfo->dwPaddingType == CARD_PADDING_PKCS1) {
size_t temp = pInfo->cbData;
unsigned int temp = pInfo->cbData;
logprintf(pCardData, 2, "sc_pkcs15_decipher: stripping PKCS1 padding\n");
r = sc_pkcs1_strip_02_padding(vs->ctx, pbuf2, pInfo->cbData, pbuf2, &temp);
r = sc_pkcs1_strip_02_padding_constant_time(vs->ctx, prkey_info->modulus_length / 8, pbuf2, pInfo->cbData, pbuf2, &temp);
pInfo->cbData = (DWORD) temp;
if (r < 0) {
logprintf(pCardData, 2, "Cannot strip PKCS1 padding: %i\n", r);
Expand Down

0 comments on commit e8883b1

Please sign in to comment.