diff --git a/.github/workflows/test-psa-api.yml b/.github/workflows/test-psa-api.yml index 753265e..da07524 100644 --- a/.github/workflows/test-psa-api.yml +++ b/.github/workflows/test-psa-api.yml @@ -110,3 +110,10 @@ jobs: make -C test psa_devid_cryptocb_test ASAN=1 rm -rf .store test/.store ./test/psa_devid_cryptocb_test + + # LeakSanitizer checks that failed key file loads free their buffer + - name: Run the pk test under AddressSanitizer + run: | + rm -f test/psa_tf_psa_pk_test test/psa_server/psa_tf_psa_pk_test.o + make -C test psa_tf_psa_pk_test ASAN=1 + ./test/psa_tf_psa_pk_test diff --git a/.gitignore b/.gitignore index c41b6d2..00b7107 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,9 @@ build-test/out/ test/psa_zero_capacity_buffer_test test/psa_tf_psa_extra_test test/psa_want_surface_test +test/psa_tf_psa_pk_test +test/psa_tf_psa_threading_test +test/build-threads +test/build-threads-asan +.cache/ +compile_commands.json diff --git a/Makefile b/Makefile index 4c9c141..5f5dd8b 100644 --- a/Makefile +++ b/Makefile @@ -79,6 +79,17 @@ WOLFCRYPT_SRC := \ WOLFCRYPT_OBJ := $(patsubst $(WOLFSSL_PATH)/wolfcrypt/src/%.c,$(OBJDIR)/wolfcrypt_%.o,$(WOLFCRYPT_SRC)) WOLFCRYPT_OBJ_PIC := $(patsubst $(WOLFSSL_PATH)/wolfcrypt/src/%.c,$(OBJDIR_PIC)/wolfcrypt_%.o,$(WOLFCRYPT_SRC)) +# MBEDTLS_THREADING_C=1 builds a thread safe library, as in Mbed TLS. See +# user_settings.h. Applications define MBEDTLS_THREADING_C too. +ifeq ($(MBEDTLS_THREADING_C),1) +CPPFLAGS += -DMBEDTLS_THREADING_C +LDFLAGS += -pthread +endif + +# Changing MBEDTLS_THREADING_C rebuilds every object +OPTIONS_STAMP := $(BUILD_DIR)/options_$(if \ + $(filter 1,$(MBEDTLS_THREADING_C)),threads,nothreads) + WOLFSSL_CPPFLAGS ?= -DWOLFSSL_USER_SETTINGS CPPFLAGS += -I$(WOLFSSL_PATH) -I$(USER_SETTINGS_PATH) \ @@ -157,6 +168,13 @@ want-surface-test: $(LIBNAME) $(SHLIBNAME) -lpthread -lm $(BUILD_DIR)/psa_want_surface_test +$(OBJ) $(WOLFCRYPT_OBJ) $(OBJ_PIC) $(WOLFCRYPT_OBJ_PIC): $(OPTIONS_STAMP) + +$(OPTIONS_STAMP): + @mkdir -p $(@D) + rm -f $(BUILD_DIR)/options_* + touch $@ + $(OBJDIR)/%.o: src/%.c @mkdir -p $(OBJDIR) $(CC) $(CPPFLAGS) $(DEPFLAGS) $(CFLAGS) -c $< -o $@ @@ -233,7 +251,9 @@ UNIT_TESTS := psa_api_test \ psa_copy_key_cross_lifetime_test \ psa_zero_capacity_buffer_test \ psa_want_surface_test \ - psa_tf_psa_extra_test + psa_tf_psa_extra_test \ + psa_tf_psa_pk_test \ + psa_tf_psa_threading_test # Run the unit test loop from the repo root (psa_rsa_pss_interop_test reads # its certificate relative to the root). Assumes the tests are already built. diff --git a/README.md b/README.md index be6e887..77ccbe7 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ wolfPSA is a PSA Crypto **engine** that implements the PSA Crypto API on - wolfCrypt as the backend crypto provider. - The default make target creates a static library (`libwolfpsa.a`) built directly from wolfPSA sources plus the required wolfCrypt objects. +- TF-PSA-Crypto's `mbedtls/pk.h` for private keys: `mbedtls_pk_init`, `free`, + `parse_key`, `parse_keyfile` and `can_do_psa`. The context has the key's + DER and nothing else. RSA and EC keys, PEM or DER, traditional or PKCS #8. + Encrypted keys and EC keys with a compressed public key are not supported. ## Repository layout @@ -60,6 +64,18 @@ AddressSanitizer build: make ASAN=1 ``` +Thread-safe build, as Mbed TLS with `MBEDTLS_THREADING_C`: wolfCrypt without +`SINGLE_THREADED`, a mutex around the key store (`WOLFPSA_THREAD_SAFE`, see +`src/psa_lock.h`) and the TF-PSA-Crypto threading API in +`mbedtls/threading.h`, on pthreads: + +``` +make MBEDTLS_THREADING_C=1 +``` + +Applications define `MBEDTLS_THREADING_C` too, as in their Mbed TLS +configuration, and link with `-pthread`. + You can override the wolfSSL source location: ``` diff --git a/src/psa_tf_psa.c b/src/psa_tf_psa.c index f0fcbeb..cf23fa4 100644 --- a/src/psa_tf_psa.c +++ b/src/psa_tf_psa.c @@ -29,9 +29,16 @@ #if defined(WOLFSSL_PSA_ENGINE) +#include + #include +#include #include +#include #include +#include +#include +#include #include /* The generated crypto config must match the build's NO_FILESYSTEM */ @@ -51,4 +58,266 @@ void mbedtls_platform_zeroize(void *buf, size_t len) } } +void mbedtls_pk_init(mbedtls_pk_context* ctx) +{ + if (ctx != NULL) { + XMEMSET(ctx, 0, sizeof(*ctx)); + } +} + +void mbedtls_pk_free(mbedtls_pk_context* ctx) +{ + if (ctx != NULL) { + mbedtls_platform_zeroize(ctx->der, ctx->der_len); + XFREE(ctx->der, NULL, DYNAMIC_TYPE_PRIVATE_KEY); + XMEMSET(ctx, 0, sizeof(*ctx)); + } +} + +enum { + PK_TYPE_NONE = 0, + PK_TYPE_RSA = 1, + PK_TYPE_ECC = 2 +}; + +/* MEMORY_E becomes PSA_ERROR_INSUFFICIENT_MEMORY, other errors become err */ +static int PkRet(int wcRet, int err) +{ + if (wcRet == 0) { + return 0; + } + return (wcRet == WC_NO_ERR_TRACE(MEMORY_E)) ? + PSA_ERROR_INSUFFICIENT_MEMORY : err; +} + +/* An RSA or EC private key, traditional or PKCS #8. *type becomes PK_TYPE_* + * when it is not NULL. */ +static int PkCheckKey(const byte* der, word32 len, int* type) +{ + int ret = MBEDTLS_ERR_PK_KEY_INVALID_FORMAT; + int found = PK_TYPE_NONE; + word32 idx; +#ifndef NO_RSA + RsaKey* rsa; +#endif /* !NO_RSA */ +#ifdef HAVE_ECC + ecc_key* ecc; +#endif /* HAVE_ECC */ + +#ifndef NO_RSA + rsa = wc_NewRsaKey(NULL, INVALID_DEVID, NULL); + if (rsa == NULL) { + return PSA_ERROR_INSUFFICIENT_MEMORY; + } + idx = 0; + ret = PkRet(wc_RsaPrivateKeyDecode(der, &idx, rsa, len), + MBEDTLS_ERR_PK_KEY_INVALID_FORMAT); + if (ret == 0) { + found = PK_TYPE_RSA; + } + wc_DeleteRsaKey(rsa, &rsa); +#endif /* !NO_RSA */ +#ifdef HAVE_ECC + if (ret == MBEDTLS_ERR_PK_KEY_INVALID_FORMAT) { + ecc = wc_ecc_key_new(NULL); + if (ecc == NULL) { + return PSA_ERROR_INSUFFICIENT_MEMORY; + } + idx = 0; + ret = PkRet(wc_EccPrivateKeyDecode(der, &idx, ecc, len), + MBEDTLS_ERR_PK_KEY_INVALID_FORMAT); + if (ret == 0) { + found = PK_TYPE_ECC; + } + wc_ecc_key_free(ecc); + } +#endif /* HAVE_ECC */ + if (type != NULL) { + *type = found; + } + (void)der; + (void)len; + (void)idx; + return ret; +} + +/* PEM or DER. Encrypted keys are not supported, so pwd is not used. */ +int mbedtls_pk_parse_key(mbedtls_pk_context* ctx, const unsigned char* key, + size_t keylen, const unsigned char* pwd, size_t pwdlen) +{ + int ret; + + (void)pwd; + (void)pwdlen; + + if (ctx == NULL || ctx->der != NULL || key == NULL || + keylen > (size_t)INT_MAX) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (keylen == 0) { + return MBEDTLS_ERR_PK_KEY_INVALID_FORMAT; + } + + /* Big enough for the DER of a PEM key */ + ctx->der = (unsigned char*)XMALLOC(keylen, NULL, DYNAMIC_TYPE_PRIVATE_KEY); + if (ctx->der == NULL) { + return PSA_ERROR_INSUFFICIENT_MEMORY; + } +#ifdef WOLFSSL_PEM_TO_DER + ret = wc_KeyPemToDer(key, (int)keylen, ctx->der, (int)keylen, NULL); +#else + ret = WC_NO_ERR_TRACE(ASN_NO_PEM_HEADER); +#endif /* WOLFSSL_PEM_TO_DER */ + if (ret > 0) { + ctx->der_len = (size_t)ret; + ret = 0; + } + else if (ret == WC_NO_ERR_TRACE(ASN_NO_PEM_HEADER)) { + XMEMCPY(ctx->der, key, keylen); + ctx->der_len = keylen; + ret = 0; + } + else { + ret = PkRet(ret, MBEDTLS_ERR_PK_KEY_INVALID_FORMAT); + } + + if (ret == 0) { + ret = PkCheckKey(ctx->der, (word32)ctx->der_len, NULL); + } + if (ret != 0) { + mbedtls_pk_free(ctx); + } + return ret; +} + +/* The key pair signs and verifies; an RSA key also encrypts and decrypts */ +int mbedtls_pk_can_do_psa(const mbedtls_pk_context* pk, psa_algorithm_t alg, + psa_key_usage_t usage) +{ + const int sign = (usage == PSA_KEY_USAGE_SIGN_HASH || + usage == PSA_KEY_USAGE_VERIFY_HASH); + const int crypt = (usage == PSA_KEY_USAGE_ENCRYPT || + usage == PSA_KEY_USAGE_DECRYPT); + int type = PK_TYPE_NONE; + + if (pk == NULL || pk->der == NULL || + PkCheckKey(pk->der, (word32)pk->der_len, &type) != 0) { + return 0; + } + if (type == PK_TYPE_RSA) { + return (sign && (PSA_ALG_IS_RSA_PKCS1V15_SIGN(alg) || + PSA_ALG_IS_RSA_PSS(alg))) || + (crypt && (alg == PSA_ALG_RSA_PKCS1V15_CRYPT || + PSA_ALG_IS_RSA_OAEP(alg))); + } + return type == PK_TYPE_ECC && sign && PSA_ALG_IS_ECDSA(alg); +} + +#if defined(MBEDTLS_FS_IO) +int mbedtls_pk_parse_keyfile(mbedtls_pk_context* ctx, const char* path, + const char* password) +{ + byte* buf = NULL; + size_t len = 0; + int ret; + + ret = PkRet(wc_FileLoad(path, &buf, &len, NULL), + MBEDTLS_ERR_PK_FILE_IO_ERROR); + if (ret == 0) { + ret = mbedtls_pk_parse_key(ctx, buf, len, + (const unsigned char*)password, + (password == NULL) ? 0 : XSTRLEN(password)); + } + /* wc_FileLoad() can fail with buf allocated */ + mbedtls_platform_zeroize(buf, len); + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} +#endif /* MBEDTLS_FS_IO */ + +#if defined(MBEDTLS_THREADING_PTHREAD) +static int ThreadingRet(int posixRet) +{ + return (posixRet == 0) ? 0 : MBEDTLS_ERR_THREADING_USAGE_ERROR; +} + +void mbedtls_mutex_init(mbedtls_threading_mutex_t* mutex) +{ + if (mutex != NULL) { + mutex->initialized = + (char)(pthread_mutex_init(&mutex->mutex, NULL) == 0); + } +} + +/* Does nothing for a zeroed or freed mutex, as in TF-PSA-Crypto */ +void mbedtls_mutex_free(mbedtls_threading_mutex_t* mutex) +{ + if (mutex != NULL && mutex->initialized) { + (void)pthread_mutex_destroy(&mutex->mutex); + mutex->initialized = 0; + } +} + +int mbedtls_mutex_lock(mbedtls_threading_mutex_t* mutex) +{ + if (mutex == NULL || !mutex->initialized) { + return MBEDTLS_ERR_THREADING_USAGE_ERROR; + } + return ThreadingRet(pthread_mutex_lock(&mutex->mutex)); +} + +int mbedtls_mutex_unlock(mbedtls_threading_mutex_t* mutex) +{ + if (mutex == NULL || !mutex->initialized) { + return MBEDTLS_ERR_THREADING_USAGE_ERROR; + } + return ThreadingRet(pthread_mutex_unlock(&mutex->mutex)); +} + +int mbedtls_condition_variable_init( + mbedtls_threading_condition_variable_t* cond) +{ + if (cond == NULL) { + return MBEDTLS_ERR_THREADING_USAGE_ERROR; + } + return ThreadingRet(pthread_cond_init(&cond->cond, NULL)); +} + +void mbedtls_condition_variable_free( + mbedtls_threading_condition_variable_t* cond) +{ + if (cond != NULL) { + (void)pthread_cond_destroy(&cond->cond); + } +} + +int mbedtls_condition_variable_signal( + mbedtls_threading_condition_variable_t* cond) +{ + if (cond == NULL) { + return MBEDTLS_ERR_THREADING_USAGE_ERROR; + } + return ThreadingRet(pthread_cond_signal(&cond->cond)); +} + +int mbedtls_condition_variable_broadcast( + mbedtls_threading_condition_variable_t* cond) +{ + if (cond == NULL) { + return MBEDTLS_ERR_THREADING_USAGE_ERROR; + } + return ThreadingRet(pthread_cond_broadcast(&cond->cond)); +} + +int mbedtls_condition_variable_wait( + mbedtls_threading_condition_variable_t* cond, + mbedtls_threading_mutex_t* mutex) +{ + if (cond == NULL || mutex == NULL || !mutex->initialized) { + return MBEDTLS_ERR_THREADING_USAGE_ERROR; + } + return ThreadingRet(pthread_cond_wait(&cond->cond, &mutex->mutex)); +} +#endif /* MBEDTLS_THREADING_PTHREAD */ + #endif /* WOLFSSL_PSA_ENGINE */ diff --git a/test/Makefile b/test/Makefile index 717ea29..fcb795b 100644 --- a/test/Makefile +++ b/test/Makefile @@ -136,9 +136,13 @@ PSA_REGRESSION_TESTS = psa_aead_multipart_test \ BINARIES += $(PSA_REGRESSION_TESTS) # TF-PSA-Crypto extension tests, built with the default link recipe. -PSA_TF_PSA_TESTS = psa_tf_psa_extra_test +PSA_TF_PSA_TESTS = psa_tf_psa_extra_test psa_tf_psa_pk_test BINARIES += $(PSA_TF_PSA_TESTS) +# The pk test reads wolfSSL's test keys +psa_tf_psa_pk_test: \ + CPPFLAGS += -DPK_CERTS_DIR='"$(abspath $(WOLFSSL_PATH))/certs"' + # Regression test with a custom link: it replaces the store backend, so it # cannot use the recipe above (see PROBE_LIB below). BINARIES += psa_import_key_probe_test @@ -221,21 +225,27 @@ psa_tls_client: # user_settings.h with that knob enabled instead of the shared libwolfpsa. CRYPTOCB_BUILD_DIR := $(abspath .)/build-cryptocb$(if $(SANITIZE_FLAGS),-asan) CRYPTOCB_LIB := $(CRYPTOCB_BUILD_DIR)/libwolfpsa-cryptocb.a -CRYPTOCB_CPPFLAGS := -DWOLFSSL_USER_SETTINGS -DWOLF_CRYPTO_CB \ - -I$(abspath $(WOLFSSL_PATH)) -I$(WOLFPSA_ABS) -I$(WOLFPSA_ABS)/wolfpsa \ - -I$(WOLFPSA_ABS)/src -I$(CRYPTOCB_BUILD_DIR)/include +VARIANT_INC := -I$(abspath $(WOLFSSL_PATH)) -I$(WOLFPSA_ABS) \ + -I$(WOLFPSA_ABS)/wolfpsa -I$(WOLFPSA_ABS)/src +CRYPTOCB_CPPFLAGS := -DWOLFSSL_USER_SETTINGS -DWOLF_CRYPTO_CB $(VARIANT_INC) \ + -I$(CRYPTOCB_BUILD_DIR)/include # FORCE, because only the sub-make knows whether a library source changed. # Without it make treats the existing archive as permanently up to date and # the test relinks against a stale copy of the code it is meant to guard. $(CRYPTOCB_LIB): FORCE + $(call variant_lib,$(CRYPTOCB_BUILD_DIR),-DWOLF_CRYPTO_CB) + +# Build a library variant: $(1) is its build dir, $(2) its extra defines +define variant_lib $(MAKE) -C $(WOLFPSA_PATH) $@ \ LIBNAME=$@ \ - BUILD_DIR=$(CRYPTOCB_BUILD_DIR) \ + BUILD_DIR=$(1) \ WOLFSSL_PATH=$(abspath $(WOLFSSL_PATH)) \ - WOLFSSL_CPPFLAGS="-DWOLFSSL_USER_SETTINGS -DWOLF_CRYPTO_CB" \ + WOLFSSL_CPPFLAGS="-DWOLFSSL_USER_SETTINGS $(2)" \ CFLAGS="$(CFLAGS)" \ LDFLAGS="$(SANITIZE_FLAGS)" +endef FORCE: @@ -289,6 +299,21 @@ psa_store_commit_test: require-wolfssl-lib psa_server/psa_store_commit_test.o FO $(CC) $(CFLAGS) -o $@ psa_server/psa_store_commit_test.o \ $(WOLFPSA_PATH)/libwolfpsa.a $(LDFLAGS) -lwolfssl -lpthread -lm $(RPATH_WOLFSSL) +# The threading API exists only with MBEDTLS_THREADING_C, so its test has a +# library built with it +THREADS_BUILD_DIR := $(abspath .)/build-threads$(if $(SANITIZE_FLAGS),-asan) +THREADS_LIB := $(THREADS_BUILD_DIR)/libwolfpsa-threads.a +THREADS_CPPFLAGS := -DWOLFSSL_USER_SETTINGS -DMBEDTLS_THREADING_C \ + $(VARIANT_INC) -I$(THREADS_BUILD_DIR)/include + +$(THREADS_LIB): FORCE + $(call variant_lib,$(THREADS_BUILD_DIR),-DMBEDTLS_THREADING_C) + +psa_tf_psa_threading_test: psa_server/psa_tf_psa_threading_test.c \ + $(THREADS_LIB) FORCE + $(CC) $(THREADS_CPPFLAGS) $(CFLAGS) -o $@ $< $(THREADS_LIB) \ + $(SANITIZE_FLAGS) -pthread -lm + wolfcrypt-psa-benchmark: make -C wolfcrypt-benchmark cp wolfcrypt-benchmark/wolfcrypt-psa-benchmark ./wolfcrypt-psa-benchmark @@ -328,5 +353,7 @@ clean: rm -f psa_server/*.o rm -rf $(abspath .)/build-cryptocb $(abspath .)/build-cryptocb-asan rm -rf $(abspath .)/build-probe $(abspath .)/build-probe-asan + rm -rf $(abspath .)/build-threads $(abspath .)/build-threads-asan rm -f $(BINARIES) psa_devid_cryptocb_test psa_kdf_zeroize_output_test *.o *.log + rm -f psa_tf_psa_threading_test rm -f psa_server/*.gcda psa_server/*.gcno diff --git a/test/psa_server/psa_tf_psa_pk_test.c b/test/psa_server/psa_tf_psa_pk_test.c new file mode 100644 index 0000000..130e36f --- /dev/null +++ b/test/psa_server/psa_tf_psa_pk_test.c @@ -0,0 +1,149 @@ +/* psa_tf_psa_pk_test.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfPSA. + * + * wolfPSA is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPSA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* mbedtls/pk.h on wolfSSL's test keys. PK_CERTS_DIR is wolfSSL's certs/. */ + +#include + +#include +#include + +#define CERT(name) PK_CERTS_DIR "/" name +#define RSA_KEY CERT("server-key.pem") +#define EC_KEY CERT("ecc-key.pem") +#define INVALID MBEDTLS_ERR_PK_KEY_INVALID_FORMAT + +static const struct { + const char* file; + int ret; +} parseCases[] = { + { RSA_KEY, 0 }, + { CERT("server-key.der"), 0 }, + { CERT("server-keyPkcs8.pem"), 0 }, + { CERT("server-keyPkcs8.der"), 0 }, + { EC_KEY, 0 }, + { CERT("ecc-key.der"), 0 }, + { CERT("ecc-keyPkcs8.pem"), 0 }, + { CERT("ecc-keyPkcs8.der"), 0 }, + { CERT("ca-ecc384-key.pem"), 0 }, + { CERT("server-keyEnc.pem"), INVALID }, + { CERT("server-keyPkcs8Enc.pem"), INVALID }, + { CERT("server-cert.pem"), INVALID }, + { CERT("missing.key"), MBEDTLS_ERR_PK_FILE_IO_ERROR }, + /* On Linux, this file reports a larger size than it reads */ + { "/sys/devices/system/cpu/online", MBEDTLS_ERR_PK_FILE_IO_ERROR } +}; + +static const struct { + const char* file; + psa_algorithm_t alg; + psa_key_usage_t usage; + int can; +} canDoCases[] = { + { RSA_KEY, PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_SHA_256), + PSA_KEY_USAGE_SIGN_HASH, 1 }, + { RSA_KEY, PSA_ALG_RSA_PSS(PSA_ALG_SHA_256), PSA_KEY_USAGE_VERIFY_HASH, 1 }, + { RSA_KEY, PSA_ALG_RSA_OAEP(PSA_ALG_SHA_256), PSA_KEY_USAGE_DECRYPT, 1 }, + { RSA_KEY, PSA_ALG_RSA_PKCS1V15_CRYPT, PSA_KEY_USAGE_ENCRYPT, 1 }, + { RSA_KEY, PSA_ALG_RSA_PSS(PSA_ALG_SHA_256), PSA_KEY_USAGE_DECRYPT, 0 }, + { RSA_KEY, PSA_ALG_ECDSA(PSA_ALG_SHA_256), PSA_KEY_USAGE_SIGN_HASH, 0 }, + { EC_KEY, PSA_ALG_ECDSA(PSA_ALG_SHA_256), PSA_KEY_USAGE_SIGN_HASH, 1 }, + { EC_KEY, PSA_ALG_DETERMINISTIC_ECDSA(PSA_ALG_SHA_256), + PSA_KEY_USAGE_VERIFY_HASH, 1 }, + { EC_KEY, PSA_ALG_ECDSA(PSA_ALG_SHA_256), PSA_KEY_USAGE_ENCRYPT, 0 }, + { EC_KEY, PSA_ALG_RSA_PSS(PSA_ALG_SHA_256), PSA_KEY_USAGE_SIGN_HASH, 0 } +}; + +static int fails; + +static void Check(int ok, const char* name) +{ + printf("%s: %s\n", ok ? "PASS" : "FAIL", name); + if (!ok) { + fails++; + } +} + +static void Parse(void) +{ + mbedtls_pk_context pk; + size_t i; + + for (i = 0; i < sizeof(parseCases) / sizeof(parseCases[0]); i++) { + mbedtls_pk_init(&pk); + Check(mbedtls_pk_parse_keyfile(&pk, parseCases[i].file, NULL) == + parseCases[i].ret, parseCases[i].file); + mbedtls_pk_free(&pk); + } +} + +static void CanDo(void) +{ + mbedtls_pk_context pk; + char name[32]; + size_t i; + + for (i = 0; i < sizeof(canDoCases) / sizeof(canDoCases[0]); i++) { + (void)snprintf(name, sizeof(name), "can_do_psa case %u", + (unsigned)i); + mbedtls_pk_init(&pk); + Check(mbedtls_pk_parse_keyfile(&pk, canDoCases[i].file, NULL) == 0 && + mbedtls_pk_can_do_psa(&pk, canDoCases[i].alg, + canDoCases[i].usage) == canDoCases[i].can, name); + mbedtls_pk_free(&pk); + } +} + +/* An empty buffer, a second key into one context, NULL and empty contexts */ +static void BadInput(void) +{ + static const unsigned char der[] = { 0x30, 0x00 }; + mbedtls_pk_context pk; + + mbedtls_pk_init(&pk); + Check(mbedtls_pk_can_do_psa(&pk, PSA_ALG_ECDSA(PSA_ALG_SHA_256), + PSA_KEY_USAGE_SIGN_HASH) == 0, "can_do_psa without a key"); + Check(mbedtls_pk_can_do_psa(NULL, PSA_ALG_ECDSA(PSA_ALG_SHA_256), + PSA_KEY_USAGE_SIGN_HASH) == 0, "can_do_psa NULL"); + Check(mbedtls_pk_parse_key(NULL, der, sizeof(der), NULL, 0) == + PSA_ERROR_INVALID_ARGUMENT, "parse_key NULL"); + Check(mbedtls_pk_parse_key(&pk, der, 0, NULL, 0) == INVALID, + "parse_key empty"); + Check(mbedtls_pk_parse_keyfile(&pk, EC_KEY, NULL) == 0 && + mbedtls_pk_parse_keyfile(&pk, RSA_KEY, NULL) == + PSA_ERROR_INVALID_ARGUMENT, "parse_key second key"); + mbedtls_pk_free(&pk); + mbedtls_pk_free(&pk); + mbedtls_pk_free(NULL); +} + +int main(void) +{ + if (psa_crypto_init() != PSA_SUCCESS) { + printf("FAIL: psa_crypto_init\n"); + return 1; + } + Parse(); + CanDo(); + BadInput(); + mbedtls_psa_crypto_free(); + return (fails == 0) ? 0 : 1; +} diff --git a/test/psa_server/psa_tf_psa_threading_test.c b/test/psa_server/psa_tf_psa_threading_test.c new file mode 100644 index 0000000..757d902 --- /dev/null +++ b/test/psa_server/psa_tf_psa_threading_test.c @@ -0,0 +1,228 @@ +/* psa_tf_psa_threading_test.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfPSA. + * + * wolfPSA is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPSA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Mutexes and condition variables through the TF-PSA-Crypto 1.x API, and PSA + * calls from several threads at once, as MBEDTLS_THREADING_C promises. */ + +#include +#include + +#include +#include + +#define THREADS 4 +#define ROUNDS 10000 +#define KEYS 100 + +static mbedtls_threading_mutex_t lock; +static mbedtls_threading_condition_variable_t ready; +static mbedtls_threading_condition_variable_t start; +static int counter; +static int waiting; +static int go; +/* A thread returns its address on failure */ +static int failed; + +static int StartThreads(pthread_t* t, void* (*f)(void*)) +{ + int n; + + for (n = 0; n < THREADS; n++) { + if (pthread_create(&t[n], NULL, f, NULL) != 0) { + break; + } + } + return n; +} + +/* All n threads started and none failed */ +static int JoinThreads(pthread_t* t, int n) +{ + void* ret; + int ok = (n == THREADS); + int i; + + for (i = 0; i < n; i++) { + if (pthread_join(t[i], &ret) != 0 || ret != NULL) { + ok = 0; + } + } + return ok; +} + +/* A zeroed or freed mutex fails, and a second free does nothing */ +static int MutexUsage(void) +{ + mbedtls_threading_mutex_t m; + int ok; + + memset(&m, 0, sizeof(m)); + ok = mbedtls_mutex_lock(&m) == MBEDTLS_ERR_THREADING_USAGE_ERROR; + mbedtls_mutex_free(&m); + mbedtls_mutex_init(&m); + ok = ok && mbedtls_mutex_lock(&m) == 0 && mbedtls_mutex_unlock(&m) == 0; + mbedtls_mutex_free(&m); + mbedtls_mutex_free(&m); + return ok && + mbedtls_mutex_unlock(&m) == MBEDTLS_ERR_THREADING_USAGE_ERROR; +} + +static void* Count(void* arg) +{ + int i; + + (void)arg; + for (i = 0; i < ROUNDS; i++) { + if (mbedtls_mutex_lock(&lock) != 0) { + return &failed; + } + counter++; + if (mbedtls_mutex_unlock(&lock) != 0) { + return &failed; + } + } + return NULL; +} + +static int Counter(void) +{ + pthread_t t[THREADS]; + + return JoinThreads(t, StartThreads(t, Count)) && + counter == THREADS * ROUNDS; +} + +/* Signals ready, then waits for the broadcast of start */ +static void* Wait(void* arg) +{ + int ret; + + (void)arg; + ret = mbedtls_mutex_lock(&lock); + if (ret == 0) { + waiting++; + ret = mbedtls_condition_variable_signal(&ready); + while (ret == 0 && !go) { + ret = mbedtls_condition_variable_wait(&start, &lock); + } + (void)mbedtls_mutex_unlock(&lock); + } + return (ret == 0) ? NULL : &failed; +} + +static int CondVar(void) +{ + pthread_t t[THREADS]; + int n; + int ret; + + n = StartThreads(t, Wait); + ret = mbedtls_mutex_lock(&lock); + if (ret == 0) { + while (ret == 0 && waiting < n) { + ret = mbedtls_condition_variable_wait(&ready, &lock); + } + go = 1; + if (ret == 0) { + ret = mbedtls_condition_variable_broadcast(&start); + } + (void)mbedtls_mutex_unlock(&lock); + } + return JoinThreads(t, n) && ret == 0; +} + +/* Volatile keys in the shared key store */ +static void* Keys(void* arg) +{ + static const unsigned char key[16] = { 0 }; + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + psa_key_attributes_t got = PSA_KEY_ATTRIBUTES_INIT; + psa_key_id_t id; + psa_status_t status = PSA_SUCCESS; + int i; + + (void)arg; + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_ENCRYPT); + psa_set_key_algorithm(&attr, PSA_ALG_ECB_NO_PADDING); + psa_set_key_type(&attr, PSA_KEY_TYPE_AES); + for (i = 0; i < KEYS && status == PSA_SUCCESS; i++) { + status = psa_import_key(&attr, key, sizeof(key), &id); + if (status == PSA_SUCCESS) { + status = psa_get_key_attributes(id, &got); + if (status == PSA_SUCCESS && + psa_get_key_type(&got) != PSA_KEY_TYPE_AES) { + status = PSA_ERROR_CORRUPTION_DETECTED; + } + psa_reset_key_attributes(&got); + if (psa_destroy_key(id) != PSA_SUCCESS) { + status = PSA_ERROR_CORRUPTION_DETECTED; + } + } + } + return (status == PSA_SUCCESS) ? NULL : &failed; +} + +static int KeyStore(void) +{ + pthread_t t[THREADS]; + + return JoinThreads(t, StartThreads(t, Keys)); +} + +int main(void) +{ + static const struct { + const char* name; + int (*f)(void); + } cases[] = { + { "mutex usage errors", MutexUsage }, + { "mutex, a counter shared by the threads", Counter }, + { "condition variable, signal and broadcast", CondVar }, + { "PSA keys in each thread", KeyStore } + }; + size_t i; + int fails = 0; + + if (psa_crypto_init() != PSA_SUCCESS) { + printf("FAIL: psa_crypto_init\n"); + return 1; + } + mbedtls_mutex_init(&lock); + if (mbedtls_condition_variable_init(&ready) != 0 || + mbedtls_condition_variable_init(&start) != 0) { + printf("FAIL: mbedtls_condition_variable_init\n"); + return 1; + } + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + if (cases[i].f()) { + printf("PASS: %s\n", cases[i].name); + } + else { + printf("FAIL: %s\n", cases[i].name); + fails++; + } + } + mbedtls_condition_variable_free(&start); + mbedtls_condition_variable_free(&ready); + mbedtls_mutex_free(&lock); + mbedtls_psa_crypto_free(); + return (fails == 0) ? 0 : 1; +} diff --git a/user_settings.h b/user_settings.h index 5bef3d8..4d0c843 100644 --- a/user_settings.h +++ b/user_settings.h @@ -35,7 +35,13 @@ #else #define WOLFCRYPT_ONLY +/* MBEDTLS_THREADING_C: thread safe, see src/psa_lock.h */ +#if defined(MBEDTLS_THREADING_C) && !defined(WOLFPSA_THREAD_SAFE) +#define WOLFPSA_THREAD_SAFE +#endif /* MBEDTLS_THREADING_C && !WOLFPSA_THREAD_SAFE */ +#ifndef WOLFPSA_THREAD_SAFE #define SINGLE_THREADED +#endif /* WOLFPSA_THREAD_SAFE */ #define WOLFSSL_PSA_ENGINE #define WOLFSSL_SP_MATH_ALL #define WOLFSSL_HAVE_SP_RSA diff --git a/wolfpsa.map b/wolfpsa.map index 8833f8d..88eed26 100644 --- a/wolfpsa.map +++ b/wolfpsa.map @@ -1,5 +1,12 @@ WOLFPSA_1.0 { global: + mbedtls_condition_variable_*; + mbedtls_mutex_*; + mbedtls_pk_can_do_psa; + mbedtls_pk_free; + mbedtls_pk_init; + mbedtls_pk_parse_key; + mbedtls_pk_parse_keyfile; mbedtls_platform_zeroize; mbedtls_psa_crypto_free; psa_aead_abort; diff --git a/wolfpsa/mbedtls/pk.h b/wolfpsa/mbedtls/pk.h new file mode 100644 index 0000000..7cdb50f --- /dev/null +++ b/wolfpsa/mbedtls/pk.h @@ -0,0 +1,75 @@ +/* pk.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfPSA. + * + * wolfPSA is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPSA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef MBEDTLS_PK_H +#define MBEDTLS_PK_H + +#include "tf-psa-crypto/build_info.h" +#include "psa/crypto.h" + +#include + +#ifdef __cplusplus + extern "C" { +#endif /* __cplusplus */ + +/* TF-PSA-Crypto returns PSA_ERROR_INVALID_ARGUMENT and + * PSA_ERROR_INSUFFICIENT_MEMORY for bad input and allocation failures */ +#define MBEDTLS_ERR_PK_TYPE_MISMATCH -0x3F00 +#define MBEDTLS_ERR_PK_FILE_IO_ERROR -0x3E00 +#define MBEDTLS_ERR_PK_KEY_INVALID_VERSION -0x3D80 +#define MBEDTLS_ERR_PK_KEY_INVALID_FORMAT -0x3D00 +#define MBEDTLS_ERR_PK_UNKNOWN_PK_ALG -0x3C80 +#define MBEDTLS_ERR_PK_PASSWORD_REQUIRED -0x3C00 +#define MBEDTLS_ERR_PK_PASSWORD_MISMATCH -0x3B80 +#define MBEDTLS_ERR_PK_INVALID_PUBKEY -0x3B00 +#define MBEDTLS_ERR_PK_INVALID_ALG -0x3A80 +#define MBEDTLS_ERR_PK_UNKNOWN_NAMED_CURVE -0x3A00 +#define MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE -0x3980 + +#if defined(PSA_WANT_ALG_DETERMINISTIC_ECDSA) + #define MBEDTLS_PK_ALG_ECDSA(hash_alg) PSA_ALG_DETERMINISTIC_ECDSA(hash_alg) +#else + #define MBEDTLS_PK_ALG_ECDSA(hash_alg) PSA_ALG_ECDSA(hash_alg) +#endif /* PSA_WANT_ALG_DETERMINISTIC_ECDSA */ + +/* An RSA or EC private key, DER */ +typedef struct mbedtls_pk_context { + unsigned char* der; + size_t der_len; +} mbedtls_pk_context; + +void mbedtls_pk_init(mbedtls_pk_context* ctx); +void mbedtls_pk_free(mbedtls_pk_context* ctx); +int mbedtls_pk_parse_key(mbedtls_pk_context* ctx, const unsigned char* key, + size_t keylen, const unsigned char* pwd, size_t pwdlen); +int mbedtls_pk_can_do_psa(const mbedtls_pk_context* pk, psa_algorithm_t alg, + psa_key_usage_t usage); +#if defined(MBEDTLS_FS_IO) +int mbedtls_pk_parse_keyfile(mbedtls_pk_context* ctx, const char* path, + const char* password); +#endif /* MBEDTLS_FS_IO */ + +#ifdef __cplusplus + } /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* MBEDTLS_PK_H */ diff --git a/wolfpsa/mbedtls/threading.h b/wolfpsa/mbedtls/threading.h new file mode 100644 index 0000000..6815411 --- /dev/null +++ b/wolfpsa/mbedtls/threading.h @@ -0,0 +1,82 @@ +/* threading.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfPSA. + * + * wolfPSA is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfPSA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* TF-PSA-Crypto 1.x mutexes and condition variables, on pthreads. Built with + * MBEDTLS_THREADING_C=1. */ + +#ifndef MBEDTLS_THREADING_H +#define MBEDTLS_THREADING_H + +#include "tf-psa-crypto/build_info.h" + +#if defined(MBEDTLS_THREADING_PTHREAD) + #include +#endif /* MBEDTLS_THREADING_PTHREAD */ + +#ifdef __cplusplus + extern "C" { +#endif /* __cplusplus */ + +#define MBEDTLS_ERR_THREADING_USAGE_ERROR -0x001E +#define MBEDTLS_ERR_THREADING_MUTEX_ERROR MBEDTLS_ERR_THREADING_USAGE_ERROR + +#if defined(MBEDTLS_THREADING_C) + +#if defined(MBEDTLS_THREADING_PTHREAD) +typedef pthread_mutex_t mbedtls_platform_mutex_t; +typedef pthread_cond_t mbedtls_platform_condition_variable_t; +#else + #error "MBEDTLS_THREADING_C is supported with pthreads only" +#endif /* MBEDTLS_THREADING_PTHREAD */ + +typedef struct mbedtls_threading_mutex_t { + mbedtls_platform_mutex_t mutex; + char initialized; +} mbedtls_threading_mutex_t; + +typedef struct mbedtls_threading_condition_variable_t { + mbedtls_platform_condition_variable_t cond; +} mbedtls_threading_condition_variable_t; + +void mbedtls_mutex_init(mbedtls_threading_mutex_t* mutex); +void mbedtls_mutex_free(mbedtls_threading_mutex_t* mutex); +int mbedtls_mutex_lock(mbedtls_threading_mutex_t* mutex); +int mbedtls_mutex_unlock(mbedtls_threading_mutex_t* mutex); + +int mbedtls_condition_variable_init( + mbedtls_threading_condition_variable_t* cond); +void mbedtls_condition_variable_free( + mbedtls_threading_condition_variable_t* cond); +int mbedtls_condition_variable_signal( + mbedtls_threading_condition_variable_t* cond); +int mbedtls_condition_variable_broadcast( + mbedtls_threading_condition_variable_t* cond); +int mbedtls_condition_variable_wait( + mbedtls_threading_condition_variable_t* cond, + mbedtls_threading_mutex_t* mutex); + +#endif /* MBEDTLS_THREADING_C */ + +#ifdef __cplusplus + } /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* MBEDTLS_THREADING_H */ diff --git a/wolfpsa/tf-psa-crypto/build_info.h b/wolfpsa/tf-psa-crypto/build_info.h index f70d357..2f01839 100644 --- a/wolfpsa/tf-psa-crypto/build_info.h +++ b/wolfpsa/tf-psa-crypto/build_info.h @@ -31,6 +31,9 @@ #define MBEDTLS_PSA_CRYPTO_C #define MBEDTLS_PSA_CRYPTO_CLIENT /* MBEDTLS_ENTROPY_C and MBEDTLS_CTR_DRBG_C come from crypto_config.h */ +/* mbedtls/pk.h: private keys, PEM or DER */ +#define MBEDTLS_PK_C +#define MBEDTLS_PK_PARSE_C #if defined(PSA_WANT_KEY_TYPE_AES) #define MBEDTLS_AES_C @@ -54,4 +57,10 @@ #define MBEDTLS_CIPHER_MODE_CTR #endif /* PSA_WANT_ALG_CTR */ +/* pthreads, the one implementation of MBEDTLS_THREADING_C */ +#if defined(MBEDTLS_THREADING_C) && !defined(MBEDTLS_THREADING_PTHREAD) && \ + !defined(__ZEPHYR__) + #define MBEDTLS_THREADING_PTHREAD +#endif /* MBEDTLS_THREADING_C && !MBEDTLS_THREADING_PTHREAD && !__ZEPHYR__ */ + #endif /* TF_PSA_CRYPTO_BUILD_INFO_H */