Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Constant time RSA PKCS#1 v1.5 depadding #2948

Merged
merged 8 commits into from
Feb 5, 2024
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
xhanulik marked this conversation as resolved.
Show resolved Hide resolved

/*-
* 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
10 changes: 6 additions & 4 deletions src/libopensc/pkcs15-sec.c
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,10 @@ 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);
LOG_TEST_RET(ctx, r, "Invalid PKCS#1 padding");
unsigned int s = r;
unsigned int key_size = (unsigned int)alg_info->key_length;
r = sc_pkcs1_strip_02_padding_constant_time(ctx, key_size / 8, out, s, out, &s);
/* for keeping PKCS#1 v1.5 depadding constant-time, do not log error here */
}
#ifdef ENABLE_OPENSSL
if (pad_flags & SC_ALGORITHM_RSA_PAD_OAEP)
Expand All @@ -332,7 +333,8 @@ int sc_pkcs15_decipher(struct sc_pkcs15_card *p15card,
LOG_TEST_RET(ctx, r, "Invalid OAEP padding");
}
#endif
LOG_FUNC_RETURN(ctx, r);
xhanulik marked this conversation as resolved.
Show resolved Hide resolved
/* do not log error code to prevent side channel attack */
return r;
}

/* derive one key from another. RSA can use decipher, so this is for only ECDH
Expand Down
Loading
Loading