diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 83c51785..1976bdaa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,6 +14,7 @@ jobs: strategy: fail-fast: false matrix: + runs-on: [ubuntu-24.04, ubuntu-24.04-arm] name: [fedora, debian, centos9, centos10, ubuntu, almalinux8] compiler: [gcc, clang] token: [softokn, softhsm, kryoptic] @@ -47,6 +48,17 @@ jobs: token: kryoptic - name: almalinux8 token: kryoptic + # Test on ARM only some use cases + - runs-on: ubuntu-24.04-arm + name: debian + - runs-on: ubuntu-24.04-arm + name: centos9 + - runs-on: ubuntu-24.04-arm + name: centos10 + - runs-on: ubuntu-24.04-arm + name: ubuntu + - runs-on: ubuntu-24.04-arm + name: almalinux8 container: ${{ matrix.container }} steps: - name: Install Dependencies @@ -103,8 +115,14 @@ jobs: uses: actions/checkout@v4 - name: Setup + id: setup if : ( steps.skip-check.outputs.skiptest != 'true' ) run: | + if [ "${{ matrix.runs-on }}" = "ubuntu-24.04-arm" ]; then + echo "cpu=ARM64" >> $GITHUB_OUTPUT + else + echo "cpu=X86_64" >> $GITHUB_OUTPUT + fi git config --global --add safe.directory \ /__w/pkcs11-provider/pkcs11-provider git submodule update --init @@ -254,4 +272,3 @@ jobs: builddir/tests/${{ matrix.token }}/p11prov-debug.log builddir/tests/${{ matrix.token }}/testvars builddir/tests/${{ matrix.token }}/openssl.cnf - diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml index 4747cd6d..292784e0 100644 --- a/.github/workflows/openssl.yml +++ b/.github/workflows/openssl.yml @@ -32,7 +32,7 @@ jobs: - name: Install Dependencies run: | dnf -y install perl-FindBin perl-IPC-Cmd perl-File-Compare \ - perl-File-Copy perl-Pod-Html git clang + perl-File-Copy perl-Pod-Html perl-Time-Piece git clang - name: Checkout Repository uses: actions/checkout@v4 diff --git a/src/cipher.c b/src/cipher.c index 72f9c432..6fb0d3d4 100644 --- a/src/cipher.c +++ b/src/cipher.c @@ -6,8 +6,12 @@ #if SKEY_SUPPORT == 1 #include "cipher.h" -#include #include "openssl/prov_ssl.h" +#include "openssl/rand.h" +#include + +#define MAX_PADDING 256; +#define AESBLOCK 16 /* 128 bits for all AES modes */ DISPATCH_CIPHER_FN(cipher, freectx); DISPATCH_CIPHER_FN(aes, dupctx); @@ -35,6 +39,14 @@ struct p11prov_cipher_ctx { CK_FLAGS operation; P11PROV_SESSION *session; + + /* OpenSSL violates layering separation and decided + * to process AES CBC MAC/padding handling in TLS 1.x < 1.3 + * in the lower cipher layer, so we have to do it here as well + * for compatibility ... */ + unsigned int tlsver; + size_t tlsmacsize; + unsigned char *tlsmac; }; static void *p11prov_cipher_newctx(void *provctx, int size, CK_ULONG mechanism) @@ -157,7 +169,7 @@ static int p11prov_aes_get_params(OSSL_PARAM params[], int size, int mode, int ciph_mode = 0; int flags = mode & MODE_flags_mask; size_t keysize = size / 8; - size_t blocksize = 16; /* 128 bits for all AES modes */ + size_t blocksize = AESBLOCK; size_t ivsize = 16; /* 128 bits for all modes but ECB */ switch (mode & MODE_modes_mask) { @@ -200,6 +212,7 @@ static void p11prov_cipher_freectx(void *ctx) p11prov_obj_free(cctx->key); p11prov_return_session(cctx->session); OPENSSL_clear_free(cctx->mech.pParameter, cctx->mech.ulParameterLen); + OPENSSL_clear_free(cctx->tlsmac, cctx->tlsmacsize); OPENSSL_clear_free(cctx, sizeof(struct p11prov_cipher_ctx)); } @@ -330,6 +343,12 @@ static CK_RV p11prov_cipher_session_init(struct p11prov_cipher_ctx *cctx) return CKR_SLOT_ID_INVALID; } + if (cctx->tlsver != 0 && cctx->mech.mechanism == CKM_AES_CBC_PAD) { + /* In the special TLS mode we handle de-padding and mac extraction + * outside the pkcs11 module to conform to what OpenSSL does */ + cctx->mech.mechanism = CKM_AES_CBC; + } + rv = p11prov_get_session(cctx->provctx, &slotid, NULL, NULL, cctx->mech.mechanism, NULL, NULL, true, false, &cctx->session); @@ -447,32 +466,209 @@ static int p11prov_cipher_decrypt_skey_init(void *ctx, void *keydata, return RET_OSSL_OK; } +/* This function needs to be executed in constant time */ +static CK_RV tlsunpad(struct p11prov_cipher_ctx *cctx, unsigned char *out, + CK_ULONG inlen, CK_ULONG *outlen) +{ + CK_RV rv = CKR_GENERAL_ERROR; + CK_ULONG overhead = cctx->tlsmacsize + 1; /* mac size + padlen byte */ + CK_ULONG maxcheck = MAX_PADDING; + CK_ULONG padsize = out[inlen - 1]; + CK_ULONG olen = inlen; + CK_ULONG pass; + + /* Remove explicit IV for TLS 1.1 and 1.2 */ + if (cctx->tlsver != 0x301) { + /* This is a bad interface as it make it seem that + * the returned output buffer is incorrectly pointing + * at the IV and not the data, but OpenSSL will in turn + * offset the buffer later, based on knowledge that this + * cipher return a length that excludes the IV from the + * count. */ + out += AESBLOCK; + olen = inlen - AESBLOCK; + } + + /* olen is public known so can be checked normally */ + if (olen < overhead) { + return CKR_BUFFER_TOO_SMALL; + } + + if (olen < cctx->tlsmacsize) { + return CKR_BUFFER_TOO_SMALL; + } + + if (maxcheck > olen) { + maxcheck = olen; + } + + /* olen must not be smaller than padsize + overhead */ + pass = ~constant_smaller_mask(olen, overhead + padsize); + + /* creates a mask so that we check only the padding bytes + * without revealing the padding length in a conditional. + * mask is 0xff when i < padsize, and 0 otherwise, allowing + * us to scan the whole buffer while really only testing for + * equality only the padding part, as the xoring with non-pad + * data is ignored my the empty mask. We skip checking the + * last value itself as that is always == padsize */ + for (int i = 0; i < maxcheck - 1; i++) { + unsigned char mask = constant_smaller_mask(i, padsize); + unsigned char data = out[olen - i - 2]; + + pass &= ~(mask & (padsize ^ data)); + } + + /* renormalize to a CK_ULONG */ + pass = constant_equal_mask(pass, 0xff); + + if (cctx->tlsmacsize > 0) { + unsigned char randmac[EVP_MAX_MD_SIZE]; + size_t mac_pos = olen - cctx->tlsmacsize - (pass & (padsize + 1)); + size_t mac_area = 0; + int err = RET_OSSL_ERR; + + /* allocate space for the mac */ + cctx->tlsmac = OPENSSL_zalloc(cctx->tlsmacsize); + if (!cctx->tlsmac) { + return CKR_GENERAL_ERROR; + } + + /* random mac we return if something is wrong */ + err = RAND_bytes_ex(p11prov_ctx_get_libctx(cctx->provctx), randmac, + sizeof(randmac), 0); + if (err != RET_OSSL_OK) { + return CKR_GENERAL_ERROR; + } + + /* olen and mac size are public data, so we can do this + * assignment without bothering with constant time */ + if (olen > cctx->tlsmacsize + 256) { + mac_area = olen - cctx->tlsmacsize - 256; + } + + for (size_t i = mac_area; i < olen; i++) { + for (int j = 0; j < cctx->tlsmacsize; j++) { + unsigned char mask = + ~constant_smaller_mask(i, mac_pos) + & constant_smaller_mask(i, mac_pos + cctx->tlsmacsize) + & constant_equal_mask(i, j + mac_pos); + cctx->tlsmac[j] |= out[i] & mask; + } + } + + /* on depadding failure overwrite with random data */ + for (int j = 0; j < cctx->tlsmacsize; j++) { + cctx->tlsmac[j] = + constant_select_byte_mask(cctx->tlsmac[j], randmac[j], pass); + } + + rv = CKR_OK; + } else { + /* no MAC to check just return the result */ + if (pass + 1 == 0) { + rv = CKR_OK; + } + } + + *outlen = olen - cctx->tlsmacsize - (pass & (padsize + 1)); + return rv; +} + static int p11prov_cipher_update(void *ctx, unsigned char *out, size_t *outl, size_t outsize, const unsigned char *in, size_t inl) { struct p11prov_cipher_ctx *cctx = (struct p11prov_cipher_ctx *)ctx; + CK_SESSION_HANDLE session_handle; CK_ULONG outlen = outsize; CK_ULONG inlen = inl; CK_RV rv; + if (cctx->tlsver != 0) { + /* Special OpenSSL layering violating mode. + * A single update is a full record. + * Inputs need to be consistent with stricter requirements */ + if (!in || in != out || outsize < inl || !cctx->pad) { + ERR_raise(ERR_LIB_PROV, PROV_R_CIPHER_OPERATION_FAILED); + return 0; + } + } + if (!cctx->session) { rv = p11prov_cipher_session_init(cctx); if (rv != CKR_OK) { return RET_OSSL_ERR; } } + session_handle = p11prov_session_handle(cctx->session); switch (cctx->operation) { case CKF_ENCRYPT: - rv = p11prov_EncryptUpdate(cctx->provctx, - p11prov_session_handle(cctx->session), - (void *)in, inlen, out, &outlen); + if (cctx->tlsver != 0) { + size_t padsize = AESBLOCK - (inl % AESBLOCK); + unsigned char padval = (unsigned char)(padsize - 1); + + if (outsize < inl + padsize) { + rv = CKR_BUFFER_TOO_SMALL; + P11PROV_raise(cctx->provctx, rv, "Output buffer too small"); + return RET_OSSL_ERR; + } + inlen += padsize; + if ((inlen % AESBLOCK) != 0) { + rv = CKR_ARGUMENTS_BAD; + P11PROV_raise(cctx->provctx, rv, "Invalid input buffer size"); + return RET_OSSL_ERR; + } + /* add the padding, relies on in == out and therefore enough + * space available in the buffer */ + memset(&out[inl], padval, padsize); + + /* in TLS mode we must use single shot encryption to properly + * auto-finalize the session as OpenSSL won't */ + rv = p11prov_Encrypt(cctx->provctx, session_handle, (void *)in, + inlen, out, &outlen); + + /* unconditionally return the session */ + p11prov_return_session(cctx->session); + cctx->session = NULL; + } else { + rv = p11prov_EncryptUpdate(cctx->provctx, session_handle, + (void *)in, inlen, out, &outlen); + } break; case CKF_DECRYPT: - rv = p11prov_DecryptUpdate(cctx->provctx, - p11prov_session_handle(cctx->session), - (void *)in, inlen, out, &outlen); + if (cctx->tlsver != 0) { + if ((inlen % AESBLOCK) != 0) { + rv = CKR_ARGUMENTS_BAD; + P11PROV_raise(cctx->provctx, rv, "Invalid input buffer size"); + return RET_OSSL_ERR; + } + /* in TLS mode we must use single shot decryption to properly + * auto-finalize the session as OpenSSL won't */ + rv = p11prov_Decrypt(cctx->provctx, session_handle, (void *)in, + inlen, out, &outlen); + + /* unconditionally return the session */ + p11prov_return_session(cctx->session); + cctx->session = NULL; + + if (rv != CKR_OK) { + P11PROV_raise(cctx->provctx, rv, "Decryption failure"); + return RET_OSSL_ERR; + } + /* remove padding and fill in tlsmac as needed */ + if (cctx->tlsmac) { + OPENSSL_clear_free(cctx->tlsmac, cctx->tlsmacsize); + cctx->tlsmac = NULL; + } + + /* Assumes inlen = outlen on correct decryption */ + rv = tlsunpad(cctx, out, inlen, &outlen); + } else { + rv = p11prov_DecryptUpdate(cctx->provctx, session_handle, + (void *)in, inlen, out, &outlen); + } break; default: rv = CKR_GENERAL_ERROR; @@ -604,9 +800,11 @@ static int p11prov_aes_get_ctx_params(void *ctx, OSSL_PARAM params[]) p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_TLS_MAC); if (p) { - /* TODO: ? (octet_ptr) */ - ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_SET_PARAMETER); - return RET_OSSL_ERR; + ret = OSSL_PARAM_set_octet_ptr(p, cctx->tlsmac, cctx->tlsmacsize); + if (ret != RET_OSSL_OK) { + ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_SET_PARAMETER); + return RET_OSSL_ERR; + } } return RET_OSSL_OK; @@ -679,6 +877,43 @@ static int p11prov_aes_set_ctx_params(void *vctx, const OSSL_PARAM params[]) } } + p = OSSL_PARAM_locate_const(params, OSSL_CIPHER_PARAM_TLS_VERSION); + if (p) { + CK_RV rv = CKR_MECHANISM_PARAM_INVALID; + unsigned int version; + int ret = OSSL_PARAM_get_uint(p, &version); + if (ret != RET_OSSL_OK) { + P11PROV_raise(ctx->provctx, rv, "Invalid TLS Version parameter"); + return RET_OSSL_ERR; + } + switch (version) { + case 0x301: /* TLS 1.0 */ + case 0x302: /* TLS 1.1 */ + case 0x303: /* TLS 1.2 */ + ctx->tlsver = version; + break; + default: + P11PROV_raise(ctx->provctx, rv, "Unsupported TLS Version"); + return RET_OSSL_ERR; + } + } + + p = OSSL_PARAM_locate_const(params, OSSL_CIPHER_PARAM_TLS_MAC_SIZE); + if (p) { + CK_RV rv = CKR_MECHANISM_PARAM_INVALID; + size_t macsize; + int ret = OSSL_PARAM_get_size_t(p, &macsize); + if (ret != RET_OSSL_OK) { + P11PROV_raise(ctx->provctx, rv, "Invalid TLS MAC Size parameter"); + return RET_OSSL_ERR; + } + if (macsize > EVP_MAX_MD_SIZE) { + P11PROV_raise(ctx->provctx, rv, "Invalid TLS Mac Size"); + return RET_OSSL_ERR; + } + ctx->tlsmacsize = macsize; + } + return RET_OSSL_OK; } @@ -689,9 +924,7 @@ static const OSSL_PARAM p11prov_aes_generic_gettable_ctx_params[] = { OSSL_PARAM_uint(OSSL_CIPHER_PARAM_NUM, NULL), OSSL_PARAM_octet_string(OSSL_CIPHER_PARAM_IV, NULL, 0), OSSL_PARAM_octet_string(OSSL_CIPHER_PARAM_UPDATED_IV, NULL, 0), - /* Supported by OpenSSL but not here yet - * OSSL_CIPHER_PARAM_TLS_MAC - */ + OSSL_PARAM_octet_string(OSSL_CIPHER_PARAM_TLS_MAC, NULL, 0), OSSL_PARAM_END }; @@ -729,12 +962,12 @@ static const OSSL_PARAM *p11prov_aes_gettable_ctx_params(void *vctx, /* Supported by OpenSSL but not here: * OSSL_CIPHER_PARAM_NUM (uint) * OSSL_CIPHER_PARAM_USE_BITS (uint) - * OSSL_CIPHER_PARAM_TLS_VERSION (uint) - * OSSL_CIPHER_PARAM_TLS_MAC_SIZE (size_t) */ static const OSSL_PARAM p11prov_aes_generic_settable_ctx_params[] = { - GENERIC_SETTABLE_CTX_PARAMS(), OSSL_PARAM_END + GENERIC_SETTABLE_CTX_PARAMS(), + OSSL_PARAM_uint(OSSL_CIPHER_PARAM_TLS_VERSION, NULL), + OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_TLS_MAC_SIZE, NULL), OSSL_PARAM_END }; static const OSSL_PARAM p11prov_aes_cts_settable_ctx_params[] = { diff --git a/src/util.h b/src/util.h index c3c45c3e..0fe2a679 100644 --- a/src/util.h +++ b/src/util.h @@ -106,11 +106,21 @@ CK_RV p11prov_mutex_destroy(P11PROV_CTX *provctx, pthread_mutex_t *lock, void p11prov_force_rwlock_reinit(pthread_rwlock_t *lock); +static inline CK_ULONG constant_smaller_mask(CK_ULONG a, CK_ULONG b) +{ + return 0 - ((a ^ ((a ^ b) | ((a - b) ^ b))) >> (sizeof(CK_ULONG) * 8 - 1)); +} + static inline CK_ULONG constant_equal(CK_ULONG a, CK_ULONG b) { return ((a ^ b) - 1U) >> (sizeof(CK_ULONG) * 8 - 1); } +static inline CK_ULONG constant_equal_mask(CK_ULONG a, CK_ULONG b) +{ + return 0 - constant_equal(a, b); +} + static inline int constant_select_int(CK_ULONG cond, int a, int b) { volatile unsigned int A = (unsigned int)a; @@ -120,6 +130,14 @@ static inline int constant_select_int(CK_ULONG cond, int a, int b) return (int)((A & mask) | (B & ~mask)); } +static inline uint8_t constant_select_byte_mask(uint8_t a, uint8_t b, + uint8_t mask) +{ + volatile uint8_t A = a & mask; + volatile uint8_t B = b & ~mask; + return A | B; +} + static inline void constant_select_buf(CK_ULONG cond, CK_ULONG size, unsigned char *dst, unsigned char *a, unsigned char *b)