| Message ID | 20251219112215.103862-3-bage@debian.org |
|---|---|
| State | Accepted |
| Headers | show |
| Series | pkcs11 decrypt provider based on p11-kit | expand |
Hi Bastian, Matej, On 12/19/25 12:21, Bastian Germann wrote: > From: Matej Zachar <zachar.matej@gmail.com> > > On platforms using "opTee + pkcs11 TA + RPMB" the decryption > speed is increased by order of magnitude - as the C_DecryptInit > context is not lost between decryption updates (C_DecryptUpdate). > On my board (iMX8MP) it went from 50min with wolfSSL to 3min with p11-kit > > Signed-off-by: Matej Zachar <zachar.matej@gmail.com> > --- > Makefile.flags | 2 - > crypto/Kconfig | 16 +- > crypto/Makefile | 6 +- > crypto/swupdate_decrypt_pkcs11_p11kit.c | 290 ++++++++++++++++++++++++ > test/Makefile | 32 ++- > test/data/token/softhsm.conf | 2 + > test/test_crypt_pkcs11.c | 98 ++++++++ > 7 files changed, 429 insertions(+), 17 deletions(-) > create mode 100644 crypto/swupdate_decrypt_pkcs11_p11kit.c > create mode 100644 test/data/token/softhsm.conf > create mode 100644 test/test_crypt_pkcs11.c > I full agree to discouple PKCS11 from a specific implementation like WolfSSL, but I guess I cannot test this myself before merging. I would like that someone sends your tested-by for this series or this patch. Nevertheless, this does not break anything, and it can be safe merged. Reviewed-by: Stefano Babic <stefano.babic@swupdate.org> Stefano > diff --git a/Makefile.flags b/Makefile.flags > index 65f112e8..40dd3b66 100644 > --- a/Makefile.flags > +++ b/Makefile.flags > @@ -163,8 +163,6 @@ endif > ifeq ($(CONFIG_SSL_IMPL_WOLFSSL),y) > KBUILD_CPPFLAGS += -DOPENSSL_ALL > LDLIBS += wolfssl > -else ifeq ($(CONFIG_PKCS11),y) > -LDLIBS += wolfssl > endif > > ifeq ($(CONFIG_SSL_IMPL_MBEDTLS),y) > diff --git a/crypto/Kconfig b/crypto/Kconfig > index 4b9db821..48eeb01c 100644 > --- a/crypto/Kconfig > +++ b/crypto/Kconfig > @@ -21,6 +21,10 @@ menu "Crypto libraries" > config SSL_IMPL_GPGME > bool "gpgme" > depends on HAVE_GPGME > + > + config PKCS11 > + bool "PKCS#11 (p11-kit)" > + depends on HAVE_P11KIT > endmenu > > config HASH_VERIFY > @@ -82,9 +86,9 @@ menu "Encryption" > > config ENCRYPTED_IMAGES > bool "Images can be encrypted with a symmetric key" > - depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL || SSL_IMPL_MBEDTLS > + depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL || SSL_IMPL_MBEDTLS || PKCS11 > comment "Image encryption needs an SSL implementation" > - depends on !SSL_IMPL_OPENSSL && !SSL_IMPL_WOLFSSL && !SSL_IMPL_MBEDTLS > + depends on !SSL_IMPL_OPENSSL && !SSL_IMPL_WOLFSSL && !SSL_IMPL_MBEDTLS && !PKCS11 > > config ENCRYPTED_SW_DESCRIPTION > bool "Even sw-description is encrypted" > @@ -126,13 +130,5 @@ config ENCRYPTED_IMAGES_HARDEN_LOGGING > hash mismatch and errors in the decryption finalization (padding) of a > streamed image are suppressed. > > -config PKCS11 > - bool "Enable PKCS#11 cryptographic operations" > - default n > - depends on SSL_IMPL_WOLFSSL && HAVE_P11KIT && ENCRYPTED_IMAGES > - help > - Enable using PKCS#11 for AES decryption instead of having the plain > - key available in a file. This is implemented with wolfSSL independent > - from the SSL implementation and replaces the plain key method. > endmenu > > diff --git a/crypto/Makefile b/crypto/Makefile > index 25ab3ab7..b591ff57 100644 > --- a/crypto/Makefile > +++ b/crypto/Makefile > @@ -14,10 +14,8 @@ endif > ifeq ($(CONFIG_SSL_IMPL_WOLFSSL),y) > obj-$(CONFIG_HASH_VERIFY) += swupdate_HASH_wolfssl.o > obj-$(CONFIG_SIGALG_CMS) += swupdate_pkcs7_verify_wolfssl.o > -ifeq ($(CONFIG_PKCS11),y) > obj-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt_wolfssl.o > endif > -endif > > ifeq ($(CONFIG_SSL_IMPL_MBEDTLS),y) > obj-$(CONFIG_HASH_VERIFY) += swupdate_HASH_mbedtls.o > @@ -28,3 +26,7 @@ endif > ifeq ($(CONFIG_SSL_IMPL_GPGME),y) > obj-$(CONFIG_SIGALG_GPG) += swupdate_gpg_verify.o > endif > + > +ifeq ($(CONFIG_PKCS11),y) > +obj-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt_pkcs11_p11kit.o > +endif > diff --git a/crypto/swupdate_decrypt_pkcs11_p11kit.c b/crypto/swupdate_decrypt_pkcs11_p11kit.c > new file mode 100644 > index 00000000..f66426bc > --- /dev/null > +++ b/crypto/swupdate_decrypt_pkcs11_p11kit.c > @@ -0,0 +1,290 @@ > +// SPDX-FileCopyrightText: 2024 Matej Zachar > +// > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Inspired by the wolfssl implementation done by Bastian Germann > + */ > + > +#include <errno.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include "swupdate_crypto.h" > +#include "swupdate_pkcs11.h" > +#include "util.h" > + > +static CK_SLOT_ID find_slot(CK_FUNCTION_LIST_PTR module, P11KitUri *uri) > +{ > + CK_RV rv; > + > + CK_SLOT_ID slot_id = p11_kit_uri_get_slot_id(uri); > + if (slot_id != (CK_SLOT_ID)-1) > + return slot_id; > + > + size_t slot_count; > + rv = module->C_GetSlotList(1, NULL_PTR, &slot_count); > + if (rv != CKR_OK) > + return (CK_SLOT_ID)-1; > + > + CK_SLOT_ID slot_ids[slot_count]; > + rv = module->C_GetSlotList(1, &slot_ids[0], &slot_count); > + if (rv != CKR_OK) > + return (CK_SLOT_ID)-1; > + > + CK_TOKEN_INFO token_info; > + for (int i = 0; i < slot_count; ++i) { > + slot_id = slot_ids[i]; > + > + rv = module->C_GetTokenInfo(slot_id, &token_info); > + if (rv != CKR_OK) > + return (CK_SLOT_ID)-1; > + > + if (p11_kit_uri_match_token_info(uri, &token_info)) > + return slot_id; > + } > + > + return (CK_SLOT_ID)-1; > +} > + > +static CK_RV find_key(CK_FUNCTION_LIST_PTR module, CK_SESSION_HANDLE session, > + CK_ATTRIBUTE_PTR key_id, CK_OBJECT_HANDLE *key_handle) > +{ > + CK_RV rv; > + > + CK_ATTRIBUTE find_template[] = { > + { CKA_ID, key_id->pValue, key_id->ulValueLen } > + }; > + > + rv = module->C_FindObjectsInit(session, find_template, 1); > + if (rv != CKR_OK) { > + return rv; > + } > + > + CK_ULONG object_count; > + rv = module->C_FindObjects(session, key_handle, 1, &object_count); > + if (rv != CKR_OK) { > + return rv; > + } > + > + rv = module->C_FindObjectsFinal(session); > + if (rv != CKR_OK) { > + return rv; > + } > + > + if (object_count == 0) { > + return CKR_DATA_INVALID; > + } > + > + return CKR_OK; > +} > + > +struct pkcs11_digest *pkcs11_DECRYPT_init(unsigned char *uri, > + char __attribute__ ((__unused__)) keylen, unsigned char *iv, cipher_t cipher) > +{ > + struct pkcs11_digest *dgst; > + CK_SLOT_ID slot_id; > + CK_ATTRIBUTE_PTR key_id; > + const char *pin; > + const char *module_path; > + const char *msg; > + int err = 0; > + CK_RV rv; > + > + if (uri == NULL || iv == NULL) { > + ERROR("PKCS#11 URI or AES IV missing for decryption!"); > + return NULL; > + } > + > + dgst = calloc(1, sizeof(*dgst)); > + if (!dgst) { > + return NULL; > + } > + > + dgst->uri = p11_kit_uri_new(); > + err = p11_kit_uri_parse((const char*)uri, P11_KIT_URI_FOR_OBJECT_ON_TOKEN_AND_MODULE, dgst->uri); > + if (err) { > + msg = p11_kit_uri_message(err); > + ERROR("PKCS#11 URI: %s", msg); > + goto free_digest; > + } > + > + key_id = p11_kit_uri_get_attribute(dgst->uri, CKA_ID); > + pin = p11_kit_uri_get_pin_value(dgst->uri); > + module_path = p11_kit_uri_get_module_path(dgst->uri); > + if (key_id == NULL || pin == NULL || module_path == NULL) { > + ERROR("PKCS#11 URI must contain id, pin-value and module-path."); > + goto free_digest; > + } > + > + dgst->module = p11_kit_module_load(module_path, 0); > + if (dgst->module == NULL) { > + msg = p11_kit_message(); > + ERROR("Failed to load PKCS#11 module [%s]: %s\n", module_path, msg); > + goto free_digest; > + } > + > + rv = dgst->module->C_Initialize(NULL_PTR); > + if (rv != CKR_OK) > + goto err_msg; > + > + slot_id = find_slot(dgst->module, dgst->uri); > + if (slot_id == -1) { > + ERROR("PKCS#11 URI must contain slot-id or token identification such as token, model, serial, manufacturer."); > + goto free_digest; > + } > + > + rv = dgst->module->C_OpenSession(slot_id, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &dgst->session); > + if (rv != CKR_OK) > + goto err_msg; > + > + rv = dgst->module->C_Login(dgst->session, CKU_USER, (unsigned char *)pin, strnlen(pin, 32)); > + if (rv != CKR_OK) > + goto err_msg; > + > + CK_OBJECT_HANDLE key; > + rv = find_key(dgst->module, dgst->session, key_id, &key); > + if (rv != CKR_OK) > + goto err_msg; > + > + // Setup a valid PKCS#7 block plus one state octet > + for (int i = 0; i <= AES_BLK_SIZE; ++i) { > + dgst->last[i] = AES_BLK_SIZE; > + } > + > + // Setup IV vector & mechanism > + memcpy(dgst->iv, iv, AES_BLK_SIZE); > + dgst->mechanism.mechanism = CKM_AES_CBC; > + dgst->mechanism.pParameter = dgst->iv; > + dgst->mechanism.ulParameterLen = AES_BLK_SIZE; > + > + rv = dgst->module->C_DecryptInit(dgst->session, &dgst->mechanism, key); > + if (rv != CKR_OK) > + goto err_msg; > + > + INFO("PKCS#11 key set up successfully."); > + return dgst; > + > +err_msg: > + msg = p11_kit_strerror(rv); > + ERROR("PKCS#11 initialization failed: %s", msg); > + > +free_digest: > + if (dgst->uri) > + p11_kit_uri_free(dgst->uri); > + > + if (dgst->session) > + dgst->module->C_CloseSession(dgst->session); > + > + if (dgst->module) { > + dgst->module->C_Finalize(NULL_PTR); > + p11_kit_module_release(dgst->module); > + } > + > + free(dgst); > + > + return NULL; > +} > + > +int pkcs11_DECRYPT_update(struct pkcs11_digest *dgst, unsigned char *buf, > + int *outlen, const unsigned char *cryptbuf, int inlen) > +{ > + // precondition: len(buf) >= inlen + AES_BLK_SIZE > + unsigned long buf_len = inlen + AES_BLK_SIZE; > + CK_RV rv; > + > + if (inlen < AES_BLK_SIZE) > + return -EFAULT; > + > + if (dgst->last[AES_BLK_SIZE]) { > + dgst->last[AES_BLK_SIZE] = 0; > + // first run - there is no block to append > + *outlen = 0; > + } else { > + // append previously decrypted last AES block > + memcpy(buf, dgst->last, AES_BLK_SIZE); > + buf += AES_BLK_SIZE; > + *outlen = AES_BLK_SIZE; > + } > + > + rv = dgst->module->C_DecryptUpdate(dgst->session, (unsigned char*)cryptbuf, inlen, buf, &buf_len); > + if (rv != CKR_OK) { > + ERROR("PKCS#11 AES decryption failed: %s", p11_kit_strerror(rv)); > + return -EFAULT; > + } > + > + // strip and remember last AES block from decoded buffer > + // it will get appended either in the next call to DECRYPT_update or DECRYPT_final > + buf_len -= AES_BLK_SIZE; > + memcpy(dgst->last, &buf[buf_len], AES_BLK_SIZE); > + > + // update iv for the next block > + memcpy(dgst->iv, cryptbuf + inlen - AES_BLK_SIZE, AES_BLK_SIZE); > + > + *outlen += (int)buf_len; > + return 0; > +} > + > +int pkcs11_DECRYPT_final(struct pkcs11_digest *dgst, unsigned char *buf, int *outlen) > +{ > + CK_RV rv; > + unsigned long extra_len = 0; > + > + if (dgst->last[AES_BLK_SIZE]) { > +#ifndef CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING > + ERROR("AES: At least one call to pkcs11_DECRYPT_update was expected"); > +#endif > + return -EINVAL; > + } > + > + // append previously decrypted last AES block if any > + memcpy(buf, dgst->last, AES_BLK_SIZE); > + > + rv = dgst->module->C_DecryptFinal(dgst->session, &buf[AES_BLK_SIZE], &extra_len); > + if (rv != CKR_OK) > + return -EFAULT; > + > + // obtain last AES block after C_DecryptFinal > + CK_BYTE_PTR last = &buf[extra_len]; > + > + // Handle manual PKCS#7 padding removal > + CK_BYTE padding_value = last[AES_BLK_SIZE - 1]; > + > + if (padding_value <= 0 || padding_value > AES_BLK_SIZE) { > +#ifndef CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING > + ERROR("AES: Invalid PKCS#7 padding value [%u]", padding_value); > +#endif > + return -EFAULT; > + } > + > + // Verify that padding is correct > + for (CK_BYTE i = 0; i < padding_value; ++i) { > + if (last[AES_BLK_SIZE - 1 - i] != padding_value) { > +#ifndef CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING > + ERROR("AES: Invalid PKCS#7 padding value [%u] at offset %u", padding_value, i); > +#endif > + return -EINVAL; > + } > + } > + > + *outlen = (int)extra_len + AES_BLK_SIZE - padding_value; > + return 0; > +} > + > +void pkcs11_DECRYPT_cleanup(struct pkcs11_digest *dgst) > +{ > + if (dgst) { > + if (dgst->uri) > + p11_kit_uri_free(dgst->uri); > + > + if (dgst->session) > + dgst->module->C_CloseSession(dgst->session); > + > + if (dgst->module) { > + dgst->module->C_Finalize(NULL_PTR); > + p11_kit_module_release(dgst->module); > + } > + > + free(dgst); > + dgst = NULL; > + } > +} > diff --git a/test/Makefile b/test/Makefile > index 385efd85..8bdf50d6 100644 > --- a/test/Makefile > +++ b/test/Makefile > @@ -17,9 +17,10 @@ > ## along with this program; if not, write to the Free Software > ## Foundation, Inc. > > -ifneq ($(CONFIG_PKCS11),y) > -tests-$(CONFIG_ENCRYPTED_IMAGES) += test_crypt > +ifeq ($(CONFIG_PKCS11),y) > +tests-$(CONFIG_ENCRYPTED_IMAGES) += test_crypt_pkcs11 > endif > +tests-$(CONFIG_ENCRYPTED_IMAGES) += test_crypt > tests-$(CONFIG_HASH_VERIFY) += test_hash > ifeq ($(CONFIG_SIGALG_RAWRSA),y) > tests-$(CONFIG_SIGNED_IMAGES) += test_verify > @@ -66,7 +67,7 @@ quiet_cmd_linktestexe = LD $(basename $@) > "$(SWLIBS)" \ > "$(LDLIBS) cmocka" > > -EXECUTE_TEST = echo "RUN $(subst $(obj)/,,$(var))"; LD_LIBRARY_PATH=$(objtree) CMOCKA_MESSAGE_OUTPUT=TAP $(var) > +EXECUTE_TEST = echo "RUN $(subst $(obj)/,,$(var))"; LD_LIBRARY_PATH=$(objtree) CMOCKA_MESSAGE_OUTPUT=TAP SOFTHSM2_CONF=$(DATADIR)/token/softhsm.conf $(var) > > PHONY += default > default: > @@ -111,4 +112,29 @@ $(DATADIR)/signing-secret.pem: > $(if $(Q),@echo " GEN $@") > $(Q)openssl genrsa -out $@ 2>/dev/null > > +ifeq ($(CONFIG_PKCS11),y) > +$(obj)/test_crypt_pkcs11.o: $(DATADIR)/softshm > + > +TOKEN_AES_KEY := dd020ce5ebd5c468556288d6a75169c88a5b335d9f569e30751c50401467d230 > +TOKEN_AES_IV := c1f390d21dd06118cbd333144a3318ca > + > +.INTERMEDIATE: $(DATADIR)/softshm > +$(DATADIR)/softshm: export SOFTHSM2_CONF=$(DATADIR)/token/softhsm.conf > +$(DATADIR)/softshm: $(DATADIR)/token/softhsm.conf > + $(if $(Q),@echo " INIT $@") > + $(Q)rm -rf $(DATADIR)/token/*/ > + > + $(if $(Q),@echo " GEN $(DATADIR)/token/original.data") > + $(Q)openssl rand 131075 > $(DATADIR)/token/original.data > + > + $(if $(Q),@echo " ENCRYPT $(DATADIR)/token/original.data") > + $(Q)openssl enc -aes-256-cbc -in $(DATADIR)/token/original.data -out $(DATADIR)/token/encrypted.data -K $(TOKEN_AES_KEY) -iv $(TOKEN_AES_IV) > + $(Q)echo -n "$(TOKEN_AES_IV)" | xxd -p -r > $(DATADIR)/token/encrypted.data.iv > + > + $(if $(Q),@echo " IMPORT $(DATADIR)/token/aes.key") > + $(Q)echo -n "$(TOKEN_AES_KEY)" | xxd -p -r > $(DATADIR)/token/aes.key > + $(Q)softhsm2-util --init-token --slot 0 --label "TestToken" --so-pin 123456 --pin 1234 > + $(Q)softhsm2-util --import $(DATADIR)/token/aes.key --aes --token "TestToken" --label "AES key" --id A1B2 --pin 1234 > +endif > + > .PHONY: $(PHONY) > diff --git a/test/data/token/softhsm.conf b/test/data/token/softhsm.conf > new file mode 100644 > index 00000000..bd43fe45 > --- /dev/null > +++ b/test/data/token/softhsm.conf > @@ -0,0 +1,2 @@ > +directories.tokendir = test/data/token > +objectstore.backend = file > \ No newline at end of file > diff --git a/test/test_crypt_pkcs11.c b/test/test_crypt_pkcs11.c > new file mode 100644 > index 00000000..94ed92a8 > --- /dev/null > +++ b/test/test_crypt_pkcs11.c > @@ -0,0 +1,98 @@ > +// SPDX-FileCopyrightText: 2024 Matej Zachar > +// > +// SPDX-License-Identifier: GPL-2.0-only > + > +#include <stdlib.h> > +#include <stdio.h> > +#include <stdarg.h> > +#include <stddef.h> > +#include <setjmp.h> > +#include <cmocka.h> > +#include <util.h> > +#include "swupdate_crypto.h" > + > +#define BUFFER_SIZE (AES_BLK_SIZE * 1024) > +#define TOKENDIR "test/data/token" > + > +static int read_file(const char *path, unsigned char *buffer, size_t *size) > +{ > + FILE *fp = fopen(path, "r"); > + if (!fp) { > + fprintf(stderr, "Failed to open file '%s'\n", path); > + return -1; > + } > + > + size_t len = fread(buffer, sizeof(char), *size, fp); > + if (ferror(fp) != 0) { > + fprintf(stderr, "Error reading file '%s'\n", path); > + fclose(fp); > + return -1; > + } > + > + *size = len; > + fclose(fp); > + > + return 0; > +} > + > +static void test_crypt_pkcs11_256(void **state) > +{ > + (void) state; > + int err; > + > + const char * uri = "pkcs11:token=TestToken;id=%A1%B2?pin-value=1234&module-path=/usr/lib/softhsm/libsofthsm2.so"; > + > + size_t original_data_len = 128 * 1024;/* 128KiB */ > + unsigned char original_data[original_data_len]; > + err = read_file(TOKENDIR "/original.data", &original_data[0], &original_data_len); > + assert_true(err == 0); > + > + size_t encrypted_data_len = 128 * 1024 + AES_BLK_SIZE;/* 128KiB AES_BLK_SIZE(16B) */ > + unsigned char encrypted_data[encrypted_data_len]; > + err = read_file(TOKENDIR "/encrypted.data", &encrypted_data[0], &encrypted_data_len); > + assert_true(err == 0); > + > + unsigned char decrypted_data[encrypted_data_len]; > + > + size_t iv_size = 16; > + unsigned char iv[iv_size]; > + err = read_file(TOKENDIR "/encrypted.data.iv", &iv[0], &iv_size); > + assert_true(err == 0); > + > + unsigned char buffer[BUFFER_SIZE + AES_BLK_SIZE]; > + > + struct swupdate_digest *dgst = swupdate_DECRYPT_init((unsigned char *)uri, 0, &iv[0], AES_CBC_256); > + assert_non_null(dgst); > + > + int len; > + size_t e_offset = 0; > + size_t d_offset = 0; > + while (e_offset < encrypted_data_len) { > + size_t chunk_size = (encrypted_data_len - e_offset > BUFFER_SIZE) ? BUFFER_SIZE : encrypted_data_len - e_offset; > + > + err = swupdate_DECRYPT_update(dgst, buffer, &len, encrypted_data + e_offset, chunk_size); > + assert_true(err == 0); > + assert_true(len >= AES_BLK_SIZE && len <= chunk_size); > + e_offset += chunk_size; > + > + memcpy(&decrypted_data[d_offset], buffer, len); > + d_offset += len; > + } > + > + err = swupdate_DECRYPT_final(dgst, buffer, &len); > + assert_true(err == 0); > + assert_true(len == 3); /* as the size is 128*1024+3 */ > + > + memcpy(&decrypted_data[d_offset], buffer, len); > + d_offset += len; > + > + assert_true(strncmp((const char *)decrypted_data, (const char *)original_data, original_data_len) == 0); > +} > + > +int main(void) > +{ > + const struct CMUnitTest crypt_pkcs11_tests[] = { > + cmocka_unit_test(test_crypt_pkcs11_256) > + }; > + return cmocka_run_group_tests_name("crypt_pkcs11", crypt_pkcs11_tests, NULL, NULL); > +} >
diff --git a/Makefile.flags b/Makefile.flags index 65f112e8..40dd3b66 100644 --- a/Makefile.flags +++ b/Makefile.flags @@ -163,8 +163,6 @@ endif ifeq ($(CONFIG_SSL_IMPL_WOLFSSL),y) KBUILD_CPPFLAGS += -DOPENSSL_ALL LDLIBS += wolfssl -else ifeq ($(CONFIG_PKCS11),y) -LDLIBS += wolfssl endif ifeq ($(CONFIG_SSL_IMPL_MBEDTLS),y) diff --git a/crypto/Kconfig b/crypto/Kconfig index 4b9db821..48eeb01c 100644 --- a/crypto/Kconfig +++ b/crypto/Kconfig @@ -21,6 +21,10 @@ menu "Crypto libraries" config SSL_IMPL_GPGME bool "gpgme" depends on HAVE_GPGME + + config PKCS11 + bool "PKCS#11 (p11-kit)" + depends on HAVE_P11KIT endmenu config HASH_VERIFY @@ -82,9 +86,9 @@ menu "Encryption" config ENCRYPTED_IMAGES bool "Images can be encrypted with a symmetric key" - depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL || SSL_IMPL_MBEDTLS + depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL || SSL_IMPL_MBEDTLS || PKCS11 comment "Image encryption needs an SSL implementation" - depends on !SSL_IMPL_OPENSSL && !SSL_IMPL_WOLFSSL && !SSL_IMPL_MBEDTLS + depends on !SSL_IMPL_OPENSSL && !SSL_IMPL_WOLFSSL && !SSL_IMPL_MBEDTLS && !PKCS11 config ENCRYPTED_SW_DESCRIPTION bool "Even sw-description is encrypted" @@ -126,13 +130,5 @@ config ENCRYPTED_IMAGES_HARDEN_LOGGING hash mismatch and errors in the decryption finalization (padding) of a streamed image are suppressed. -config PKCS11 - bool "Enable PKCS#11 cryptographic operations" - default n - depends on SSL_IMPL_WOLFSSL && HAVE_P11KIT && ENCRYPTED_IMAGES - help - Enable using PKCS#11 for AES decryption instead of having the plain - key available in a file. This is implemented with wolfSSL independent - from the SSL implementation and replaces the plain key method. endmenu diff --git a/crypto/Makefile b/crypto/Makefile index 25ab3ab7..b591ff57 100644 --- a/crypto/Makefile +++ b/crypto/Makefile @@ -14,10 +14,8 @@ endif ifeq ($(CONFIG_SSL_IMPL_WOLFSSL),y) obj-$(CONFIG_HASH_VERIFY) += swupdate_HASH_wolfssl.o obj-$(CONFIG_SIGALG_CMS) += swupdate_pkcs7_verify_wolfssl.o -ifeq ($(CONFIG_PKCS11),y) obj-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt_wolfssl.o endif -endif ifeq ($(CONFIG_SSL_IMPL_MBEDTLS),y) obj-$(CONFIG_HASH_VERIFY) += swupdate_HASH_mbedtls.o @@ -28,3 +26,7 @@ endif ifeq ($(CONFIG_SSL_IMPL_GPGME),y) obj-$(CONFIG_SIGALG_GPG) += swupdate_gpg_verify.o endif + +ifeq ($(CONFIG_PKCS11),y) +obj-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt_pkcs11_p11kit.o +endif diff --git a/crypto/swupdate_decrypt_pkcs11_p11kit.c b/crypto/swupdate_decrypt_pkcs11_p11kit.c new file mode 100644 index 00000000..f66426bc --- /dev/null +++ b/crypto/swupdate_decrypt_pkcs11_p11kit.c @@ -0,0 +1,290 @@ +// SPDX-FileCopyrightText: 2024 Matej Zachar +// +// SPDX-License-Identifier: GPL-2.0-only +/* + * Inspired by the wolfssl implementation done by Bastian Germann + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "swupdate_crypto.h" +#include "swupdate_pkcs11.h" +#include "util.h" + +static CK_SLOT_ID find_slot(CK_FUNCTION_LIST_PTR module, P11KitUri *uri) +{ + CK_RV rv; + + CK_SLOT_ID slot_id = p11_kit_uri_get_slot_id(uri); + if (slot_id != (CK_SLOT_ID)-1) + return slot_id; + + size_t slot_count; + rv = module->C_GetSlotList(1, NULL_PTR, &slot_count); + if (rv != CKR_OK) + return (CK_SLOT_ID)-1; + + CK_SLOT_ID slot_ids[slot_count]; + rv = module->C_GetSlotList(1, &slot_ids[0], &slot_count); + if (rv != CKR_OK) + return (CK_SLOT_ID)-1; + + CK_TOKEN_INFO token_info; + for (int i = 0; i < slot_count; ++i) { + slot_id = slot_ids[i]; + + rv = module->C_GetTokenInfo(slot_id, &token_info); + if (rv != CKR_OK) + return (CK_SLOT_ID)-1; + + if (p11_kit_uri_match_token_info(uri, &token_info)) + return slot_id; + } + + return (CK_SLOT_ID)-1; +} + +static CK_RV find_key(CK_FUNCTION_LIST_PTR module, CK_SESSION_HANDLE session, + CK_ATTRIBUTE_PTR key_id, CK_OBJECT_HANDLE *key_handle) +{ + CK_RV rv; + + CK_ATTRIBUTE find_template[] = { + { CKA_ID, key_id->pValue, key_id->ulValueLen } + }; + + rv = module->C_FindObjectsInit(session, find_template, 1); + if (rv != CKR_OK) { + return rv; + } + + CK_ULONG object_count; + rv = module->C_FindObjects(session, key_handle, 1, &object_count); + if (rv != CKR_OK) { + return rv; + } + + rv = module->C_FindObjectsFinal(session); + if (rv != CKR_OK) { + return rv; + } + + if (object_count == 0) { + return CKR_DATA_INVALID; + } + + return CKR_OK; +} + +struct pkcs11_digest *pkcs11_DECRYPT_init(unsigned char *uri, + char __attribute__ ((__unused__)) keylen, unsigned char *iv, cipher_t cipher) +{ + struct pkcs11_digest *dgst; + CK_SLOT_ID slot_id; + CK_ATTRIBUTE_PTR key_id; + const char *pin; + const char *module_path; + const char *msg; + int err = 0; + CK_RV rv; + + if (uri == NULL || iv == NULL) { + ERROR("PKCS#11 URI or AES IV missing for decryption!"); + return NULL; + } + + dgst = calloc(1, sizeof(*dgst)); + if (!dgst) { + return NULL; + } + + dgst->uri = p11_kit_uri_new(); + err = p11_kit_uri_parse((const char*)uri, P11_KIT_URI_FOR_OBJECT_ON_TOKEN_AND_MODULE, dgst->uri); + if (err) { + msg = p11_kit_uri_message(err); + ERROR("PKCS#11 URI: %s", msg); + goto free_digest; + } + + key_id = p11_kit_uri_get_attribute(dgst->uri, CKA_ID); + pin = p11_kit_uri_get_pin_value(dgst->uri); + module_path = p11_kit_uri_get_module_path(dgst->uri); + if (key_id == NULL || pin == NULL || module_path == NULL) { + ERROR("PKCS#11 URI must contain id, pin-value and module-path."); + goto free_digest; + } + + dgst->module = p11_kit_module_load(module_path, 0); + if (dgst->module == NULL) { + msg = p11_kit_message(); + ERROR("Failed to load PKCS#11 module [%s]: %s\n", module_path, msg); + goto free_digest; + } + + rv = dgst->module->C_Initialize(NULL_PTR); + if (rv != CKR_OK) + goto err_msg; + + slot_id = find_slot(dgst->module, dgst->uri); + if (slot_id == -1) { + ERROR("PKCS#11 URI must contain slot-id or token identification such as token, model, serial, manufacturer."); + goto free_digest; + } + + rv = dgst->module->C_OpenSession(slot_id, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &dgst->session); + if (rv != CKR_OK) + goto err_msg; + + rv = dgst->module->C_Login(dgst->session, CKU_USER, (unsigned char *)pin, strnlen(pin, 32)); + if (rv != CKR_OK) + goto err_msg; + + CK_OBJECT_HANDLE key; + rv = find_key(dgst->module, dgst->session, key_id, &key); + if (rv != CKR_OK) + goto err_msg; + + // Setup a valid PKCS#7 block plus one state octet + for (int i = 0; i <= AES_BLK_SIZE; ++i) { + dgst->last[i] = AES_BLK_SIZE; + } + + // Setup IV vector & mechanism + memcpy(dgst->iv, iv, AES_BLK_SIZE); + dgst->mechanism.mechanism = CKM_AES_CBC; + dgst->mechanism.pParameter = dgst->iv; + dgst->mechanism.ulParameterLen = AES_BLK_SIZE; + + rv = dgst->module->C_DecryptInit(dgst->session, &dgst->mechanism, key); + if (rv != CKR_OK) + goto err_msg; + + INFO("PKCS#11 key set up successfully."); + return dgst; + +err_msg: + msg = p11_kit_strerror(rv); + ERROR("PKCS#11 initialization failed: %s", msg); + +free_digest: + if (dgst->uri) + p11_kit_uri_free(dgst->uri); + + if (dgst->session) + dgst->module->C_CloseSession(dgst->session); + + if (dgst->module) { + dgst->module->C_Finalize(NULL_PTR); + p11_kit_module_release(dgst->module); + } + + free(dgst); + + return NULL; +} + +int pkcs11_DECRYPT_update(struct pkcs11_digest *dgst, unsigned char *buf, + int *outlen, const unsigned char *cryptbuf, int inlen) +{ + // precondition: len(buf) >= inlen + AES_BLK_SIZE + unsigned long buf_len = inlen + AES_BLK_SIZE; + CK_RV rv; + + if (inlen < AES_BLK_SIZE) + return -EFAULT; + + if (dgst->last[AES_BLK_SIZE]) { + dgst->last[AES_BLK_SIZE] = 0; + // first run - there is no block to append + *outlen = 0; + } else { + // append previously decrypted last AES block + memcpy(buf, dgst->last, AES_BLK_SIZE); + buf += AES_BLK_SIZE; + *outlen = AES_BLK_SIZE; + } + + rv = dgst->module->C_DecryptUpdate(dgst->session, (unsigned char*)cryptbuf, inlen, buf, &buf_len); + if (rv != CKR_OK) { + ERROR("PKCS#11 AES decryption failed: %s", p11_kit_strerror(rv)); + return -EFAULT; + } + + // strip and remember last AES block from decoded buffer + // it will get appended either in the next call to DECRYPT_update or DECRYPT_final + buf_len -= AES_BLK_SIZE; + memcpy(dgst->last, &buf[buf_len], AES_BLK_SIZE); + + // update iv for the next block + memcpy(dgst->iv, cryptbuf + inlen - AES_BLK_SIZE, AES_BLK_SIZE); + + *outlen += (int)buf_len; + return 0; +} + +int pkcs11_DECRYPT_final(struct pkcs11_digest *dgst, unsigned char *buf, int *outlen) +{ + CK_RV rv; + unsigned long extra_len = 0; + + if (dgst->last[AES_BLK_SIZE]) { +#ifndef CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING + ERROR("AES: At least one call to pkcs11_DECRYPT_update was expected"); +#endif + return -EINVAL; + } + + // append previously decrypted last AES block if any + memcpy(buf, dgst->last, AES_BLK_SIZE); + + rv = dgst->module->C_DecryptFinal(dgst->session, &buf[AES_BLK_SIZE], &extra_len); + if (rv != CKR_OK) + return -EFAULT; + + // obtain last AES block after C_DecryptFinal + CK_BYTE_PTR last = &buf[extra_len]; + + // Handle manual PKCS#7 padding removal + CK_BYTE padding_value = last[AES_BLK_SIZE - 1]; + + if (padding_value <= 0 || padding_value > AES_BLK_SIZE) { +#ifndef CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING + ERROR("AES: Invalid PKCS#7 padding value [%u]", padding_value); +#endif + return -EFAULT; + } + + // Verify that padding is correct + for (CK_BYTE i = 0; i < padding_value; ++i) { + if (last[AES_BLK_SIZE - 1 - i] != padding_value) { +#ifndef CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING + ERROR("AES: Invalid PKCS#7 padding value [%u] at offset %u", padding_value, i); +#endif + return -EINVAL; + } + } + + *outlen = (int)extra_len + AES_BLK_SIZE - padding_value; + return 0; +} + +void pkcs11_DECRYPT_cleanup(struct pkcs11_digest *dgst) +{ + if (dgst) { + if (dgst->uri) + p11_kit_uri_free(dgst->uri); + + if (dgst->session) + dgst->module->C_CloseSession(dgst->session); + + if (dgst->module) { + dgst->module->C_Finalize(NULL_PTR); + p11_kit_module_release(dgst->module); + } + + free(dgst); + dgst = NULL; + } +} diff --git a/test/Makefile b/test/Makefile index 385efd85..8bdf50d6 100644 --- a/test/Makefile +++ b/test/Makefile @@ -17,9 +17,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc. -ifneq ($(CONFIG_PKCS11),y) -tests-$(CONFIG_ENCRYPTED_IMAGES) += test_crypt +ifeq ($(CONFIG_PKCS11),y) +tests-$(CONFIG_ENCRYPTED_IMAGES) += test_crypt_pkcs11 endif +tests-$(CONFIG_ENCRYPTED_IMAGES) += test_crypt tests-$(CONFIG_HASH_VERIFY) += test_hash ifeq ($(CONFIG_SIGALG_RAWRSA),y) tests-$(CONFIG_SIGNED_IMAGES) += test_verify @@ -66,7 +67,7 @@ quiet_cmd_linktestexe = LD $(basename $@) "$(SWLIBS)" \ "$(LDLIBS) cmocka" -EXECUTE_TEST = echo "RUN $(subst $(obj)/,,$(var))"; LD_LIBRARY_PATH=$(objtree) CMOCKA_MESSAGE_OUTPUT=TAP $(var) +EXECUTE_TEST = echo "RUN $(subst $(obj)/,,$(var))"; LD_LIBRARY_PATH=$(objtree) CMOCKA_MESSAGE_OUTPUT=TAP SOFTHSM2_CONF=$(DATADIR)/token/softhsm.conf $(var) PHONY += default default: @@ -111,4 +112,29 @@ $(DATADIR)/signing-secret.pem: $(if $(Q),@echo " GEN $@") $(Q)openssl genrsa -out $@ 2>/dev/null +ifeq ($(CONFIG_PKCS11),y) +$(obj)/test_crypt_pkcs11.o: $(DATADIR)/softshm + +TOKEN_AES_KEY := dd020ce5ebd5c468556288d6a75169c88a5b335d9f569e30751c50401467d230 +TOKEN_AES_IV := c1f390d21dd06118cbd333144a3318ca + +.INTERMEDIATE: $(DATADIR)/softshm +$(DATADIR)/softshm: export SOFTHSM2_CONF=$(DATADIR)/token/softhsm.conf +$(DATADIR)/softshm: $(DATADIR)/token/softhsm.conf + $(if $(Q),@echo " INIT $@") + $(Q)rm -rf $(DATADIR)/token/*/ + + $(if $(Q),@echo " GEN $(DATADIR)/token/original.data") + $(Q)openssl rand 131075 > $(DATADIR)/token/original.data + + $(if $(Q),@echo " ENCRYPT $(DATADIR)/token/original.data") + $(Q)openssl enc -aes-256-cbc -in $(DATADIR)/token/original.data -out $(DATADIR)/token/encrypted.data -K $(TOKEN_AES_KEY) -iv $(TOKEN_AES_IV) + $(Q)echo -n "$(TOKEN_AES_IV)" | xxd -p -r > $(DATADIR)/token/encrypted.data.iv + + $(if $(Q),@echo " IMPORT $(DATADIR)/token/aes.key") + $(Q)echo -n "$(TOKEN_AES_KEY)" | xxd -p -r > $(DATADIR)/token/aes.key + $(Q)softhsm2-util --init-token --slot 0 --label "TestToken" --so-pin 123456 --pin 1234 + $(Q)softhsm2-util --import $(DATADIR)/token/aes.key --aes --token "TestToken" --label "AES key" --id A1B2 --pin 1234 +endif + .PHONY: $(PHONY) diff --git a/test/data/token/softhsm.conf b/test/data/token/softhsm.conf new file mode 100644 index 00000000..bd43fe45 --- /dev/null +++ b/test/data/token/softhsm.conf @@ -0,0 +1,2 @@ +directories.tokendir = test/data/token +objectstore.backend = file \ No newline at end of file diff --git a/test/test_crypt_pkcs11.c b/test/test_crypt_pkcs11.c new file mode 100644 index 00000000..94ed92a8 --- /dev/null +++ b/test/test_crypt_pkcs11.c @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2024 Matej Zachar +// +// SPDX-License-Identifier: GPL-2.0-only + +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <cmocka.h> +#include <util.h> +#include "swupdate_crypto.h" + +#define BUFFER_SIZE (AES_BLK_SIZE * 1024) +#define TOKENDIR "test/data/token" + +static int read_file(const char *path, unsigned char *buffer, size_t *size) +{ + FILE *fp = fopen(path, "r"); + if (!fp) { + fprintf(stderr, "Failed to open file '%s'\n", path); + return -1; + } + + size_t len = fread(buffer, sizeof(char), *size, fp); + if (ferror(fp) != 0) { + fprintf(stderr, "Error reading file '%s'\n", path); + fclose(fp); + return -1; + } + + *size = len; + fclose(fp); + + return 0; +} + +static void test_crypt_pkcs11_256(void **state) +{ + (void) state; + int err; + + const char * uri = "pkcs11:token=TestToken;id=%A1%B2?pin-value=1234&module-path=/usr/lib/softhsm/libsofthsm2.so"; + + size_t original_data_len = 128 * 1024;/* 128KiB */ + unsigned char original_data[original_data_len]; + err = read_file(TOKENDIR "/original.data", &original_data[0], &original_data_len); + assert_true(err == 0); + + size_t encrypted_data_len = 128 * 1024 + AES_BLK_SIZE;/* 128KiB AES_BLK_SIZE(16B) */ + unsigned char encrypted_data[encrypted_data_len]; + err = read_file(TOKENDIR "/encrypted.data", &encrypted_data[0], &encrypted_data_len); + assert_true(err == 0); + + unsigned char decrypted_data[encrypted_data_len]; + + size_t iv_size = 16; + unsigned char iv[iv_size]; + err = read_file(TOKENDIR "/encrypted.data.iv", &iv[0], &iv_size); + assert_true(err == 0); + + unsigned char buffer[BUFFER_SIZE + AES_BLK_SIZE]; + + struct swupdate_digest *dgst = swupdate_DECRYPT_init((unsigned char *)uri, 0, &iv[0], AES_CBC_256); + assert_non_null(dgst); + + int len; + size_t e_offset = 0; + size_t d_offset = 0; + while (e_offset < encrypted_data_len) { + size_t chunk_size = (encrypted_data_len - e_offset > BUFFER_SIZE) ? BUFFER_SIZE : encrypted_data_len - e_offset; + + err = swupdate_DECRYPT_update(dgst, buffer, &len, encrypted_data + e_offset, chunk_size); + assert_true(err == 0); + assert_true(len >= AES_BLK_SIZE && len <= chunk_size); + e_offset += chunk_size; + + memcpy(&decrypted_data[d_offset], buffer, len); + d_offset += len; + } + + err = swupdate_DECRYPT_final(dgst, buffer, &len); + assert_true(err == 0); + assert_true(len == 3); /* as the size is 128*1024+3 */ + + memcpy(&decrypted_data[d_offset], buffer, len); + d_offset += len; + + assert_true(strncmp((const char *)decrypted_data, (const char *)original_data, original_data_len) == 0); +} + +int main(void) +{ + const struct CMUnitTest crypt_pkcs11_tests[] = { + cmocka_unit_test(test_crypt_pkcs11_256) + }; + return cmocka_run_group_tests_name("crypt_pkcs11", crypt_pkcs11_tests, NULL, NULL); +}