From: Matej Zachar <
zachar...@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...@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
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);
+}