From 4e35d840889a9142dbde1c95db335991dcafc5c8 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 11:27:06 +0200 Subject: [PATCH 01/24] F-3860: add direction-mismatch tests for psa_aead_finish and psa_aead_verify Test that psa_aead_finish on a decrypt context and psa_aead_verify on an encrypt context both return PSA_ERROR_BAD_STATE, covering the direction enforcement checks that had zero test coverage. --- test/psa_server/psa_api_test.c | 68 ++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/test/psa_server/psa_api_test.c b/test/psa_server/psa_api_test.c index 41a95ae..cb5088a 100644 --- a/test/psa_server/psa_api_test.c +++ b/test/psa_server/psa_api_test.c @@ -3486,6 +3486,68 @@ static int test_aead_requires_decrypt_usage(void) return ret; } +/* F-3860: psa_aead_finish on a decrypt context and psa_aead_verify on an + * encrypt context must both return PSA_ERROR_BAD_STATE. */ +static int test_aead_direction_mismatch_rejected(void) +{ + static const uint8_t key[16] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + static const uint8_t tag[16] = { 0 }; + uint8_t buf[16]; + size_t len; + psa_key_id_t key_id = 0; + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_aead_operation_t op = psa_aead_operation_init(); + psa_status_t st; + int ret = TEST_FAIL; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attrs, 128); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&attrs, PSA_ALG_GCM); + + st = psa_import_key(&attrs, key, sizeof(key), &key_id); + if (check_status(st, "psa_import_key(direction mismatch)") != TEST_OK) { + goto cleanup; + } + + /* decrypt_setup + psa_aead_finish must be rejected */ + st = psa_aead_decrypt_setup(&op, key_id, PSA_ALG_GCM); + if (check_status(st, "psa_aead_decrypt_setup(direction mismatch)") != TEST_OK) { + goto cleanup; + } + st = psa_aead_finish(&op, buf, sizeof(buf), &len, buf, sizeof(buf), &len); + if (check_true(st == PSA_ERROR_BAD_STATE, + "psa_aead_finish on decrypt context rejected") != TEST_OK) { + goto cleanup; + } + (void)psa_aead_abort(&op); + + /* encrypt_setup + psa_aead_verify must be rejected */ + op = psa_aead_operation_init(); + st = psa_aead_encrypt_setup(&op, key_id, PSA_ALG_GCM); + if (check_status(st, "psa_aead_encrypt_setup(direction mismatch)") != TEST_OK) { + goto cleanup; + } + st = psa_aead_verify(&op, buf, sizeof(buf), &len, tag, sizeof(tag)); + if (check_true(st == PSA_ERROR_BAD_STATE, + "psa_aead_verify on encrypt context rejected") != TEST_OK) { + goto cleanup; + } + (void)psa_aead_abort(&op); + + ret = TEST_OK; + +cleanup: + (void)psa_aead_abort(&op); + if (key_id != 0) { + (void)psa_destroy_key(key_id); + } + return ret; +} + static int test_aead_gcm_rejects_short_tags(void) { static const uint8_t key[16] = { @@ -8094,6 +8156,12 @@ int main(int argc, char** argv) return TEST_FAIL; } } + if (only == NULL || strcmp(only, "aead_direction_mismatch") == 0) { + if (run_named_test("aead_direction_mismatch", + test_aead_direction_mismatch_rejected) == TEST_FAIL) { + return TEST_FAIL; + } + } if (only == NULL || strcmp(only, "aead_gcm_short_tag") == 0) { if (run_named_test("aead_gcm_short_tag", test_aead_gcm_rejects_short_tags) == TEST_FAIL) { From 6c8db186c546a3d3133fb68305eae132ddc02302 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 11:29:44 +0200 Subject: [PATCH 02/24] F-3859: add test_hash_compare covering success, mismatch, wrong-length, and non-hash-alg paths The ConstantCompare mismatch-detection branch was reachable only by calling psa_hash_compare before psa_crypto_init (PSA_ERROR_BAD_STATE) or with a NULL reference hash (PSA_ERROR_INVALID_ARGUMENT). The success path and the critical INVALID_SIGNATURE path were entirely untested, so a mutation of the ConstantCompare check or the hash_length guard would survive undetected. --- test/psa_server/psa_api_test.c | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test/psa_server/psa_api_test.c b/test/psa_server/psa_api_test.c index cb5088a..1eb30db 100644 --- a/test/psa_server/psa_api_test.c +++ b/test/psa_server/psa_api_test.c @@ -273,6 +273,48 @@ static int test_hash(void) return TEST_OK; } +static int test_hash_compare(void) +{ + static const uint8_t msg[] = "abc"; + /* SHA-256("abc") */ + static const uint8_t sha256_abc[WC_SHA256_DIGEST_SIZE] = { + 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, + 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, + 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, + 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad, + }; + uint8_t bad_hash[WC_SHA256_DIGEST_SIZE]; + psa_status_t st; + + /* Success path: correct digest must return PSA_SUCCESS. */ + st = psa_hash_compare(PSA_ALG_SHA_256, msg, sizeof(msg) - 1, + sha256_abc, sizeof(sha256_abc)); + if (check_status(st, "psa_hash_compare correct") != TEST_OK) return TEST_FAIL; + + /* Mismatch path: one byte flipped must return PSA_ERROR_INVALID_SIGNATURE. + * Catches mutation of the ConstantCompare check. */ + memcpy(bad_hash, sha256_abc, sizeof(bad_hash)); + bad_hash[0] ^= 0x01; + st = psa_hash_compare(PSA_ALG_SHA_256, msg, sizeof(msg) - 1, + bad_hash, sizeof(bad_hash)); + if (check_true(st == PSA_ERROR_INVALID_SIGNATURE, + "psa_hash_compare mismatch") != TEST_OK) return TEST_FAIL; + + /* Wrong reference length must return PSA_ERROR_INVALID_SIGNATURE. */ + st = psa_hash_compare(PSA_ALG_SHA_256, msg, sizeof(msg) - 1, + sha256_abc, sizeof(sha256_abc) - 1); + if (check_true(st == PSA_ERROR_INVALID_SIGNATURE, + "psa_hash_compare wrong length") != TEST_OK) return TEST_FAIL; + + /* Non-hash algorithm must return PSA_ERROR_INVALID_ARGUMENT. */ + st = psa_hash_compare(PSA_ALG_HMAC(PSA_ALG_SHA_256), msg, sizeof(msg) - 1, + sha256_abc, sizeof(sha256_abc)); + if (check_true(st == PSA_ERROR_INVALID_ARGUMENT, + "psa_hash_compare non-hash alg") != TEST_OK) return TEST_FAIL; + + return TEST_OK; +} + static int check_status_or_skip(psa_status_t st, const char* what) { if (st == PSA_ERROR_NOT_SUPPORTED) { @@ -7956,6 +7998,9 @@ int main(int argc, char** argv) if (only == NULL || strcmp(only, "hash") == 0) { if (run_named_test("hash", test_hash) == TEST_FAIL) return TEST_FAIL; } + if (only == NULL || strcmp(only, "hash_compare") == 0) { + if (run_named_test("hash_compare", test_hash_compare) == TEST_FAIL) return TEST_FAIL; + } if (only == NULL || strcmp(only, "hash_error_state") == 0) { if (run_named_test("hash_error_state", test_hash_error_aborts_operation) == TEST_FAIL) { From 046f3d08b3ad6f4135fad46a346766df744eb1b1 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 11:39:46 +0200 Subject: [PATCH 03/24] F-3660: fix algorithm confusion in psa_xchacha20_poly1305_encrypt/decrypt Both xchacha20 helpers checked alg != PSA_ALG_CHACHA20_POLY1305 (passing IETF ChaCha20), but then invoked wc_XChaCha20Poly1305_Encrypt/Decrypt and required a 24-byte XChaCha20 nonce. Invert the check to reject PSA_ALG_CHACHA20_POLY1305 so the standard IETF algorithm identifier cannot route to the XChaCha20 primitive. --- src/psa_chacha20_poly1305.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/psa_chacha20_poly1305.c b/src/psa_chacha20_poly1305.c index 835e872..5b83de5 100644 --- a/src/psa_chacha20_poly1305.c +++ b/src/psa_chacha20_poly1305.c @@ -224,24 +224,24 @@ psa_status_t psa_xchacha20_poly1305_encrypt( return PSA_ERROR_INVALID_ARGUMENT; } - if (alg != PSA_ALG_CHACHA20_POLY1305) { + if (alg == PSA_ALG_CHACHA20_POLY1305) { return PSA_ERROR_NOT_SUPPORTED; } - + if (nonce == NULL || nonce_length != XCHACHA20_POLY1305_AEAD_NONCE_SIZE) { return PSA_ERROR_INVALID_ARGUMENT; } - + /* Additional data can be NULL if length is 0 */ if (additional_data == NULL && additional_data_length > 0) { return PSA_ERROR_INVALID_ARGUMENT; } - + /* Plaintext can be NULL if length is 0 */ if (plaintext == NULL && plaintext_length > 0) { return PSA_ERROR_INVALID_ARGUMENT; } - + if (plaintext_length > SIZE_MAX - CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE) { return PSA_ERROR_BUFFER_TOO_SMALL; } @@ -251,7 +251,7 @@ psa_status_t psa_xchacha20_poly1305_encrypt( if (ciphertext_size < required_size) { return PSA_ERROR_BUFFER_TOO_SMALL; } - + /* Encrypt using XChaCha20-Poly1305 */ ret = wc_XChaCha20Poly1305_Encrypt( ciphertext, ciphertext_size, @@ -287,19 +287,19 @@ psa_status_t psa_xchacha20_poly1305_decrypt( return PSA_ERROR_INVALID_ARGUMENT; } - if (alg != PSA_ALG_CHACHA20_POLY1305) { + if (alg == PSA_ALG_CHACHA20_POLY1305) { return PSA_ERROR_NOT_SUPPORTED; } - + if (nonce == NULL || nonce_length != XCHACHA20_POLY1305_AEAD_NONCE_SIZE) { return PSA_ERROR_INVALID_ARGUMENT; } - + /* Additional data can be NULL if length is 0 */ if (additional_data == NULL && additional_data_length > 0) { return PSA_ERROR_INVALID_ARGUMENT; } - + /* Ciphertext must include authentication tag */ if (ciphertext_length < CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE) { return PSA_ERROR_INVALID_ARGUMENT; From 19b51ca8431335711558772b50520956a51f8506 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 11:42:57 +0200 Subject: [PATCH 04/24] F-3658: zeroize padded plaintext block in CBC-PKCS7 encrypt path Mirror the decrypt branch's wc_ForceZero pattern: introduce a cbc_pkcs7_encrypt_done label so the stack buffer holding the last partial plaintext block plus PKCS7 padding is always scrubbed before the function returns, on all three exit paths (NOT_SUPPORTED, wolfCrypt error, and success). --- src/psa_cipher.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/psa_cipher.c b/src/psa_cipher.c index 25e210a..39dea16 100644 --- a/src/psa_cipher.c +++ b/src/psa_cipher.c @@ -1323,6 +1323,7 @@ psa_status_t psa_cipher_finish(psa_cipher_operation_t *operation, if (ctx->direction == AES_ENCRYPTION) { uint8_t block[AES_BLOCK_SIZE]; size_t pad_len = block_size - ctx->partial_len; + psa_status_t status = PSA_SUCCESS; if (pad_len == 0) { pad_len = block_size; @@ -1340,7 +1341,8 @@ psa_status_t psa_cipher_finish(psa_cipher_operation_t *operation, ret = wc_Des3_CbcEncrypt(&ctx->des3, output, block, (word32)block_size); #else - return wolfpsa_cipher_fail(operation, PSA_ERROR_NOT_SUPPORTED); + status = PSA_ERROR_NOT_SUPPORTED; + goto cbc_pkcs7_encrypt_done; #endif } else { @@ -1348,10 +1350,17 @@ psa_status_t psa_cipher_finish(psa_cipher_operation_t *operation, (word32)block_size); } if (ret != 0) { - return wolfpsa_cipher_fail(operation, wc_error_to_psa_status(ret)); + status = wc_error_to_psa_status(ret); + goto cbc_pkcs7_encrypt_done; } ctx->partial_len = 0; *output_length = block_size; + +cbc_pkcs7_encrypt_done: + wc_ForceZero(block, sizeof(block)); + if (status != PSA_SUCCESS) { + return wolfpsa_cipher_fail(operation, status); + } psa_cipher_abort(operation); return PSA_SUCCESS; } From f8c4f11d97a8ab1ab381a9f517f9d7d0d2aee7d5 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 11:46:59 +0200 Subject: [PATCH 05/24] F-3653: check ciphertext buffer size before psa_aead_finish to avoid modifying output on BUFFER_TOO_SMALL The ciphertext size check (ciphertext_size < out_len + tag_length) ran after psa_aead_finish had already written plaintext_length bytes of ciphertext into the caller's buffer, violating the PSA Crypto API requirement that BUFFER_TOO_SMALL must not modify output buffers. Add an early check using wolfpsa_aead_tag_length before calling psa_aead_finish, using overflow-safe subtraction. The post-finish check is kept as a defence-in-depth assertion. --- src/psa_aead.c | 9 +++++ test/psa_server/psa_api_test.c | 64 ++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/psa_aead.c b/src/psa_aead.c index 408d47c..0e82d32 100644 --- a/src/psa_aead.c +++ b/src/psa_aead.c @@ -888,6 +888,15 @@ psa_status_t psa_aead_encrypt(psa_key_id_t key, return status; } + { + size_t needed_tag_len = wolfpsa_aead_tag_length(alg); + if (needed_tag_len > ciphertext_size || + ciphertext_size - needed_tag_len < plaintext_length) { + psa_aead_abort(&operation); + return PSA_ERROR_BUFFER_TOO_SMALL; + } + } + status = psa_aead_finish(&operation, ciphertext, ciphertext_size, &out_len, tag, sizeof(tag), &tag_length); if (status != PSA_SUCCESS) { diff --git a/test/psa_server/psa_api_test.c b/test/psa_server/psa_api_test.c index 1eb30db..90d3f34 100644 --- a/test/psa_server/psa_api_test.c +++ b/test/psa_server/psa_api_test.c @@ -2679,6 +2679,64 @@ static int aead_corrupt_tag_multipart(psa_key_id_t key_id, psa_algorithm_t alg, return ret; } +/* F-3653: psa_aead_encrypt must return BUFFER_TOO_SMALL without modifying the + * output buffer when ciphertext_size < plaintext_length + tag_length. */ +static int test_aead_encrypt_buffer_too_small(void) +{ + static const uint8_t key[16] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + static const uint8_t nonce[12] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 + }; + static const uint8_t plaintext[16] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + /* Exactly plaintext_length — no room for the 16-byte GCM tag. */ + uint8_t out[sizeof(plaintext)]; + size_t out_len = 0; + psa_key_id_t key_id = 0; + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_status_t st; + int result = TEST_FAIL; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attrs, 128); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_ENCRYPT); + psa_set_key_algorithm(&attrs, PSA_ALG_GCM); + + st = psa_import_key(&attrs, key, sizeof(key), &key_id); + if (check_status(st, "psa_import_key") != TEST_OK) goto cleanup; + + XMEMSET(out, 0, sizeof(out)); + + st = psa_aead_encrypt(key_id, PSA_ALG_GCM, + nonce, sizeof(nonce), + NULL, 0, + plaintext, sizeof(plaintext), + out, sizeof(out), &out_len); + if (check_true(st == PSA_ERROR_BUFFER_TOO_SMALL, + "psa_aead_encrypt with short buffer returns BUFFER_TOO_SMALL") + != TEST_OK) goto cleanup; + + /* Output buffer must not have been modified on BUFFER_TOO_SMALL. */ + { + uint8_t zeros[sizeof(out)]; + XMEMSET(zeros, 0, sizeof(zeros)); + if (check_buf_eq("psa_aead_encrypt short-buf output unmodified", + out, zeros, sizeof(zeros)) != TEST_OK) goto cleanup; + } + + result = TEST_OK; +cleanup: + if (key_id != 0) + (void)psa_destroy_key(key_id); + return result; +} + static int test_aead_rejects_corrupted_tag(void) { static const uint8_t aes_key[16] = { @@ -8153,6 +8211,12 @@ int main(int argc, char** argv) if (only == NULL || strcmp(only, "aead_gcm") == 0) { if (run_named_test("aead_gcm", test_aead_gcm) == TEST_FAIL) return TEST_FAIL; } + if (only == NULL || strcmp(only, "aead_encrypt_buffer_too_small") == 0) { + if (run_named_test("aead_encrypt_buffer_too_small", + test_aead_encrypt_buffer_too_small) == TEST_FAIL) { + return TEST_FAIL; + } + } if (only == NULL || strcmp(only, "aead_rejects_corrupted_tag") == 0) { if (run_named_test("aead_rejects_corrupted_tag", test_aead_rejects_corrupted_tag) == TEST_FAIL) { From 38d943ff6f78f037d7e6e2393e97eb7b84b99be3 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 11:50:07 +0200 Subject: [PATCH 06/24] F-4561: check output buffer size before psa_cipher_generate_iv in psa_cipher_encrypt iv_len is deterministic from wolfpsa_cipher_iv_length before any side-effectful call, so the output_size < iv_len check was needlessly ordered after psa_cipher_generate_iv, which consumed entropy and mutated operation state even when the caller's buffer was too small. Move the check ahead of the generate call so BUFFER_TOO_SMALL is returned without drawing from the RNG. --- src/psa_cipher.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/psa_cipher.c b/src/psa_cipher.c index 39dea16..6830c50 100644 --- a/src/psa_cipher.c +++ b/src/psa_cipher.c @@ -1492,15 +1492,15 @@ psa_status_t psa_cipher_encrypt(psa_key_id_t key, iv_len = wolfpsa_cipher_iv_length(alg, ctx->key_type); if (iv_len != 0) { + if (output_size < iv_len) { + psa_cipher_abort(&operation); + return PSA_ERROR_BUFFER_TOO_SMALL; + } status = psa_cipher_generate_iv(&operation, iv, sizeof(iv), &iv_len); if (status != PSA_SUCCESS) { psa_cipher_abort(&operation); return status; } - if (output_size < iv_len) { - psa_cipher_abort(&operation); - return PSA_ERROR_BUFFER_TOO_SMALL; - } XMEMCPY(output, iv, iv_len); offset = iv_len; } From 0f6784534ada36d1da43211083926cad5212f2bc Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 11:57:55 +0200 Subject: [PATCH 07/24] F-4560: add wc_ecc_make_pub_ex to psa_asymmetric_export_public_key_ecc and wire it in psa_asymmetric_export_public_key_ecc was dead code missing wc_ecc_make_pub_ex in the key-pair branch, which would cause wc_ecc_export_x963 to fail on a private-only key. Add the missing call (matching the verify path at psa_ecc.c:247 and the inline block it replaces in psa_key_storage.c), add a forward declaration, and replace the duplicated inline block with a call to the function. Add test_ecc_export_public_key to cover psa_export_public_key on a SECP R1 key pair. --- src/psa_ecc.c | 3 ++ src/psa_key_storage.c | 50 ++++++++-------------------------- test/psa_server/psa_api_test.c | 38 ++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 38 deletions(-) diff --git a/src/psa_ecc.c b/src/psa_ecc.c index 0149ef6..996985e 100644 --- a/src/psa_ecc.c +++ b/src/psa_ecc.c @@ -421,6 +421,9 @@ psa_status_t psa_asymmetric_export_public_key_ecc(psa_key_type_t key_type, if (PSA_KEY_TYPE_IS_ECC_KEY_PAIR(key_type)) { ret = wc_ecc_import_private_key_ex(key_buffer, (word32)key_buffer_size, NULL, 0, &ecc, curve_id); + if (ret == 0) { + ret = wc_ecc_make_pub_ex(&ecc, NULL, NULL); + } } else { ret = wc_ecc_import_x963(key_buffer, (word32)key_buffer_size, &ecc); diff --git a/src/psa_key_storage.c b/src/psa_key_storage.c index 923be51..c60ed92 100644 --- a/src/psa_key_storage.c +++ b/src/psa_key_storage.c @@ -152,6 +152,15 @@ psa_status_t psa_asymmetric_export_public_key_x448(psa_key_type_t key_type, size_t output_size, size_t *output_length); #endif +#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_EXPORT) && defined(HAVE_ECC_KEY_IMPORT) +psa_status_t psa_asymmetric_export_public_key_ecc(psa_key_type_t key_type, + size_t key_bits, + const uint8_t *key_buffer, + size_t key_buffer_size, + uint8_t *output, + size_t output_size, + size_t *output_length); +#endif static psa_status_t psa_wc_error_to_psa_status(int ret) { @@ -1655,44 +1664,9 @@ psa_status_t psa_export_public_key( } } else { - ecc_key ecc; - word32 out_len = (word32)data_size; - int curve_id = wc_psa_get_ecc_curve_id(attributes.type, - attributes.bits); - - if (curve_id == ECC_CURVE_INVALID) { - status = PSA_ERROR_NOT_SUPPORTED; - } - else if (wc_ecc_init(&ecc) != 0) { - status = PSA_ERROR_INSUFFICIENT_MEMORY; - } - else { - ret = wc_ecc_import_private_key_ex(key_data, - (word32)key_data_length, - NULL, 0, &ecc, curve_id); - if (ret != 0) { - wc_ecc_free(&ecc); - status = psa_wc_error_to_psa_status(ret); - } - else { - ret = wc_ecc_make_pub_ex(&ecc, NULL, NULL); - if (ret != 0) { - wc_ecc_free(&ecc); - status = psa_wc_error_to_psa_status(ret); - } - else { - ret = wc_ecc_export_x963(&ecc, data, &out_len); - wc_ecc_free(&ecc); - if (ret != 0) { - status = psa_wc_error_to_psa_status(ret); - } - else { - *data_length = (size_t)out_len; - status = PSA_SUCCESS; - } - } - } - } + status = psa_asymmetric_export_public_key_ecc( + attributes.type, attributes.bits, key_data, + key_data_length, data, data_size, data_length); } } #else diff --git a/test/psa_server/psa_api_test.c b/test/psa_server/psa_api_test.c index 90d3f34..9d6cb45 100644 --- a/test/psa_server/psa_api_test.c +++ b/test/psa_server/psa_api_test.c @@ -4918,6 +4918,39 @@ static int test_ed448_export_public_key(void) "psa_generate_key(ED448 export)"); } +static int test_ecc_export_public_key(void) +{ + /* P-256 uncompressed: 0x04 || 32-byte X || 32-byte Y */ + uint8_t pub[65]; + size_t pub_len = 0; + psa_key_id_t key_id = 0; + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_status_t st; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(&attrs, 256); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&attrs, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); + + st = psa_generate_key(&attrs, &key_id); + if (check_status(st, "psa_generate_key(ECC export)") != TEST_OK) return TEST_FAIL; + + st = psa_export_public_key(key_id, pub, sizeof(pub), &pub_len); + if (check_status(st, "psa_export_public_key(ECC)") != TEST_OK) { + (void)psa_destroy_key(key_id); + return TEST_FAIL; + } + if (check_true(pub_len == 65u, "psa_export_public_key(ECC) length") != TEST_OK) { + (void)psa_destroy_key(key_id); + return TEST_FAIL; + } + + st = psa_destroy_key(key_id); + if (check_status(st, "psa_destroy_key(ECC)") != TEST_OK) return TEST_FAIL; + + return TEST_OK; +} + static int test_x25519_key_usability(void) { static const uint8_t alice_priv[X25519_KEY_BYTES] = { @@ -8403,6 +8436,11 @@ int main(int argc, char** argv) return TEST_FAIL; } } + if (only == NULL || strcmp(only, "ecc_export_pub") == 0) { + if (run_named_test("ecc_export_pub", test_ecc_export_public_key) == TEST_FAIL) { + return TEST_FAIL; + } + } if (only == NULL || strcmp(only, "x25519_key_usability") == 0) { if (run_named_test("x25519_key_usability", test_x25519_key_usability) == TEST_FAIL) { From 930f0b31f086919d47c66ace825a5ee800fada14 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 12:04:56 +0200 Subject: [PATCH 08/24] F-4096: reject PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG in wolfpsa_aead_setup Wildcard AEAD algorithms (PSA_ALG_AEAD_AT_LEAST_THIS_LENGTH_FLAG set) are policy-only constructs per the PSA Crypto spec and must not be accepted as the algorithm for an AEAD operation. Without this check, the flag bits were silently ignored and min_len was used as the concrete tag length. --- src/psa_aead.c | 3 ++ test/psa_server/psa_api_test.c | 56 ++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/psa_aead.c b/src/psa_aead.c index 0e82d32..b5f7bc3 100644 --- a/src/psa_aead.c +++ b/src/psa_aead.c @@ -222,6 +222,9 @@ static psa_status_t wolfpsa_aead_setup(psa_aead_operation_t *operation, if (!PSA_ALG_IS_AEAD(alg) || PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_CCM_STAR_NO_TAG)) { return PSA_ERROR_NOT_SUPPORTED; } + if ((alg & PSA_ALG_AEAD_AT_LEAST_THIS_LENGTH_FLAG) != 0) { + return PSA_ERROR_INVALID_ARGUMENT; + } #ifndef HAVE_AESGCM if (PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_GCM)) { return PSA_ERROR_NOT_SUPPORTED; diff --git a/test/psa_server/psa_api_test.c b/test/psa_server/psa_api_test.c index 9d6cb45..575388f 100644 --- a/test/psa_server/psa_api_test.c +++ b/test/psa_server/psa_api_test.c @@ -3689,6 +3689,56 @@ static int test_aead_gcm_rejects_short_tags(void) return ret; } +/* F-4096: PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG is a wildcard policy + * algorithm and must be rejected by operation setup with + * PSA_ERROR_INVALID_ARGUMENT, not accepted as a concrete tag length. */ +static int test_aead_rejects_wildcard_alg(void) +{ + static const uint8_t key[16] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + psa_key_id_t key_id = 0; + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_aead_operation_t op = psa_aead_operation_init(); + psa_algorithm_t wildcard = PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG( + PSA_ALG_GCM, 8); + psa_status_t st; + int ret = TEST_OK; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attrs, 128); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&attrs, wildcard); + + st = psa_import_key(&attrs, key, sizeof(key), &key_id); + if (check_status(st, "psa_import_key(GCM wildcard policy)") != TEST_OK) { + return TEST_FAIL; + } + + st = psa_aead_encrypt_setup(&op, key_id, wildcard); + if (check_true(st == PSA_ERROR_INVALID_ARGUMENT, + "psa_aead_encrypt_setup rejects wildcard alg") != TEST_OK) { + ret = TEST_FAIL; + } + psa_aead_abort(&op); + + st = psa_aead_decrypt_setup(&op, key_id, wildcard); + if (check_true(st == PSA_ERROR_INVALID_ARGUMENT, + "psa_aead_decrypt_setup rejects wildcard alg") != TEST_OK) { + ret = TEST_FAIL; + } + psa_aead_abort(&op); + + st = psa_destroy_key(key_id); + if (check_status(st, "psa_destroy_key(GCM wildcard policy)") != TEST_OK) { + return TEST_FAIL; + } + + return ret; +} + static int test_aead_gcm_rejects_short_nonce(void) { static const uint8_t key[16] = { @@ -8316,6 +8366,12 @@ int main(int argc, char** argv) return TEST_FAIL; } } + if (only == NULL || strcmp(only, "aead_wildcard_alg_rejected") == 0) { + if (run_named_test("aead_wildcard_alg_rejected", + test_aead_rejects_wildcard_alg) == TEST_FAIL) { + return TEST_FAIL; + } + } if (only == NULL || strcmp(only, "aead_gcm_ccm_non_aes_key") == 0) { if (run_named_test("aead_gcm_ccm_non_aes_key", test_gcm_ccm_reject_non_aes_key_type) == TEST_FAIL) { From 6c7f12b1d9480139936d0ba92093ea61fc3ff75a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 12:12:01 +0200 Subject: [PATCH 09/24] F-4090: use PSA_BITS_TO_BYTES instead of key_bits/8 in wc_ecc_make_key_ex call Integer division truncates for P-521 (521/8=65), but the correct byte length is 66 (PSA_BITS_TO_BYTES(521)=66). The rest of the function already uses PSA_BITS_TO_BYTES consistently; align the wc_ecc_make_key_ex keysize hint to match. --- src/psa_ecc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psa_ecc.c b/src/psa_ecc.c index 996985e..18887b3 100644 --- a/src/psa_ecc.c +++ b/src/psa_ecc.c @@ -337,7 +337,7 @@ psa_status_t psa_asymmetric_generate_key_ecc(psa_key_type_t key_type, } /* Generate key pair */ - ret = wc_ecc_make_key_ex(&rng, (int)key_bits / 8, &ecc, curve_id); + ret = wc_ecc_make_key_ex(&rng, (int)PSA_BITS_TO_BYTES(key_bits), &ecc, curve_id); if (ret != 0) { wc_FreeRng(&rng); wc_ecc_free(&ecc); From 889910a2ed06ab8597cf9bae61607cda25c7971a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 12:18:54 +0200 Subject: [PATCH 10/24] F-3862: add SHAKE256_512 to hash engine so psa_sign/verify_message works for Ed448PH PSA_ALG_SHAKE256_512 was missing from psa_hash_engine.c, causing psa_hash_compute() to return PSA_ERROR_NOT_SUPPORTED whenever psa_sign_message() or psa_verify_message() was called with PSA_ALG_ED448PH. Add PSA_ALG_SHAKE256_512 cases (guarded by WOLFSSL_SHAKE256) to all seven switch statements in psa_hash_engine.c: check_alg_supported, get_size, cleanup, setup, update, finish, and clone. Add test_ed448_sign_message to cover the full psa_sign_message/psa_verify_message roundtrip for Ed448PH. --- src/psa_hash_engine.c | 40 +++++++++++++++++++++++-- test/psa_server/psa_api_test.c | 53 ++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/psa_hash_engine.c b/src/psa_hash_engine.c index 121cd38..0ab24e8 100644 --- a/src/psa_hash_engine.c +++ b/src/psa_hash_engine.c @@ -197,6 +197,11 @@ static void psa_hash_cleanup_ctx(psa_hash_operation_ctx_t *ctx) case PSA_ALG_SHA3_512: wc_Sha3_512_Free(&ctx->ctx.sha3); break; +#endif +#ifdef WOLFSSL_SHAKE256 + case PSA_ALG_SHAKE256_512: + wc_Shake256_Free((wc_Shake *)&ctx->ctx.sha3); + break; #endif default: break; @@ -250,6 +255,10 @@ psa_status_t psa_hash_check_alg_supported(psa_algorithm_t alg) case PSA_ALG_SHA3_384: case PSA_ALG_SHA3_512: return PSA_SUCCESS; + #endif + #ifdef WOLFSSL_SHAKE256 + case PSA_ALG_SHAKE256_512: + return PSA_SUCCESS; #endif default: return PSA_ERROR_NOT_SUPPORTED; @@ -305,6 +314,10 @@ static size_t psa_hash_get_size(psa_algorithm_t alg) return WC_SHA3_384_DIGEST_SIZE; case PSA_ALG_SHA3_512: return WC_SHA3_512_DIGEST_SIZE; + #endif + #ifdef WOLFSSL_SHAKE256 + case PSA_ALG_SHAKE256_512: + return 64u; #endif default: return 0; @@ -413,6 +426,12 @@ psa_status_t psa_hash_setup(psa_hash_operation_t *operation, case PSA_ALG_SHA3_512: ret = wc_InitSha3_512(&ctx->ctx.sha3, NULL, wolfPSA_GetDefaultDevID()); break; +#endif +#ifdef WOLFSSL_SHAKE256 + case PSA_ALG_SHAKE256_512: + ret = wc_InitShake256((wc_Shake *)&ctx->ctx.sha3, NULL, + wolfPSA_GetDefaultDevID()); + break; #endif default: XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -520,6 +539,12 @@ psa_status_t psa_hash_update(psa_hash_operation_t *operation, ret = wc_Sha3_512_Update(&ctx->ctx.sha3, input, (word32)input_length); break; +#endif +#ifdef WOLFSSL_SHAKE256 + case PSA_ALG_SHAKE256_512: + ret = wc_Shake256_Update((wc_Shake *)&ctx->ctx.sha3, input, + (word32)input_length); + break; #endif default: return wolfpsa_hash_fail(operation, PSA_ERROR_NOT_SUPPORTED); @@ -625,15 +650,20 @@ psa_status_t psa_hash_finish(psa_hash_operation_t *operation, case PSA_ALG_SHA3_512: ret = wc_Sha3_512_Final(&ctx->ctx.sha3, hash); break; +#endif +#ifdef WOLFSSL_SHAKE256 + case PSA_ALG_SHAKE256_512: + ret = wc_Shake256_Final((wc_Shake *)&ctx->ctx.sha3, hash, 64u); + break; #endif default: return wolfpsa_hash_fail(operation, PSA_ERROR_NOT_SUPPORTED); } - + if (ret != 0) { return wolfpsa_hash_fail(operation, wc_error_to_psa_status(ret)); } - + *hash_length = expected_hash_size; ctx->finalized = 1; psa_hash_cleanup_ctx(ctx); @@ -813,6 +843,12 @@ psa_status_t psa_hash_clone(const psa_hash_operation_t *source_operation, ret = wc_Sha3_512_Copy((wc_Sha3 *)(uintptr_t)&source_ctx->ctx.sha3, &target_ctx->ctx.sha3); break; +#endif +#ifdef WOLFSSL_SHAKE256 + case PSA_ALG_SHAKE256_512: + ret = wc_Shake256_Copy((wc_Shake *)(uintptr_t)&source_ctx->ctx.sha3, + &target_ctx->ctx.sha3); + break; #endif default: wc_ForceZero(target_ctx, sizeof(*target_ctx)); diff --git a/test/psa_server/psa_api_test.c b/test/psa_server/psa_api_test.c index 575388f..5758f6d 100644 --- a/test/psa_server/psa_api_test.c +++ b/test/psa_server/psa_api_test.c @@ -4912,6 +4912,53 @@ static int test_ed448_rejects_bad_hash_length(void) 448, PSA_ALG_ED448PH, "psa_generate_key(ED448 bad hash len)"); } +static int test_ed448_sign_message(void) +{ + static const uint8_t msg[] = "ed448 sign_message roundtrip"; + uint8_t sig[128]; + size_t sig_len = sizeof(sig); + psa_key_id_t key_id = 0; + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_status_t st; + + psa_set_key_type(&attrs, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS)); + psa_set_key_bits(&attrs, 448); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE); + psa_set_key_algorithm(&attrs, PSA_ALG_ED448PH); + + st = psa_generate_key(&attrs, &key_id); + if (st == PSA_ERROR_NOT_SUPPORTED) { + return TEST_SKIPPED; + } + if (check_status(st, "psa_generate_key(ED448)") != TEST_OK) return TEST_FAIL; + + st = psa_sign_message(key_id, PSA_ALG_ED448PH, + msg, sizeof(msg) - 1, + sig, sizeof(sig), &sig_len); + if (check_status(st, "psa_sign_message(ED448PH)") != TEST_OK) { + (void)psa_destroy_key(key_id); + return TEST_FAIL; + } + if (check_true(sig_len == 114u, "psa_sign_message(ED448PH) length") != TEST_OK) { + (void)psa_destroy_key(key_id); + return TEST_FAIL; + } + + st = psa_verify_message(key_id, PSA_ALG_ED448PH, + msg, sizeof(msg) - 1, sig, sig_len); + if (check_status(st, "psa_verify_message(ED448PH)") != TEST_OK) { + (void)psa_destroy_key(key_id); + return TEST_FAIL; + } + + st = psa_destroy_key(key_id); + if (check_status(st, "psa_destroy_key(ED448)") != TEST_OK) return TEST_FAIL; + + return TEST_OK; +} + static int test_twisted_edwards_export_public_key(size_t bits, psa_algorithm_t alg, size_t expected_pub_len, @@ -8482,6 +8529,12 @@ int main(int argc, char** argv) return TEST_FAIL; } } + if (only == NULL || strcmp(only, "ed448_sign_message") == 0) { + if (run_named_test("ed448_sign_message", + test_ed448_sign_message) == TEST_FAIL) { + return TEST_FAIL; + } + } if (only == NULL || strcmp(only, "ed25519_export_pub") == 0) { if (run_named_test("ed25519_export_pub", test_ed25519_export_public_key) == TEST_FAIL) { return TEST_FAIL; From e89506b8f5800a358b2a1e342693dd6409ff7f97 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 12:22:59 +0200 Subject: [PATCH 11/24] F-3656: remove dead psa_chacha20_poly1305_check_* helpers with no callers The three WOLFSSL_LOCAL check helpers were never called; psa_aead.c performs the equivalent validation inline. Removing them eliminates dead flash and prevents undetected mutations. --- src/psa_chacha20_poly1305.c | 34 --------------------------------- wolfpsa/psa_chacha20_poly1305.h | 12 ------------ 2 files changed, 46 deletions(-) diff --git a/src/psa_chacha20_poly1305.c b/src/psa_chacha20_poly1305.c index 5b83de5..857f5e3 100644 --- a/src/psa_chacha20_poly1305.c +++ b/src/psa_chacha20_poly1305.c @@ -39,40 +39,6 @@ #include #include -/* Check if ChaCha20-Poly1305 algorithm is supported */ -psa_status_t psa_chacha20_poly1305_check_alg_supported(psa_algorithm_t alg) -{ - if (alg == PSA_ALG_CHACHA20_POLY1305) { - return PSA_SUCCESS; - } - - return PSA_ERROR_NOT_SUPPORTED; -} - -/* Check if ChaCha20-Poly1305 key type is supported */ -psa_status_t psa_chacha20_poly1305_check_key_type_supported(psa_key_type_t type) -{ - if (type == PSA_KEY_TYPE_CHACHA20) { - return PSA_SUCCESS; - } - - return PSA_ERROR_NOT_SUPPORTED; -} - -/* Check if ChaCha20-Poly1305 key size is valid */ -psa_status_t psa_chacha20_poly1305_check_key_size_valid(psa_key_type_t type, - size_t bits) -{ - if (type == PSA_KEY_TYPE_CHACHA20) { - /* ChaCha20 key size is 256 bits (32 bytes) */ - if (bits == 256) { - return PSA_SUCCESS; - } - } - - return PSA_ERROR_INVALID_ARGUMENT; -} - /* Encrypt using ChaCha20-Poly1305 */ psa_status_t psa_chacha20_poly1305_encrypt( const uint8_t *key, size_t key_length, diff --git a/wolfpsa/psa_chacha20_poly1305.h b/wolfpsa/psa_chacha20_poly1305.h index ffdc83b..c84bb14 100644 --- a/wolfpsa/psa_chacha20_poly1305.h +++ b/wolfpsa/psa_chacha20_poly1305.h @@ -43,18 +43,6 @@ #include #include -/* Check if ChaCha20-Poly1305 algorithm is supported */ -WOLFSSL_LOCAL psa_status_t psa_chacha20_poly1305_check_alg_supported( - psa_algorithm_t alg); - -/* Check if ChaCha20-Poly1305 key type is supported */ -WOLFSSL_LOCAL psa_status_t psa_chacha20_poly1305_check_key_type_supported( - psa_key_type_t type); - -/* Check if ChaCha20-Poly1305 key size is valid */ -WOLFSSL_LOCAL psa_status_t psa_chacha20_poly1305_check_key_size_valid( - psa_key_type_t type, size_t bits); - /* Encrypt using ChaCha20-Poly1305 */ WOLFSSL_LOCAL psa_status_t psa_chacha20_poly1305_encrypt( const uint8_t *key, size_t key_length, From bbc920646530c735bb33d8e216fe830d87529d6c Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 12:25:16 +0200 Subject: [PATCH 12/24] F-3655: remove dead psa_tls_prf helpers with no callers src/psa_tls_prf.c and wolfpsa/psa_tls_prf.h defined five WOLFSSL_LOCAL TLS 1.3 PRF helpers (psa_tls_prf_check_alg_supported, psa_tls13_prf, psa_tls13_hkdf_extract, psa_tls13_hkdf_expand, psa_tls13_hkdf_expand_label) that had no callers anywhere in the codebase. The header was only included by the implementation file itself. Same pattern as F-3429. --- src/psa_tls_prf.c | 408 ------------------------------------------ wolfpsa/psa_tls_prf.h | 80 --------- 2 files changed, 488 deletions(-) delete mode 100644 src/psa_tls_prf.c delete mode 100644 wolfpsa/psa_tls_prf.h diff --git a/src/psa_tls_prf.c b/src/psa_tls_prf.c deleted file mode 100644 index 962d796..0000000 --- a/src/psa_tls_prf.c +++ /dev/null @@ -1,408 +0,0 @@ -/* psa_tls_prf.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 - */ - -#ifdef HAVE_CONFIG_H - #include -#endif - -#include - -#if defined(WOLFSSL_PSA_ENGINE) && defined(WOLFSSL_TLS13) && \ - defined(HAVE_HKDF) && !defined(NO_HMAC) - -#include -#include "psa_size.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Check if TLS PRF algorithm is supported */ -psa_status_t psa_tls_prf_check_alg_supported(psa_algorithm_t alg) -{ - if (PSA_ALG_IS_TLS13_PRF(alg)) { - /* Check if the hash algorithm is supported */ - switch (PSA_ALG_HASH_FROM_TLS13_PRF(alg)) { - case PSA_ALG_SHA_256: - case PSA_ALG_SHA_384: - return PSA_SUCCESS; - default: - return PSA_ERROR_NOT_SUPPORTED; - } - } - - return PSA_ERROR_NOT_SUPPORTED; -} - -/* Get the hash algorithm from TLS 1.3 PRF algorithm */ -static int psa_tls13_get_hash_type(psa_algorithm_t alg, enum wc_HashType* hashType) -{ - psa_algorithm_t hash_alg = PSA_ALG_HASH_FROM_TLS13_PRF(alg); - - switch (hash_alg) { - case PSA_ALG_SHA_256: - *hashType = WC_HASH_TYPE_SHA256; - return 0; - case PSA_ALG_SHA_384: - *hashType = WC_HASH_TYPE_SHA384; - return 0; - default: - return -1; - } -} - -/* TLS 1.3 PRF (HKDF) */ -psa_status_t psa_tls13_prf( - psa_algorithm_t alg, - const uint8_t *secret, size_t secret_length, - const uint8_t *label, size_t label_length, - const uint8_t *context, size_t context_length, - uint8_t *output, size_t output_length) -{ - int ret; - enum wc_HashType hashType; - - /* Check parameters */ - if (!PSA_ALG_IS_TLS13_PRF(alg)) { - return PSA_ERROR_NOT_SUPPORTED; - } - - if (secret == NULL && secret_length > 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - if (label == NULL && label_length > 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - if (context == NULL && context_length > 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - if (output == NULL || output_length == 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - if ((wolfpsa_check_word32_length(secret_length) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(label_length) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(context_length) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(output_length) != PSA_SUCCESS)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - /* Get hash type */ - ret = psa_tls13_get_hash_type(alg, &hashType); - if (ret != 0) { - return PSA_ERROR_NOT_SUPPORTED; - } - - /* Perform TLS 1.3 PRF operation */ - ret = wc_Tls13_HKDF_Expand_Label( - output, (word32)output_length, - secret, (word32)secret_length, - NULL, 0, /* protocol */ - label, (word32)label_length, - context, (word32)context_length, - hashType - ); - - if (ret != 0) { - return wc_error_to_psa_status(ret); - } - - return PSA_SUCCESS; -} - -/* TLS 1.3 HKDF Extract */ -psa_status_t psa_tls13_hkdf_extract( - psa_algorithm_t alg, - const uint8_t *salt, size_t salt_length, - const uint8_t *ikm, size_t ikm_length, - uint8_t *output, size_t output_size, size_t *output_length) -{ - int ret; - enum wc_HashType hashType; - int hashLen; - - /* Check parameters */ - if (!PSA_ALG_IS_TLS13_PRF(alg)) { - return PSA_ERROR_NOT_SUPPORTED; - } - - if (salt == NULL && salt_length > 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - if (ikm == NULL && ikm_length > 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - if (output == NULL || output_size == 0 || output_length == NULL) { - return PSA_ERROR_INVALID_ARGUMENT; - } - if ((wolfpsa_check_word32_length(salt_length) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(ikm_length) != PSA_SUCCESS)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - /* Get hash type */ - ret = psa_tls13_get_hash_type(alg, &hashType); - if (ret != 0) { - return PSA_ERROR_NOT_SUPPORTED; - } - - /* Get hash length */ - ret = wc_HashGetDigestSize(hashType); - if (ret < 0) { - return wc_error_to_psa_status(ret); - } - hashLen = ret; - - /* Check output buffer size */ - if (output_size < (size_t)hashLen) { - return PSA_ERROR_BUFFER_TOO_SMALL; - } - - /* Perform HKDF Extract operation */ - ret = wc_HKDF_Extract( - hashType, - salt, (word32)salt_length, - ikm, (word32)ikm_length, - output - ); - - if (ret != 0) { - return wc_error_to_psa_status(ret); - } - - *output_length = hashLen; - - return PSA_SUCCESS; -} - -/* TLS 1.3 HKDF Expand */ -psa_status_t psa_tls13_hkdf_expand( - psa_algorithm_t alg, - const uint8_t *prk, size_t prk_length, - const uint8_t *info, size_t info_length, - uint8_t *output, size_t output_length) -{ - int ret; - enum wc_HashType hashType; - - /* Check parameters */ - if (!PSA_ALG_IS_TLS13_PRF(alg)) { - return PSA_ERROR_NOT_SUPPORTED; - } - - if (prk == NULL || prk_length == 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - if (info == NULL && info_length > 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - if (output == NULL || output_length == 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - if ((wolfpsa_check_word32_length(prk_length) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(info_length) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(output_length) != PSA_SUCCESS)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - /* Get hash type */ - ret = psa_tls13_get_hash_type(alg, &hashType); - if (ret != 0) { - return PSA_ERROR_NOT_SUPPORTED; - } - - /* Perform HKDF Expand operation */ - ret = wc_HKDF_Expand( - hashType, - prk, (word32)prk_length, - info, (word32)info_length, - output, (word32)output_length - ); - - if (ret != 0) { - return wc_error_to_psa_status(ret); - } - - return PSA_SUCCESS; -} - -/* TLS 1.3 HKDF Expand Label */ -psa_status_t psa_tls13_hkdf_expand_label( - psa_algorithm_t alg, - const uint8_t *secret, size_t secret_length, - const uint8_t *label, size_t label_length, - const uint8_t *context, size_t context_length, - uint8_t *output, size_t output_length) -{ - int ret; - enum wc_HashType hashType; - - /* Check parameters */ - if (!PSA_ALG_IS_TLS13_PRF(alg)) { - return PSA_ERROR_NOT_SUPPORTED; - } - - if (secret == NULL && secret_length > 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - if (label == NULL && label_length > 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - if (context == NULL && context_length > 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - if (output == NULL || output_length == 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - if ((wolfpsa_check_word32_length(secret_length) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(label_length) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(context_length) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(output_length) != PSA_SUCCESS)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - /* Get hash type */ - ret = psa_tls13_get_hash_type(alg, &hashType); - if (ret != 0) { - return PSA_ERROR_NOT_SUPPORTED; - } - - /* Perform TLS 1.3 HKDF Expand Label operation */ - ret = wc_Tls13_HKDF_Expand_Label( - output, (word32)output_length, - secret, (word32)secret_length, - NULL, 0, /* protocol */ - label, (word32)label_length, - context, (word32)context_length, - hashType - ); - - if (ret != 0) { - return wc_error_to_psa_status(ret); - } - - return PSA_SUCCESS; -} - -#elif defined(WOLFSSL_PSA_ENGINE) && defined(WOLFSSL_TLS13) - -#include -#include - -psa_status_t psa_tls_prf_check_alg_supported(psa_algorithm_t alg) -{ - (void)alg; - - return PSA_ERROR_NOT_SUPPORTED; -} - -psa_status_t psa_tls13_prf( - psa_algorithm_t alg, - const uint8_t *secret, size_t secret_length, - const uint8_t *label, size_t label_length, - const uint8_t *context, size_t context_length, - uint8_t *output, size_t output_length) -{ - (void)alg; - (void)secret; - (void)secret_length; - (void)label; - (void)label_length; - (void)context; - (void)context_length; - (void)output; - (void)output_length; - - return PSA_ERROR_NOT_SUPPORTED; -} - -psa_status_t psa_tls13_hkdf_extract( - psa_algorithm_t alg, - const uint8_t *salt, size_t salt_length, - const uint8_t *ikm, size_t ikm_length, - uint8_t *output, size_t output_size, size_t *output_length) -{ - (void)alg; - (void)salt; - (void)salt_length; - (void)ikm; - (void)ikm_length; - (void)output; - (void)output_size; - (void)output_length; - - return PSA_ERROR_NOT_SUPPORTED; -} - -psa_status_t psa_tls13_hkdf_expand( - psa_algorithm_t alg, - const uint8_t *prk, size_t prk_length, - const uint8_t *info, size_t info_length, - uint8_t *output, size_t output_length) -{ - (void)alg; - (void)prk; - (void)prk_length; - (void)info; - (void)info_length; - (void)output; - (void)output_length; - - return PSA_ERROR_NOT_SUPPORTED; -} - -psa_status_t psa_tls13_hkdf_expand_label( - psa_algorithm_t alg, - const uint8_t *secret, size_t secret_length, - const uint8_t *label, size_t label_length, - const uint8_t *context, size_t context_length, - uint8_t *output, size_t output_length) -{ - (void)alg; - (void)secret; - (void)secret_length; - (void)label; - (void)label_length; - (void)context; - (void)context_length; - (void)output; - (void)output_length; - - return PSA_ERROR_NOT_SUPPORTED; -} - -#endif /* WOLFSSL_PSA_ENGINE && WOLFSSL_TLS13 */ diff --git a/wolfpsa/psa_tls_prf.h b/wolfpsa/psa_tls_prf.h deleted file mode 100644 index 6e26caf..0000000 --- a/wolfpsa/psa_tls_prf.h +++ /dev/null @@ -1,80 +0,0 @@ -/* psa_tls_prf.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 - */ - -/** - * Platform Security Architecture (PSA) TLS PRF API header - * - * If WOLFSSL_PSA_ENGINE is defined, wolfSSL provides an implementation of the - * PSA Crypto API for TLS PRF operations that calls wolfCrypt APIs. - * - */ - -#ifndef WOLFSSL_PSA_TLS_PRF_H -#define WOLFSSL_PSA_TLS_PRF_H - -#ifdef HAVE_CONFIG_H - #include -#endif - -#include - -#if defined(WOLFSSL_PSA_ENGINE) && defined(WOLFSSL_TLS13) - -#include -#include -#include -#include - -/* Check if TLS PRF algorithm is supported */ -WOLFSSL_LOCAL psa_status_t psa_tls_prf_check_alg_supported(psa_algorithm_t alg); - -/* TLS 1.3 PRF (HKDF) */ -WOLFSSL_LOCAL psa_status_t psa_tls13_prf( - psa_algorithm_t alg, - const uint8_t *secret, size_t secret_length, - const uint8_t *label, size_t label_length, - const uint8_t *context, size_t context_length, - uint8_t *output, size_t output_length); - -/* TLS 1.3 HKDF Extract */ -WOLFSSL_LOCAL psa_status_t psa_tls13_hkdf_extract( - psa_algorithm_t alg, - const uint8_t *salt, size_t salt_length, - const uint8_t *ikm, size_t ikm_length, - uint8_t *output, size_t output_size, size_t *output_length); - -/* TLS 1.3 HKDF Expand */ -WOLFSSL_LOCAL psa_status_t psa_tls13_hkdf_expand( - psa_algorithm_t alg, - const uint8_t *prk, size_t prk_length, - const uint8_t *info, size_t info_length, - uint8_t *output, size_t output_length); - -/* TLS 1.3 HKDF Expand Label */ -WOLFSSL_LOCAL psa_status_t psa_tls13_hkdf_expand_label( - psa_algorithm_t alg, - const uint8_t *secret, size_t secret_length, - const uint8_t *label, size_t label_length, - const uint8_t *context, size_t context_length, - uint8_t *output, size_t output_length); - -#endif /* WOLFSSL_PSA_ENGINE && WOLFSSL_TLS13 */ -#endif /* WOLFSSL_PSA_TLS_PRF_H */ From 3ef39c4d102629fae457de4ad1f2309557f43221 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 12:27:31 +0200 Subject: [PATCH 13/24] F-3654: fix dead out_len guard in wolfpsa_aead_encrypt_final ChaCha branch The check `out_len < ctx->tag_length` was dead code: psa_chacha20_poly1305_encrypt always sets *ciphertext_length = plaintext_length + tag_size on success, so out_len equals input_length + tag_length and the condition reduces to input_length < 0, impossible for size_t. The correct bound to protect the subsequent XMEMCPY(tag, tmp + input_length, tag_length) is out_len < input_length + tag_length. --- src/psa_aead.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psa_aead.c b/src/psa_aead.c index b5f7bc3..a11d4d8 100644 --- a/src/psa_aead.c +++ b/src/psa_aead.c @@ -625,7 +625,7 @@ static psa_status_t wolfpsa_aead_encrypt_final(wolfpsa_aead_ctx_t *ctx, XFREE(tmp, NULL, DYNAMIC_TYPE_TMP_BUFFER); return (psa_status_t)ret; } - if (out_len < ctx->tag_length) { + if (out_len < ctx->input_length + ctx->tag_length) { wc_ForceZero(tmp, chacha_ciphertext_size); XFREE(tmp, NULL, DYNAMIC_TYPE_TMP_BUFFER); return PSA_ERROR_GENERIC_ERROR; From 8ac5c347f2e758a3af55891e27699ec45009e0ea Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 14:01:43 +0200 Subject: [PATCH 14/24] build: enable ML-KEM, LMS/XMSS verify-only, Ascon, XChaCha, AES-KW, CMAC-KDF Enable the wolfCrypt backends needed for PSA 1.4: WOLFSSL_HAVE_MLKEM, verify-only LMS/XMSS, Ascon (experimental opt-in), XChaCha, AES key wrap and CMAC KDF. Add ascon.c to the wolfCrypt source list. Quarantine the legacy ML-KEM and LMS/XMSS modules behind #if 0: they target headers and APIs that no longer exist on wolfSSL master (mlkem.h, lms.h/xmss.h, psa_ml_kem_parameter_t) and are rewritten in the PSA 1.4 work. Replace the stale WOLFSSL_HAVE_KYBER guard in psa_pq.c and add psa_pqc_internal.h with the internal PQC backend contracts. --- Makefile | 1 + src/psa_lms_xmss.c | 5 ++- src/psa_mlkem.c | 5 ++- src/psa_pq.c | 4 +-- src/psa_pqc_internal.h | 78 ++++++++++++++++++++++++++++++++++++++++++ user_settings.h | 12 +++++++ 6 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 src/psa_pqc_internal.h diff --git a/Makefile b/Makefile index bdcb20b..e822c71 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,7 @@ OBJ_PIC := $(patsubst src/%.c,$(OBJDIR_PIC)/%.o,$(SRC)) WOLFCRYPT_SRC := \ $(WOLFSSL_PATH)/wolfcrypt/src/aes.c \ + $(WOLFSSL_PATH)/wolfcrypt/src/ascon.c \ $(WOLFSSL_PATH)/wolfcrypt/src/asn.c \ $(WOLFSSL_PATH)/wolfcrypt/src/chacha.c \ $(WOLFSSL_PATH)/wolfcrypt/src/chacha20_poly1305.c \ diff --git a/src/psa_lms_xmss.c b/src/psa_lms_xmss.c index ce24939..1db4a8e 100644 --- a/src/psa_lms_xmss.c +++ b/src/psa_lms_xmss.c @@ -25,7 +25,10 @@ #include -#if defined(WOLFSSL_PSA_ENGINE) && (defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS)) +/* Quarantined pending the PSA 1.4 LMS/XMSS rewrite: this legacy code targets + * the removed / headers and + * key-generation/sign APIs that do not exist under the verify-only build. */ +#if 0 #include #include "psa_size.h" diff --git a/src/psa_mlkem.c b/src/psa_mlkem.c index 34508aa..5c4c985 100644 --- a/src/psa_mlkem.c +++ b/src/psa_mlkem.c @@ -25,7 +25,10 @@ #include -#if defined(WOLFSSL_PSA_ENGINE) && (defined(WOLFSSL_HAVE_KYBER) || defined(WOLFSSL_WC_MLKEM)) +/* Quarantined pending the PSA 1.4 ML-KEM rewrite: this legacy code targets + * the removed API and references + * psa_ml_kem_parameter_t, which is not defined by any header. */ +#if 0 #include #include diff --git a/src/psa_pq.c b/src/psa_pq.c index 640744a..e78df4d 100644 --- a/src/psa_pq.c +++ b/src/psa_pq.c @@ -40,7 +40,7 @@ psa_status_t psa_pq_check_key_type_supported(psa_key_type_t type) { switch (type) { -#if defined(WOLFSSL_HAVE_KYBER) || defined(WOLFSSL_WC_MLKEM) +#if defined(WOLFSSL_HAVE_MLKEM) case PSA_KEY_TYPE_ML_KEM_KEY_PAIR: case PSA_KEY_TYPE_ML_KEM_PUBLIC_KEY: return PSA_SUCCESS; @@ -78,7 +78,7 @@ psa_status_t psa_pq_check_key_size_valid(psa_key_type_t type, size_t bits) (void)bits; switch (type) { -#if defined(WOLFSSL_HAVE_KYBER) || defined(WOLFSSL_WC_MLKEM) +#if defined(WOLFSSL_HAVE_MLKEM) case PSA_KEY_TYPE_ML_KEM_KEY_PAIR: case PSA_KEY_TYPE_ML_KEM_PUBLIC_KEY: /* ML-KEM key sizes: 512, 768, 1024 */ diff --git a/src/psa_pqc_internal.h b/src/psa_pqc_internal.h new file mode 100644 index 0000000..6033a3d --- /dev/null +++ b/src/psa_pqc_internal.h @@ -0,0 +1,78 @@ +/* psa_pqc_internal.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 + */ + +/* Internal interfaces between the PQC backends (psa_mldsa.c, psa_mlkem.c, + * psa_lms_xmss.c) and the PSA front-end dispatch (psa_asymmetric_api.c, + * psa_key_storage.c, psa_kem.c). Not part of the public API. + * + * Key material conventions (PSA Crypto API 1.4 PQC extension): + * - ML-DSA key pair = 32-byte seed xi (FIPS 204 ML-DSA.KeyGen_internal), + * bits in {128, 192, 256} for ML-DSA-44/65/87. + * - ML-DSA public key = raw FIPS 204 pk bytes (1312/1952/2592). + * - ML-KEM key pair = 64-byte seed d||z (FIPS 203 ML-KEM.KeyGen_internal), + * bits in {512, 768, 1024}. + * - ML-KEM public key = raw FIPS 203 ek bytes (800/1184/1568). + * - LMS/HSS and XMSS/XMSS^MT: raw public keys, verify only. + */ + +#ifndef PSA_PQC_INTERNAL_H +#define PSA_PQC_INTERNAL_H + +#include + +#define WOLFPSA_MLDSA_SEED_SIZE 32 +#define WOLFPSA_MLKEM_SEED_SIZE 64 +#define WOLFPSA_MLKEM_SS_SIZE 32 + +/* psa_mldsa.c -- bits in {128, 192, 256} */ +psa_status_t wolfpsa_mldsa_generate_seed(size_t bits, uint8_t *seed); +psa_status_t wolfpsa_mldsa_export_public(size_t bits, const uint8_t *seed, + uint8_t *out, size_t out_size, size_t *out_length); +psa_status_t wolfpsa_mldsa_sign(size_t bits, const uint8_t *key_data, + size_t key_data_length, psa_algorithm_t alg, + const uint8_t *context, size_t context_length, + const uint8_t *input, size_t input_length, int input_is_hash, + uint8_t *signature, size_t signature_size, size_t *signature_length); +psa_status_t wolfpsa_mldsa_verify(size_t bits, psa_key_type_t key_type, + const uint8_t *key_data, size_t key_data_length, psa_algorithm_t alg, + const uint8_t *context, size_t context_length, + const uint8_t *input, size_t input_length, int input_is_hash, + const uint8_t *signature, size_t signature_length); + +/* psa_mlkem.c -- bits in {512, 768, 1024} */ +psa_status_t wolfpsa_mlkem_generate_seed(size_t bits, uint8_t *seed); +psa_status_t wolfpsa_mlkem_export_public(size_t bits, const uint8_t *seed, + uint8_t *out, size_t out_size, size_t *out_length); +psa_status_t wolfpsa_mlkem_encapsulate(size_t bits, psa_key_type_t key_type, + const uint8_t *key_data, size_t key_data_length, + uint8_t *ciphertext, size_t ciphertext_size, size_t *ciphertext_length, + uint8_t *shared_secret); +psa_status_t wolfpsa_mlkem_decapsulate(size_t bits, const uint8_t *seed, + const uint8_t *ciphertext, size_t ciphertext_length, + uint8_t *shared_secret); + +/* psa_lms_xmss.c -- verify only, raw public keys */ +psa_status_t wolfpsa_lms_verify(const uint8_t *pub, size_t pub_len, + const uint8_t *msg, size_t msg_len, const uint8_t *sig, size_t sig_len); +psa_status_t wolfpsa_xmss_verify(const uint8_t *pub, size_t pub_len, + const uint8_t *msg, size_t msg_len, const uint8_t *sig, size_t sig_len); + +#endif /* PSA_PQC_INTERNAL_H */ diff --git a/user_settings.h b/user_settings.h index 016fd01..e7d8159 100644 --- a/user_settings.h +++ b/user_settings.h @@ -78,5 +78,17 @@ #define HAVE_ED448 #define WOLFSSL_ED448_STREAMING_VERIFY #define WOLFSSL_HAVE_MLDSA +#define WOLFSSL_HAVE_MLKEM +#define WOLFSSL_HAVE_LMS +#define WOLFSSL_LMS_VERIFY_ONLY +#define WOLFSSL_HAVE_XMSS +#define WOLFSSL_XMSS_VERIFY_ONLY +/* Ascon is marked experimental in wolfSSL master and refuses to build + * without this opt-in. */ +#define WOLFSSL_EXPERIMENTAL_SETTINGS +#define HAVE_ASCON +#define HAVE_AES_KEYWRAP +#define HAVE_XCHACHA +#define HAVE_CMAC_KDF #endif /* WOLFSSL_USER_SETTINGS_H */ From 1ab901e5cbb1e55e5a00c4a248943f02da13a972 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 14:13:50 +0200 Subject: [PATCH 15/24] headers: complete PSA 1.4 + PQC extension macro/declaration surface Fill the previously empty PQC macros in crypto-pqc.h with the exact spec-defined implementations (ML-DSA/ML-KEM/SLH-DSA classifiers, key types, HashML-DSA encodings) and drop the nonstandard psa_ml_dsa_parameter_t / PSA_ML_DSA_PARAMETER_* convention. crypto_values.h: add 1.3/1.4 algorithm and policy values (EDDSA_CTX, KW/KWP, XOF algs, ECIES_SEC1, Ascon AEAD/hash, XChaCha20, SP800-108 KDFs, WPA3-SAE, WRAP/UNWRAP usage flags) and adopt the 1.4 example definitions of the sign-classification macros. crypto_sizes.h: ML-DSA/ML-KEM arms for sign/export sizes, KEM ciphertext sizes, key-wrap output sizes and the hash-suspend format constants; raise the max-size constants that ML-DSA now dominates. crypto.h/crypto_struct.h: declare psa_encapsulate/psa_decapsulate, the four *_with_context signature functions, key wrapping, the XOF operation API, hash suspend/resume, psa_attach_key and psa_check_key_usage; remove the legacy psa_ml_dsa_*/psa_lms_verify/ psa_xmss_verify declarations and add psa_xof_operation_t. Quarantine the legacy ML-DSA backend and its psa_api_test coverage pending the seed-based rewrite. --- src/psa_mldsa.c | 5 +- test/psa_server/psa_api_test.c | 7 +- wolfpsa/psa/crypto-pqc.h | 126 ++++++++++++++++++---------- wolfpsa/psa/crypto.h | 148 ++++++++++++++++++++++++--------- wolfpsa/psa/crypto_config.h | 5 +- wolfpsa/psa/crypto_sizes.h | 140 ++++++++++++++++++++++++++++++- wolfpsa/psa/crypto_struct.h | 10 +++ wolfpsa/psa/crypto_values.h | 100 ++++++++++++++++++++-- 8 files changed, 447 insertions(+), 94 deletions(-) diff --git a/src/psa_mldsa.c b/src/psa_mldsa.c index 7734d81..cdcc905 100644 --- a/src/psa_mldsa.c +++ b/src/psa_mldsa.c @@ -25,7 +25,10 @@ #include -#if defined(WOLFSSL_PSA_ENGINE) && defined(WOLFSSL_HAVE_MLDSA) +/* Quarantined pending the PSA 1.4 ML-DSA rewrite: this legacy code uses the + * removed psa_ml_dsa_parameter_t convention (wolfSSL levels 2/3/5) instead of + * the spec-mandated seed-based keys with bits 128/192/256. */ +#if 0 #include #include "psa_size.h" diff --git a/test/psa_server/psa_api_test.c b/test/psa_server/psa_api_test.c index 5758f6d..aeeff8e 100644 --- a/test/psa_server/psa_api_test.c +++ b/test/psa_server/psa_api_test.c @@ -5537,6 +5537,10 @@ static int test_asym_requires_verify_usage(void) return ret; } +/* Quarantined: the legacy psa_ml_dsa_* API and PSA_ML_DSA_PARAMETER_* + * convention were removed by the PSA 1.4 upgrade. Spec-API ML-DSA coverage + * lives in test/psa_mldsa_test.c. */ +#if 0 static int test_ml_dsa_verify_rejects_bad_signature_for_parameter( psa_ml_dsa_parameter_t parameter, size_t expected_private_key_length, size_t expected_public_key_length, size_t expected_signature_length, @@ -5614,10 +5618,11 @@ static int test_ml_dsa_verify_rejects_bad_signature_for_parameter( return TEST_SKIPPED; #endif } +#endif /* 0 */ static int test_ml_dsa_verify_rejects_bad_signature(void) { -#if defined(WOLFSSL_HAVE_MLDSA) +#if 0 /* legacy psa_ml_dsa_* API removed; see test/psa_mldsa_test.c */ int ran = 0; int skipped = 0; int ret; diff --git a/wolfpsa/psa/crypto-pqc.h b/wolfpsa/psa/crypto-pqc.h index 0c03c0c..abec3c4 100644 --- a/wolfpsa/psa/crypto-pqc.h +++ b/wolfpsa/psa/crypto-pqc.h @@ -18,57 +18,97 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ + +/* Implements the PSA Certified Crypto API 1.4 PQC extension appendix. */ + #ifndef PSA_CRYPTO_PQC_H #define PSA_CRYPTO_PQC_H #include "psa/crypto_types.h" typedef uint8_t psa_slh_dsa_family_t; -typedef uint8_t psa_ml_dsa_parameter_t; - -#define PSA_ALG_SHA_256_192 ((psa_algorithm_t)0x0200000E) -#define PSA_ALG_SHAKE128_256 ((psa_algorithm_t)0x02000016) -#define PSA_ALG_SHAKE256_192 ((psa_algorithm_t)0x02000017) -#define PSA_ALG_SHAKE256_256 ((psa_algorithm_t)0x02000018) -#define PSA_KEY_TYPE_ML_KEM_KEY_PAIR ((psa_key_type_t)0x7004) -#define PSA_KEY_TYPE_ML_KEM_PUBLIC_KEY ((psa_key_type_t)0x4004) -#define PSA_KEY_TYPE_IS_ML_KEM(type) -#define PSA_ALG_ML_KEM ((psa_algorithm_t)0x0c000200) -#define PSA_KEY_TYPE_ML_DSA_KEY_PAIR ((psa_key_type_t)0x7002) -#define PSA_KEY_TYPE_ML_DSA_PUBLIC_KEY ((psa_key_type_t)0x4002) -#define PSA_ML_DSA_PARAMETER_2 ((psa_ml_dsa_parameter_t)2) -#define PSA_ML_DSA_PARAMETER_3 ((psa_ml_dsa_parameter_t)3) -#define PSA_ML_DSA_PARAMETER_5 ((psa_ml_dsa_parameter_t)5) -#define PSA_KEY_TYPE_IS_ML_DSA(type) -#define PSA_ALG_ML_DSA ((psa_algorithm_t) 0x06004400) -#define PSA_ALG_DETERMINISTIC_ML_DSA ((psa_algorithm_t) 0x06004500) -#define PSA_ALG_HASH_ML_DSA(hash_alg) -#define PSA_ALG_IS_ML_DSA(alg) -#define PSA_ALG_IS_HASH_ML_DSA(alg) -#define PSA_ALG_IS_DETERMINISTIC_HASH_ML_DSA(alg) - -#define PSA_ALG_IS_HEDGED_HASH_ML_DSA(alg) -#define PSA_KEY_TYPE_SLH_DSA_KEY_PAIR(set) -#define PSA_KEY_TYPE_SLH_DSA_PUBLIC_KEY(set) -#define PSA_SLH_DSA_FAMILY_SHA2_S ((psa_slh_dsa_family_t) 0x02) -#define PSA_SLH_DSA_FAMILY_SHA2_F ((psa_slh_dsa_family_t) 0x04) -#define PSA_SLH_DSA_FAMILY_SHAKE_S ((psa_slh_dsa_family_t) 0x0b) -#define PSA_SLH_DSA_FAMILY_SHAKE_F ((psa_slh_dsa_family_t) 0x0d) -#define PSA_KEY_TYPE_IS_SLH_DSA(type) -#define PSA_KEY_TYPE_SLH_DSA_GET_FAMILY(type) -#define PSA_ALG_SLH_DSA ((psa_algorithm_t) 0x06004000) -#define PSA_ALG_DETERMINISTIC_SLH_DSA ((psa_algorithm_t) 0x06004100) -#define PSA_ALG_HASH_SLH_DSA(hash_alg) -#define PSA_ALG_IS_SLH_DSA(alg) -#define PSA_ALG_IS_HASH_SLH_DSA(alg) -#define PSA_ALG_IS_HEDGED_HASH_SLH_DSA(alg) + +/* --- Additional hash algorithm identifiers used by PQC --- */ + +#define PSA_ALG_SHA_256_192 ((psa_algorithm_t)0x0200000E) +#define PSA_ALG_SHAKE128_256 ((psa_algorithm_t)0x02000016) +#define PSA_ALG_SHAKE256_192 ((psa_algorithm_t)0x02000017) +#define PSA_ALG_SHAKE256_256 ((psa_algorithm_t)0x02000018) + +/* --- ML-KEM (CRYSTALS-Kyber) --- */ + +#define PSA_KEY_TYPE_ML_KEM_KEY_PAIR ((psa_key_type_t)0x7004) +#define PSA_KEY_TYPE_ML_KEM_PUBLIC_KEY ((psa_key_type_t)0x4004) +#define PSA_KEY_TYPE_IS_ML_KEM(type) \ + (PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR(type) == 0x4004) +#define PSA_ALG_ML_KEM ((psa_algorithm_t)0x0c000200) + +/* --- ML-DSA (CRYSTALS-Dilithium) --- */ + +#define PSA_KEY_TYPE_ML_DSA_KEY_PAIR ((psa_key_type_t)0x7002) +#define PSA_KEY_TYPE_ML_DSA_PUBLIC_KEY ((psa_key_type_t)0x4002) +#define PSA_KEY_TYPE_IS_ML_DSA(type) \ + (PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR(type) == 0x4002) +#define PSA_ALG_ML_DSA ((psa_algorithm_t)0x06004400) +#define PSA_ALG_DETERMINISTIC_ML_DSA ((psa_algorithm_t)0x06004500) +#define PSA_ALG_HASH_ML_DSA(hash_alg) \ + ((psa_algorithm_t)(0x06004600 | ((hash_alg) & 0x000000ff))) +#define PSA_ALG_DETERMINISTIC_HASH_ML_DSA(hash_alg) \ + ((psa_algorithm_t)(0x06004700 | ((hash_alg) & 0x000000ff))) +#define PSA_ALG_IS_ML_DSA(alg) \ + (((alg) & ~0x00000100U) == 0x06004400) +#define PSA_ALG_IS_HASH_ML_DSA(alg) \ + (((alg) & ~0x000001ffU) == 0x06004600) +#define PSA_ALG_IS_DETERMINISTIC_HASH_ML_DSA(alg) \ + (((alg) & ~0x000000ffU) == 0x06004700) +#define PSA_ALG_IS_HEDGED_HASH_ML_DSA(alg) \ + (((alg) & ~0x000000ffU) == 0x06004600) + +/* --- SLH-DSA (SPHINCS+) --- */ + +#define PSA_SLH_DSA_FAMILY_SHA2_S ((psa_slh_dsa_family_t)0x02) +#define PSA_SLH_DSA_FAMILY_SHA2_F ((psa_slh_dsa_family_t)0x04) +#define PSA_SLH_DSA_FAMILY_SHAKE_S ((psa_slh_dsa_family_t)0x0b) +#define PSA_SLH_DSA_FAMILY_SHAKE_F ((psa_slh_dsa_family_t)0x0d) +#define PSA_KEY_TYPE_SLH_DSA_KEY_PAIR(set) \ + ((psa_key_type_t)(0x7180 | ((set) & 0x007f))) +#define PSA_KEY_TYPE_SLH_DSA_PUBLIC_KEY(set) \ + ((psa_key_type_t)(0x4180 | ((set) & 0x007f))) +#define PSA_KEY_TYPE_IS_SLH_DSA(type) \ + ((PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR(type) & 0xff80) == 0x4180) +#define PSA_KEY_TYPE_IS_SLH_DSA_KEY_PAIR(type) \ + (((type) & 0xff80) == 0x7180) +#define PSA_KEY_TYPE_IS_SLH_DSA_PUBLIC_KEY(type) \ + (((type) & 0xff80) == 0x4180) +#define PSA_KEY_TYPE_SLH_DSA_GET_FAMILY(type) \ + ((psa_slh_dsa_family_t)((type) & 0x007f)) +#define PSA_ALG_SLH_DSA ((psa_algorithm_t)0x06004000) +#define PSA_ALG_DETERMINISTIC_SLH_DSA ((psa_algorithm_t)0x06004100) +#define PSA_ALG_HASH_SLH_DSA(hash_alg) \ + ((psa_algorithm_t)(0x06004200 | ((hash_alg) & 0x000000ff))) +#define PSA_ALG_DETERMINISTIC_HASH_SLH_DSA(hash_alg) \ + ((psa_algorithm_t)(0x06004300 | ((hash_alg) & 0x000000ff))) +#define PSA_ALG_IS_SLH_DSA(alg) \ + (((alg) & ~0x00000100U) == 0x06004000) +#define PSA_ALG_IS_HASH_SLH_DSA(alg) \ + (((alg) & ~0x000001ffU) == 0x06004200) +#define PSA_ALG_IS_DETERMINISTIC_HASH_SLH_DSA(alg) \ + (((alg) & ~0x000000ffU) == 0x06004300) +#define PSA_ALG_IS_HEDGED_HASH_SLH_DSA(alg) \ + (((alg) & ~0x000000ffU) == 0x06004200) + +/* --- LMS / HSS --- */ + #define PSA_KEY_TYPE_LMS_PUBLIC_KEY ((psa_key_type_t)0x4007) #define PSA_KEY_TYPE_HSS_PUBLIC_KEY ((psa_key_type_t)0x4008) -#define PSA_ALG_LMS ((psa_algorithm_t) 0x06004800) -#define PSA_ALG_HSS ((psa_algorithm_t) 0x06004900) -#define PSA_KEY_TYPE_XMSS_PUBLIC_KEY ((psa_key_type_t)0x400B) +#define PSA_ALG_LMS ((psa_algorithm_t)0x06004800) +#define PSA_ALG_HSS ((psa_algorithm_t)0x06004900) + +/* --- XMSS / XMSS^MT --- */ + +#define PSA_KEY_TYPE_XMSS_PUBLIC_KEY ((psa_key_type_t)0x400B) #define PSA_KEY_TYPE_XMSS_MT_PUBLIC_KEY ((psa_key_type_t)0x400D) -#define PSA_ALG_XMSS ((psa_algorithm_t) 0x06004A00) -#define PSA_ALG_XMSS_MT ((psa_algorithm_t) 0x06004B00) +#define PSA_ALG_XMSS ((psa_algorithm_t)0x06004A00) +#define PSA_ALG_XMSS_MT ((psa_algorithm_t)0x06004B00) #endif /* PSA_CRYPTO_PQC_H */ diff --git a/wolfpsa/psa/crypto.h b/wolfpsa/psa/crypto.h index 344046a..9aa57b1 100644 --- a/wolfpsa/psa/crypto.h +++ b/wolfpsa/psa/crypto.h @@ -41,6 +41,7 @@ typedef struct psa_hash_operation_s psa_hash_operation_t; typedef struct psa_mac_operation_s psa_mac_operation_t; typedef struct psa_cipher_operation_s psa_cipher_operation_t; typedef struct psa_aead_operation_s psa_aead_operation_t; +typedef struct psa_xof_operation_s psa_xof_operation_t; typedef struct psa_key_derivation_s psa_key_derivation_operation_t; typedef struct psa_sign_hash_interruptible_operation_s psa_sign_hash_interruptible_operation_t; typedef struct psa_verify_hash_interruptible_operation_s psa_verify_hash_interruptible_operation_t; @@ -97,6 +98,11 @@ void psa_reset_key_attributes(psa_key_attributes_t *attributes); psa_status_t psa_purge_key(psa_key_id_t key); +/* Check whether a key supports a given algorithm and usage combination. */ +psa_status_t psa_check_key_usage(psa_key_id_t key, + psa_algorithm_t alg, + psa_key_usage_t usage); + psa_status_t psa_copy_key(psa_key_id_t source_key, const psa_key_attributes_t *attributes, psa_key_id_t *target_key); @@ -155,6 +161,37 @@ psa_status_t psa_hash_abort(psa_hash_operation_t *operation); psa_status_t psa_hash_clone(const psa_hash_operation_t *source_operation, psa_hash_operation_t *target_operation); +/* Suspend an in-progress hash operation, saving state to a caller buffer. */ +psa_status_t psa_hash_suspend(psa_hash_operation_t *operation, + uint8_t *hash_state, + size_t hash_state_size, + size_t *hash_state_length); + +/* Restore a hash operation from a previously suspended state buffer. */ +psa_status_t psa_hash_resume(psa_hash_operation_t *operation, + const uint8_t *hash_state, + size_t hash_state_length); + +/* XOF (extendable-output function) multi-part operation. */ +static psa_xof_operation_t psa_xof_operation_init(void); + +psa_status_t psa_xof_setup(psa_xof_operation_t *operation, + psa_algorithm_t alg); + +psa_status_t psa_xof_set_context(psa_xof_operation_t *operation, + const uint8_t *context, + size_t context_length); + +psa_status_t psa_xof_update(psa_xof_operation_t *operation, + const uint8_t *input, + size_t input_length); + +psa_status_t psa_xof_output(psa_xof_operation_t *operation, + uint8_t *output, + size_t output_length); + +psa_status_t psa_xof_abort(psa_xof_operation_t *operation); + psa_status_t psa_mac_compute(psa_key_id_t key, psa_algorithm_t alg, const uint8_t *input, @@ -352,44 +389,44 @@ psa_status_t psa_verify_hash(psa_key_id_t key, const uint8_t *signature, size_t signature_length); -psa_status_t psa_ml_dsa_generate_key(psa_ml_dsa_parameter_t parameter, - uint8_t *private_key, - size_t private_key_size, - size_t *private_key_length, - uint8_t *public_key, - size_t public_key_size, - size_t *public_key_length); - -psa_status_t psa_ml_dsa_sign(psa_ml_dsa_parameter_t parameter, - const uint8_t *private_key, - size_t private_key_size, - const uint8_t *message, - size_t message_length, - uint8_t *signature, - size_t signature_size, - size_t *signature_length); - -psa_status_t psa_ml_dsa_verify(psa_ml_dsa_parameter_t parameter, - const uint8_t *public_key, - size_t public_key_size, - const uint8_t *message, - size_t message_length, - const uint8_t *signature, - size_t signature_length); - -psa_status_t psa_lms_verify(const uint8_t *public_key, - size_t public_key_size, - const uint8_t *message, - size_t message_length, - const uint8_t *signature, - size_t signature_length); - -psa_status_t psa_xmss_verify(const uint8_t *public_key, - size_t public_key_size, - const uint8_t *message, - size_t message_length, - const uint8_t *signature, - size_t signature_length); +/* PSA 1.4: sign/verify with an explicit context string (e.g. Ed25519ctx). */ +psa_status_t psa_sign_message_with_context(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *input, + size_t input_length, + const uint8_t *context, + size_t context_length, + uint8_t *signature, + size_t signature_size, + size_t *signature_length); + +psa_status_t psa_verify_message_with_context(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *input, + size_t input_length, + const uint8_t *context, + size_t context_length, + const uint8_t *signature, + size_t signature_length); + +psa_status_t psa_sign_hash_with_context(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *hash, + size_t hash_length, + const uint8_t *context, + size_t context_length, + uint8_t *signature, + size_t signature_size, + size_t *signature_length); + +psa_status_t psa_verify_hash_with_context(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *hash, + size_t hash_length, + const uint8_t *context, + size_t context_length, + const uint8_t *signature, + size_t signature_length); psa_status_t psa_asymmetric_encrypt(psa_key_id_t key, psa_algorithm_t alg, @@ -495,6 +532,22 @@ psa_status_t psa_key_agreement(psa_key_id_t private_key, const psa_key_attributes_t *attributes, psa_key_id_t *key); +/* PSA 1.4: KEM encapsulate/decapsulate (e.g. ML-KEM). */ +psa_status_t psa_encapsulate(psa_key_id_t key, + psa_algorithm_t alg, + const psa_key_attributes_t *attributes, + psa_key_id_t *output_key, + uint8_t *ciphertext, + size_t ciphertext_size, + size_t *ciphertext_length); + +psa_status_t psa_decapsulate(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *ciphertext, + size_t ciphertext_length, + const psa_key_attributes_t *attributes, + psa_key_id_t *output_key); + psa_status_t psa_generate_random(uint8_t *output, size_t output_size); @@ -507,6 +560,25 @@ psa_status_t psa_generate_key_custom(const psa_key_attributes_t *attributes, size_t custom_data_length, psa_key_id_t *key); +/* PSA 1.4: key wrapping and hardware-bound key attachment. */ +psa_status_t psa_wrap_key(psa_key_id_t wrapping_key, + psa_algorithm_t alg, + psa_key_id_t key, + uint8_t *data, + size_t data_size, + size_t *data_length); + +psa_status_t psa_unwrap_key(const psa_key_attributes_t *attributes, + psa_key_id_t wrapping_key, + psa_algorithm_t alg, + const uint8_t *data, + size_t data_length, + psa_key_id_t *key); + +psa_status_t psa_attach_key(const psa_key_attributes_t *attributes, + const uint8_t *label, + size_t label_length, + psa_key_id_t *key); void psa_interruptible_set_max_ops(uint32_t max_ops); diff --git a/wolfpsa/psa/crypto_config.h b/wolfpsa/psa/crypto_config.h index dd09403..ab2665e 100644 --- a/wolfpsa/psa/crypto_config.h +++ b/wolfpsa/psa/crypto_config.h @@ -32,4 +32,7 @@ #define PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_BASIC #define PSA_WANT_KEY_TYPE_RSA_PUBLIC_KEY -#endif +#define PSA_WANT_KEY_TYPE_ML_DSA +#define PSA_WANT_KEY_TYPE_ML_KEM + +#endif diff --git a/wolfpsa/psa/crypto_sizes.h b/wolfpsa/psa/crypto_sizes.h index 4b35235..1d44b6f 100644 --- a/wolfpsa/psa/crypto_sizes.h +++ b/wolfpsa/psa/crypto_sizes.h @@ -268,6 +268,11 @@ #define PSA_SIGN_OUTPUT_SIZE(key_type, key_bits, alg) \ (PSA_KEY_TYPE_IS_RSA(key_type) ? ((void) alg, PSA_BITS_TO_BYTES(key_bits)) : \ PSA_KEY_TYPE_IS_ECC(key_type) ? PSA_ECDSA_SIGNATURE_SIZE(key_bits) : \ + PSA_KEY_TYPE_IS_ML_DSA(key_type) ? \ + ((void) alg, \ + (key_bits) == 128u ? 2420u : \ + (key_bits) == 192u ? 3309u : \ + (key_bits) == 256u ? 4627u : 0u) : \ ((void) alg, 0u)) #define PSA_VENDOR_ECDSA_SIGNATURE_MAX_SIZE \ @@ -286,6 +291,10 @@ #undef PSA_SIGNATURE_MAX_SIZE #define PSA_SIGNATURE_MAX_SIZE PSA_BITS_TO_BYTES(PSA_VENDOR_RSA_MAX_KEY_BITS) #endif +#if (defined(PSA_WANT_KEY_TYPE_ML_DSA)) && (4627u > PSA_SIGNATURE_MAX_SIZE) +#undef PSA_SIGNATURE_MAX_SIZE +#define PSA_SIGNATURE_MAX_SIZE 4627u +#endif #define PSA_ASYMMETRIC_ENCRYPT_OUTPUT_SIZE(key_type, key_bits, alg) \ @@ -354,6 +363,18 @@ (key_type) == PSA_KEY_TYPE_DSA_PUBLIC_KEY ? PSA_KEY_EXPORT_DSA_PUBLIC_KEY_MAX_SIZE(key_bits) : \ PSA_KEY_TYPE_IS_ECC_KEY_PAIR(key_type) ? PSA_KEY_EXPORT_ECC_KEY_PAIR_MAX_SIZE(key_bits) : \ PSA_KEY_TYPE_IS_ECC_PUBLIC_KEY(key_type) ? PSA_KEY_EXPORT_ECC_PUBLIC_KEY_MAX_SIZE(key_bits) : \ + /* ML-DSA: key-pair export is the 32-byte seed; public key export by parameter set */ \ + (key_type) == PSA_KEY_TYPE_ML_DSA_KEY_PAIR ? 32u : \ + (key_type) == PSA_KEY_TYPE_ML_DSA_PUBLIC_KEY ? \ + ((key_bits) == 128u ? 1312u : \ + (key_bits) == 192u ? 1952u : \ + (key_bits) == 256u ? 2592u : 0u) : \ + /* ML-KEM: key-pair export is the 64-byte seed; public key export by parameter set */ \ + (key_type) == PSA_KEY_TYPE_ML_KEM_KEY_PAIR ? 64u : \ + (key_type) == PSA_KEY_TYPE_ML_KEM_PUBLIC_KEY ? \ + ((key_bits) == 512u ? 800u : \ + (key_bits) == 768u ? 1184u : \ + (key_bits) == 1024u ? 1568u : 0u) : \ 0u) @@ -361,6 +382,16 @@ (PSA_KEY_TYPE_IS_RSA(key_type) ? PSA_KEY_EXPORT_RSA_PUBLIC_KEY_MAX_SIZE(key_bits) : \ PSA_KEY_TYPE_IS_ECC(key_type) ? PSA_KEY_EXPORT_ECC_PUBLIC_KEY_MAX_SIZE(key_bits) : \ PSA_KEY_TYPE_IS_DH(key_type) ? PSA_BITS_TO_BYTES(key_bits) : \ + /* ML-DSA: both key-pair and public-key types export the public key bytes */ \ + PSA_KEY_TYPE_IS_ML_DSA(key_type) ? \ + ((key_bits) == 128u ? 1312u : \ + (key_bits) == 192u ? 1952u : \ + (key_bits) == 256u ? 2592u : 0u) : \ + /* ML-KEM: both key-pair and public-key types export the public key bytes */ \ + PSA_KEY_TYPE_IS_ML_KEM(key_type) ? \ + ((key_bits) == 512u ? 800u : \ + (key_bits) == 768u ? 1184u : \ + (key_bits) == 1024u ? 1568u : 0u) : \ 0u) @@ -412,11 +443,19 @@ #define PSA_EXPORT_PUBLIC_KEY_MAX_SIZE \ PSA_KEY_EXPORT_FFDH_PUBLIC_KEY_MAX_SIZE(PSA_VENDOR_FFDH_MAX_KEY_BITS) #endif +/* ML-DSA-87 public key (2592 bytes) dominates all classical public key sizes */ +#if defined(PSA_WANT_KEY_TYPE_ML_DSA) && (2592u > PSA_EXPORT_PUBLIC_KEY_MAX_SIZE) +#undef PSA_EXPORT_PUBLIC_KEY_MAX_SIZE +#define PSA_EXPORT_PUBLIC_KEY_MAX_SIZE 2592u +#endif #define PSA_EXPORT_KEY_PAIR_OR_PUBLIC_MAX_SIZE \ ((PSA_EXPORT_KEY_PAIR_MAX_SIZE > PSA_EXPORT_PUBLIC_KEY_MAX_SIZE) ? \ PSA_EXPORT_KEY_PAIR_MAX_SIZE : PSA_EXPORT_PUBLIC_KEY_MAX_SIZE) +/* PSA Crypto 1.4: alias for the max of key-pair and public-key export sizes */ +#define PSA_EXPORT_ASYMMETRIC_KEY_MAX_SIZE PSA_EXPORT_KEY_PAIR_OR_PUBLIC_MAX_SIZE + #define PSA_RAW_KEY_AGREEMENT_OUTPUT_SIZE(key_type, key_bits) \ ((PSA_KEY_TYPE_IS_ECC_KEY_PAIR(key_type) || \ @@ -517,4 +556,103 @@ #define PSA_CIPHER_FINISH_OUTPUT_MAX_SIZE \ (PSA_BLOCK_CIPHER_BLOCK_MAX_SIZE) -#endif + +/* --- KEM encapsulation output sizes (PSA 1.4 PQC extension) --- */ + +/** Sufficient ciphertext buffer size for psa_encapsulate(). + * + * For ML-KEM the ciphertext size depends on the parameter set (key_bits): + * ML-KEM-512 (512) -> 768 bytes + * ML-KEM-768 (768) -> 1088 bytes + * ML-KEM-1024(1024) -> 1568 bytes + */ +#define PSA_ENCAPSULATE_CIPHERTEXT_SIZE(key_type, key_bits, alg) \ + (PSA_KEY_TYPE_IS_ML_KEM(key_type) && (alg) == PSA_ALG_ML_KEM ? \ + ((key_bits) == 512u ? 768u : \ + (key_bits) == 768u ? 1088u : \ + (key_bits) == 1024u ? 1568u : 0u) : \ + 0u) + +/** Maximum ciphertext buffer size for psa_encapsulate(), for any supported + * key-encapsulation algorithm (ML-KEM-1024 dominates at 1568 bytes). */ +#define PSA_ENCAPSULATE_CIPHERTEXT_MAX_SIZE 1568u + + +/* --- Key wrapping output sizes (PSA 1.4) --- */ + +/** Sufficient output buffer size for psa_wrap_key(). + * + * For PSA_ALG_KW (AES Key Wrap, RFC 3394) the wrapped output is: + * ceil(export_size / 8) * 8 + 8 + * i.e. the plaintext (key export) rounded up to the next 8-byte boundary, + * plus the 8-byte integrity check value prepended by KW. + * For other/unknown algorithms the result is 0 (implementation must extend). + */ +#define PSA_WRAP_KEY_OUTPUT_SIZE(wrap_key_type, alg, key_type, key_bits) \ + (((alg) == PSA_ALG_KW) ? \ + ((void)(wrap_key_type), \ + PSA_ROUND_UP_TO_MULTIPLE(8u, \ + PSA_EXPORT_KEY_OUTPUT_SIZE(key_type, key_bits)) + 8u) : \ + 0u) + +/** Sufficient output buffer size for psa_wrap_key() for any asymmetric + * key pair supported by this implementation. + * + * Computed as PSA_WRAP_KEY_OUTPUT_SIZE over the largest key-pair export: + * RSA-4096 key pair (~2363 bytes) wrapped with AES-KW: + * ceil(2363/8)*8 + 8 = 2368 + 8 = 2376 bytes. + * (ML-DSA seed = 32 bytes and ML-KEM seed = 64 bytes are much smaller.) + */ +#define PSA_WRAP_KEY_PAIR_MAX_SIZE \ + (PSA_ROUND_UP_TO_MULTIPLE(8u, PSA_EXPORT_KEY_PAIR_MAX_SIZE) + 8u) + + +/* --- Hash suspend state sizes (PSA 1.4, spec-defined formulas) --- */ + +/** The size of the algorithm field in the psa_hash_suspend() output (bytes). + * This is a fixed 4-byte encoding of the algorithm identifier. */ +#define PSA_HASH_SUSPEND_ALGORITHM_FIELD_LENGTH ((size_t)4) + +/** The size of the input-length field in the psa_hash_suspend() output. + * Depends on the hash algorithm's internal counter width. */ +#define PSA_HASH_SUSPEND_INPUT_LENGTH_FIELD_LENGTH(alg) \ + ((alg) == PSA_ALG_MD2 ? 1u : \ + (alg) == PSA_ALG_MD4 || (alg) == PSA_ALG_MD5 || \ + (alg) == PSA_ALG_RIPEMD160 || (alg) == PSA_ALG_SHA_1 || \ + (alg) == PSA_ALG_SHA_224 || (alg) == PSA_ALG_SHA_256 ? 8u : \ + (alg) == PSA_ALG_SHA_512 || (alg) == PSA_ALG_SHA_384 || \ + (alg) == PSA_ALG_SHA_512_224 || (alg) == PSA_ALG_SHA_512_256 ? 16u : \ + 0u) + +/** The size of the hash-state field in the psa_hash_suspend() output. + * Holds the intermediate chaining value of the hash. */ +#define PSA_HASH_SUSPEND_HASH_STATE_FIELD_LENGTH(alg) \ + ((alg) == PSA_ALG_MD2 ? 64u : \ + (alg) == PSA_ALG_MD4 || (alg) == PSA_ALG_MD5 ? 16u : \ + (alg) == PSA_ALG_RIPEMD160 || (alg) == PSA_ALG_SHA_1 ? 20u : \ + (alg) == PSA_ALG_SHA_224 || (alg) == PSA_ALG_SHA_256 ? 32u : \ + (alg) == PSA_ALG_SHA_512 || (alg) == PSA_ALG_SHA_384 || \ + (alg) == PSA_ALG_SHA_512_224 || (alg) == PSA_ALG_SHA_512_256 ? 64u : \ + 0u) + +/** Sufficient hash suspend state buffer size for psa_hash_suspend(). + * + * Formula (spec-defined): + * PSA_HASH_SUSPEND_ALGORITHM_FIELD_LENGTH + * + PSA_HASH_SUSPEND_INPUT_LENGTH_FIELD_LENGTH(alg) + * + PSA_HASH_SUSPEND_HASH_STATE_FIELD_LENGTH(alg) + * + PSA_HASH_BLOCK_LENGTH(alg) - 1 + */ +#define PSA_HASH_SUSPEND_OUTPUT_SIZE(alg) \ + (PSA_HASH_SUSPEND_ALGORITHM_FIELD_LENGTH + \ + PSA_HASH_SUSPEND_INPUT_LENGTH_FIELD_LENGTH(alg) + \ + PSA_HASH_SUSPEND_HASH_STATE_FIELD_LENGTH(alg) + \ + PSA_HASH_BLOCK_LENGTH(alg) - 1u) + +/** Maximum suspend state size over all supported hash algorithms. + * SHA-512 / SHA-384 / SHA-512/224 / SHA-512/256 gives the largest output: + * 4 (alg) + 16 (len) + 64 (state) + 128 (block) - 1 = 211 bytes. */ +#define PSA_HASH_SUSPEND_OUTPUT_MAX_SIZE \ + (PSA_HASH_SUSPEND_ALGORITHM_FIELD_LENGTH + 16u + 64u + 128u - 1u) + +#endif diff --git a/wolfpsa/psa/crypto_struct.h b/wolfpsa/psa/crypto_struct.h index 2ab3571..510dbf4 100644 --- a/wolfpsa/psa/crypto_struct.h +++ b/wolfpsa/psa/crypto_struct.h @@ -42,6 +42,16 @@ static inline psa_hash_operation_t psa_hash_operation_init(void) return v; } +typedef struct psa_xof_operation_s { + uintptr_t opaque; +} psa_xof_operation_t; +#define PSA_XOF_OPERATION_INIT { 0 } +static inline psa_xof_operation_t psa_xof_operation_init(void) +{ + const psa_xof_operation_t v = PSA_XOF_OPERATION_INIT; + return v; +} + typedef struct psa_cipher_operation_s { uintptr_t opaque; } psa_cipher_operation_t; diff --git a/wolfpsa/psa/crypto_values.h b/wolfpsa/psa/crypto_values.h index 6bcb67e..5c3be4c 100644 --- a/wolfpsa/psa/crypto_values.h +++ b/wolfpsa/psa/crypto_values.h @@ -400,6 +400,8 @@ #define PSA_ALG_SHAKE256_512 ((psa_algorithm_t) 0x02000015) +#define PSA_ALG_ASCON_HASH256 ((psa_algorithm_t) 0x02000019) + #define PSA_ALG_ANY_HASH ((psa_algorithm_t) 0x020000ff) @@ -506,6 +508,10 @@ #define PSA_ALG_CHACHA20_POLY1305 ((psa_algorithm_t) 0x05100500) +#define PSA_ALG_XCHACHA20_POLY1305 ((psa_algorithm_t) 0x05100600) + +#define PSA_ALG_ASCON_AEAD128 ((psa_algorithm_t) 0x05100700) + #define PSA_ALG_AEAD_TAG_LENGTH_MASK ((psa_algorithm_t) 0x003f0000) #define PSA_AEAD_TAG_LENGTH_OFFSET 16 @@ -598,6 +604,8 @@ #define PSA_ALG_PURE_EDDSA ((psa_algorithm_t) 0x06000800) +#define PSA_ALG_EDDSA_CTX ((psa_algorithm_t) 0x06000A00) + #define PSA_ALG_HASH_EDDSA_BASE ((psa_algorithm_t) 0x06000900) #define PSA_ALG_IS_HASH_EDDSA(alg) \ (((alg) & ~PSA_ALG_HASH_MASK) == PSA_ALG_HASH_EDDSA_BASE) @@ -615,18 +623,18 @@ #define PSA_ALG_IS_SIGN_HASH(alg) \ - (PSA_ALG_IS_RSA_PSS(alg) || PSA_ALG_IS_RSA_PKCS1V15_SIGN(alg) || \ - PSA_ALG_IS_ECDSA(alg) || PSA_ALG_IS_HASH_EDDSA(alg) || \ - PSA_ALG_IS_VENDOR_HASH_AND_SIGN(alg)) + (PSA_ALG_IS_SIGN(alg) && \ + (alg) != PSA_ALG_PURE_EDDSA && (alg) != PSA_ALG_EDDSA_CTX) #define PSA_ALG_IS_SIGN_MESSAGE(alg) \ - (PSA_ALG_IS_SIGN_HASH(alg) || (alg) == PSA_ALG_PURE_EDDSA) + (PSA_ALG_IS_SIGN(alg) && \ + (alg) != PSA_ALG_ECDSA_ANY && (alg) != PSA_ALG_RSA_PKCS1V15_SIGN_RAW) #define PSA_ALG_IS_HASH_AND_SIGN(alg) \ - (PSA_ALG_IS_SIGN_HASH(alg) && \ - ((alg) & PSA_ALG_HASH_MASK) != 0) + (PSA_ALG_IS_RSA_PSS(alg) || PSA_ALG_IS_RSA_PKCS1V15_SIGN(alg) || \ + PSA_ALG_IS_ECDSA(alg) || PSA_ALG_IS_HASH_EDDSA(alg)) #define PSA_ALG_SIGN_GET_HASH(alg) \ @@ -725,6 +733,15 @@ (PSA_ALG_IS_PBKDF2_HMAC(kdf_alg) || \ ((kdf_alg) == PSA_ALG_PBKDF2_AES_CMAC_PRF_128)) + +#define PSA_ALG_SP800_108_COUNTER_HMAC(hash_alg) \ + ((psa_algorithm_t) (0x08000700 | ((hash_alg) & 0x000000ff))) + +#define PSA_ALG_SP800_108_COUNTER_CMAC ((psa_algorithm_t) 0x08000800) + +#define PSA_ALG_IS_SP800_108_COUNTER_HMAC(alg) \ + (((alg) & ~0x000000ff) == 0x08000700) + #define PSA_ALG_KEY_DERIVATION_MASK ((psa_algorithm_t) 0xfe00ffff) #define PSA_ALG_KEY_AGREEMENT_MASK ((psa_algorithm_t) 0xffff0000) @@ -772,7 +789,7 @@ #define PSA_ALG_GET_HASH(alg) \ - (((alg) & 0x000000ff) == 0 ? ((psa_algorithm_t) 0) : 0x02000000 | ((alg) & 0x000000ff)) + (((alg) & 0x000000ff) == 0 ? PSA_ALG_NONE : 0x02000000 | ((alg) & 0x000000ff)) @@ -987,7 +1004,8 @@ static inline int psa_key_id_is_null(psa_key_id_t key) #define PSA_ALG_IS_SPAKE2P_CMAC(alg) \ (((alg) & 0xffff0000u) == PSA_ALG_SPAKE2P_CMAC_BASE) -#define PSA_ALG_IS_PAKE(alg) (PSA_ALG_IS_JPAKE(alg) || PSA_ALG_IS_SPAKE2P(alg)) +#define PSA_ALG_IS_PAKE(alg) \ + (PSA_ALG_IS_JPAKE(alg) || PSA_ALG_IS_SPAKE2P(alg) || PSA_ALG_IS_WPA3_SAE(alg)) #define PSA_KEY_TYPE_SPAKE2P_KEY_PAIR(curve) \ ((psa_key_type_t) (0x7200u | ((curve) & 0xffu))) @@ -1008,4 +1026,68 @@ static inline int psa_key_id_is_null(psa_key_id_t key) ((psa_ecc_family_t)((type) & 0xffu)) -#endif +/* PSA Crypto API 1.4: XOF algorithms (category 0x0D) */ + +#define PSA_ALG_SHAKE128 ((psa_algorithm_t) 0x0D000100) + +#define PSA_ALG_SHAKE256 ((psa_algorithm_t) 0x0D000200) + +#define PSA_ALG_ASCON_XOF128 ((psa_algorithm_t) 0x0D000300) + +#define PSA_ALG_ASCON_CXOF128 ((psa_algorithm_t) 0x0D008300) + +#define PSA_ALG_IS_XOF(alg) \ + (((alg) & 0x7f000000) == 0x0D000000) + +#define PSA_ALG_XOF_HAS_CONTEXT(alg) \ + (((alg) & 0x00008000) != 0) + + +/* PSA Crypto API 1.4: Key wrapping algorithms (category 0x0B) */ + +#define PSA_ALG_KW ((psa_algorithm_t) 0x0B400100) + +#define PSA_ALG_KWP ((psa_algorithm_t) 0x0BC00200) + +#define PSA_ALG_IS_KEY_WRAP(alg) \ + (((alg) & 0x7f000000) == 0x0b000000) + + +/* PSA Crypto API 1.4: Key encapsulation algorithms (category 0x0C) */ + +#define PSA_ALG_ECIES_SEC1 ((psa_algorithm_t) 0x0c000100) + +#define PSA_ALG_IS_KEY_ENCAPSULATION(alg) \ + (((alg) & 0x7f000000) == 0x0c000000) + + +/* PSA Crypto API 1.4: WPA3-SAE PAKE algorithms */ + +#define PSA_ALG_WPA3_SAE_FIXED(hash_alg) \ + ((psa_algorithm_t) (0x0a000800 | ((hash_alg) & 0x000000ff))) + +#define PSA_ALG_WPA3_SAE_GDH(hash_alg) \ + ((psa_algorithm_t) (0x0a000900 | ((hash_alg) & 0x000000ff))) + +#define PSA_ALG_WPA3_SAE_H2E(hash_alg) \ + ((psa_algorithm_t) (0x08800400 | ((hash_alg) & 0x000000ff))) + +#define PSA_ALG_IS_WPA3_SAE(alg) \ + (((alg) & ~0x000001ff) == 0x0a000800) + + +/* PSA Crypto API 1.4: new symmetric key types */ + +#define PSA_KEY_TYPE_XCHACHA20 ((psa_key_type_t) 0x2007) + +#define PSA_KEY_TYPE_ASCON ((psa_key_type_t) 0x2008) + + +/* PSA Crypto API 1.4: key wrapping usage flags */ + +#define PSA_KEY_USAGE_WRAP ((psa_key_usage_t) 0x00010000) + +#define PSA_KEY_USAGE_UNWRAP ((psa_key_usage_t) 0x00020000) + + +#endif From 6390a47f2a4f73242d275d84721694f1a58daacc Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 14:22:00 +0200 Subject: [PATCH 16/24] pqc: seed-based ML-DSA and ML-KEM backends per PSA 1.4 PQC extension Rewrite the ML-DSA backend around the spec key format: key pairs are the 32-byte FIPS 204 seed xi with bits 128/192/256, public keys are raw pk bytes. Sign/verify dispatch covers hedged and deterministic pure ML-DSA (wc_MlDsaKey_SignCtx / SignCtxWithSeed with a zero rnd) and HashML-DSA over pre-computed digests (SignCtxHash/VerifyCtxHash), with signing contexts up to 255 bytes. Rewrite the ML-KEM backend against wc_mlkem.h: key pairs are the 64-byte d||z seed (FIPS 203) expanded via wc_MlKemKey_MakeKeyWithRandom on each use; encapsulation accepts raw public keys or seeds and decapsulation enforces exact ciphertext length, relying on ML-KEM implicit rejection. Breaking change: the legacy psa_ml_dsa_* public API and its level-as-bits (2/3/5) convention are gone. --- src/psa_mldsa.c | 525 +++++++++++++++++++++++++++++++++++------------- src/psa_mlkem.c | 332 ++++++++++++++++-------------- 2 files changed, 564 insertions(+), 293 deletions(-) diff --git a/src/psa_mldsa.c b/src/psa_mldsa.c index cdcc905..f75d82f 100644 --- a/src/psa_mldsa.c +++ b/src/psa_mldsa.c @@ -25,160 +25,266 @@ #include -/* Quarantined pending the PSA 1.4 ML-DSA rewrite: this legacy code uses the - * removed psa_ml_dsa_parameter_t convention (wolfSSL levels 2/3/5) instead of - * the spec-mandated seed-based keys with bits 128/192/256. */ -#if 0 +#if defined(WOLFSSL_PSA_ENGINE) && defined(WOLFSSL_HAVE_MLDSA) #include #include "psa_size.h" +#include "psa_pqc_internal.h" #include #include #include #include #include -#include #include #include -/* Convert ML-DSA parameter to wolfCrypt key type */ -static int psa_ml_dsa_parameter_to_type(psa_ml_dsa_parameter_t parameter) +/* Map PSA security-strength bits to wolfCrypt ML-DSA level constants. + * Returns WC_ML_DSA_44/65/87 on success, or -1 for unsupported sizes. */ +static int mldsa_bits_to_level(size_t bits) { - switch (parameter) { - case PSA_ML_DSA_PARAMETER_2: + switch (bits) { + case 128: return WC_ML_DSA_44; - case PSA_ML_DSA_PARAMETER_3: + case 192: return WC_ML_DSA_65; - case PSA_ML_DSA_PARAMETER_5: + case 256: return WC_ML_DSA_87; default: return -1; } } -/* Generate an ML-DSA key pair */ -psa_status_t psa_ml_dsa_generate_key(psa_ml_dsa_parameter_t parameter, - uint8_t *private_key, - size_t private_key_size, - size_t *private_key_length, - uint8_t *public_key, - size_t public_key_size, - size_t *public_key_length) +/* Map a PSA hash algorithm (extracted via PSA_ALG_GET_HASH) to a wolfCrypt + * wc_HashType for use with wc_MlDsaKey_SignCtxHash / VerifyCtxHash. + * Covers the hashes listed in FIPS 204 Table 1 for HashML-DSA. */ +static int mldsa_psa_hash_to_wc(psa_algorithm_t hash_alg) +{ + switch (hash_alg) { + case PSA_ALG_SHA_256: + return WC_HASH_TYPE_SHA256; + case PSA_ALG_SHA_384: + return WC_HASH_TYPE_SHA384; + case PSA_ALG_SHA_512: + return WC_HASH_TYPE_SHA512; + case PSA_ALG_SHA3_256: + return WC_HASH_TYPE_SHA3_256; + case PSA_ALG_SHA3_384: + return WC_HASH_TYPE_SHA3_384; + case PSA_ALG_SHA3_512: + return WC_HASH_TYPE_SHA3_512; + case PSA_ALG_SHAKE128_256: + return WC_HASH_TYPE_SHAKE128; + case PSA_ALG_SHAKE256_512: + return WC_HASH_TYPE_SHAKE256; + default: + return WC_HASH_TYPE_NONE; + } +} + +/* Initialise key and set params from bits. Key must be freed by caller via + * wc_MlDsaKey_Free() even when this function fails after wc_MlDsaKey_Init. */ +static psa_status_t mldsa_key_init(wc_MlDsaKey *key, size_t bits) { int ret; - wc_MlDsaKey key; - int type; - WC_RNG rng; - word32 priv_len; - word32 pub_len; + int level; - /* Convert parameter to wolfCrypt key type */ - type = psa_ml_dsa_parameter_to_type(parameter); - if (type < 0) { + level = mldsa_bits_to_level(bits); + if (level < 0) { return PSA_ERROR_NOT_SUPPORTED; } - if ((wolfpsa_check_word32_length(private_key_size) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(public_key_size) != PSA_SUCCESS)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - /* Initialize ML-DSA key */ - ret = wc_MlDsaKey_Init(&key, NULL, wolfPSA_GetDefaultDevID()); + ret = wc_MlDsaKey_Init(key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } - ret = wc_MlDsaKey_SetParams(&key, (byte)type); + + ret = wc_MlDsaKey_SetParams(key, (byte)level); if (ret != 0) { - wc_MlDsaKey_Free(&key); + wc_MlDsaKey_Free(key); return wc_error_to_psa_status(ret); } - /* Initialize RNG */ - ret = wc_InitRng(&rng); + return PSA_SUCCESS; +} + +/* Expand seed into a fully populated key pair. Caller must have already + * called mldsa_key_init() on *key. */ +static psa_status_t mldsa_expand_seed(wc_MlDsaKey *key, const uint8_t *seed) +{ + int ret; + + ret = wc_MlDsaKey_MakeKeyFromSeed(key, (const byte*)seed); if (ret != 0) { - wc_MlDsaKey_Free(&key); return wc_error_to_psa_status(ret); } - /* Generate key pair */ - ret = wc_MlDsaKey_MakeKey(&key, &rng); - if (ret != 0) { - wc_FreeRng(&rng); - wc_MlDsaKey_Free(&key); - return wc_error_to_psa_status(ret); + return PSA_SUCCESS; +} + +/* + * wolfpsa_mldsa_generate_seed + * + * Generate a fresh 32-byte FIPS 204 seed xi for an ML-DSA key pair. + * Validates that the seed can successfully expand into a key before + * returning it to the caller. + */ +psa_status_t wolfpsa_mldsa_generate_seed(size_t bits, uint8_t *seed) +{ + psa_status_t status; + wc_MlDsaKey key; + + /* Validate bits before touching anything. */ + if (mldsa_bits_to_level(bits) < 0) { + return PSA_ERROR_NOT_SUPPORTED; } - priv_len = (word32)private_key_size; - ret = wc_MlDsaKey_ExportPrivRaw(&key, private_key, &priv_len); - if (ret != 0) { - wc_FreeRng(&rng); + /* Fill seed with random bytes via PSA RNG. */ + status = psa_generate_random(seed, WOLFPSA_MLDSA_SEED_SIZE); + if (status != PSA_SUCCESS) { + return status; + } + + /* Validate that the seed is well-formed by expanding it. */ + status = mldsa_key_init(&key, bits); + if (status != PSA_SUCCESS) { + wc_ForceZero(seed, WOLFPSA_MLDSA_SEED_SIZE); + return status; + } + + status = mldsa_expand_seed(&key, seed); + wc_MlDsaKey_Free(&key); + + if (status != PSA_SUCCESS) { + wc_ForceZero(seed, WOLFPSA_MLDSA_SEED_SIZE); + return status; + } + + return PSA_SUCCESS; +} + +/* + * wolfpsa_mldsa_export_public + * + * Expand the 32-byte seed and export the raw FIPS 204 public key bytes + * (1312 / 1952 / 2592 bytes for ML-DSA-44/65/87). + */ +psa_status_t wolfpsa_mldsa_export_public(size_t bits, const uint8_t *seed, + uint8_t *out, size_t out_size, size_t *out_length) +{ + psa_status_t status; + wc_MlDsaKey key; + int pub_size; + word32 pub_len; + int ret; + + if (wolfpsa_check_word32_length(out_size) != PSA_SUCCESS) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + status = mldsa_key_init(&key, bits); + if (status != PSA_SUCCESS) { + return status; + } + + status = mldsa_expand_seed(&key, seed); + if (status != PSA_SUCCESS) { wc_MlDsaKey_Free(&key); - return wc_error_to_psa_status(ret); + return status; } - pub_len = (word32)public_key_size; - ret = wc_MlDsaKey_ExportPubRaw(&key, public_key, &pub_len); - if (ret != 0) { - wc_FreeRng(&rng); + /* Check output buffer size before exporting. */ + pub_size = wc_MlDsaKey_PubSize(&key); + if (pub_size < 0) { wc_MlDsaKey_Free(&key); - return wc_error_to_psa_status(ret); + return wc_error_to_psa_status(pub_size); + } + if (out_size < (size_t)pub_size) { + wc_MlDsaKey_Free(&key); + return PSA_ERROR_BUFFER_TOO_SMALL; } - *private_key_length = priv_len; - *public_key_length = pub_len; - wc_FreeRng(&rng); + pub_len = (word32)out_size; + ret = wc_MlDsaKey_ExportPubRaw(&key, out, &pub_len); wc_MlDsaKey_Free(&key); + if (ret != 0) { + return wc_error_to_psa_status(ret); + } + + *out_length = (size_t)pub_len; return PSA_SUCCESS; } -/* Sign a message with ML-DSA */ -psa_status_t psa_ml_dsa_sign(psa_ml_dsa_parameter_t parameter, - const uint8_t *private_key, - size_t private_key_size, - const uint8_t *message, - size_t message_length, - uint8_t *signature, - size_t signature_size, - size_t *signature_length) +/* + * wolfpsa_mldsa_sign + * + * Sign a message or pre-computed hash with an ML-DSA key pair seed. + * + * Algorithm dispatch: + * input_is_hash == 0 (raw message): + * PSA_ALG_ML_DSA → wc_MlDsaKey_SignCtx (hedged, uses RNG) + * PSA_ALG_DETERMINISTIC_ML_DSA → wc_MlDsaKey_SignCtxWithSeed (zero seed) + * HashML-DSA variants → PSA_ERROR_INVALID_ARGUMENT (front-end + * pre-hashes before calling this function) + * input_is_hash == 1 (pre-computed hash): + * PSA_ALG_IS_HASH_ML_DSA → wc_MlDsaKey_SignCtxHash (hedged) + * PSA_ALG_IS_DETERMINISTIC_HASH_ML_DSA → wc_MlDsaKey_SignCtxHashWithSeed + * PSA_ALG_ML_DSA / PSA_ALG_DETERMINISTIC_ML_DSA → PSA_ERROR_INVALID_ARGUMENT + */ +psa_status_t wolfpsa_mldsa_sign(size_t bits, const uint8_t *key_data, + size_t key_data_length, psa_algorithm_t alg, + const uint8_t *context, size_t context_length, + const uint8_t *input, size_t input_length, int input_is_hash, + uint8_t *signature, size_t signature_size, size_t *signature_length) { - int ret; + psa_status_t status; wc_MlDsaKey key; - int type; - int sig_size; WC_RNG rng; - word32 sigLen; - - /* Convert parameter to wolfCrypt key type */ - type = psa_ml_dsa_parameter_to_type(parameter); - if (type < 0) { - return PSA_ERROR_NOT_SUPPORTED; + int rng_inited = 0; + int sig_size; + word32 sig_len; + int ret; + /* FIPS 204 deterministic rnd: 32 zero bytes. */ + static const byte zero32[WOLFPSA_MLDSA_SEED_SIZE] = { 0 }; + /* ctx/ctxLen helpers for the wolfCrypt call. */ + const byte *ctx; + byte ctx_len; + + /* key_data must be exactly the 32-byte seed. */ + if (key_data_length != WOLFPSA_MLDSA_SEED_SIZE) { + return PSA_ERROR_INVALID_ARGUMENT; + } + /* context_length is transmitted as a single byte to wolfCrypt. */ + if (context_length > 255u) { + return PSA_ERROR_INVALID_ARGUMENT; } - if ((wolfpsa_check_word32_length(private_key_size) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(message_length) != PSA_SUCCESS) || + if ((wolfpsa_check_word32_length(input_length) != PSA_SUCCESS) || (wolfpsa_check_word32_length(signature_size) != PSA_SUCCESS)) { return PSA_ERROR_INVALID_ARGUMENT; } - /* Initialize ML-DSA key */ - ret = wc_MlDsaKey_Init(&key, NULL, wolfPSA_GetDefaultDevID()); - if (ret != 0) { - return wc_error_to_psa_status(ret); + /* Resolve context pointer: pass NULL to wolfCrypt when empty. */ + if (context_length == 0) { + ctx = NULL; + ctx_len = 0; } - ret = wc_MlDsaKey_SetParams(&key, (byte)type); - if (ret != 0) { - wc_MlDsaKey_Free(&key); - return wc_error_to_psa_status(ret); + else { + ctx = (const byte*)context; + ctx_len = (byte)context_length; } - /* Import private key */ - ret = wc_MlDsaKey_ImportPrivRaw(&key, private_key, (word32)private_key_size); - if (ret != 0) { + status = mldsa_key_init(&key, bits); + if (status != PSA_SUCCESS) { + return status; + } + + status = mldsa_expand_seed(&key, key_data); + if (status != PSA_SUCCESS) { wc_MlDsaKey_Free(&key); - return wc_error_to_psa_status(ret); + return status; } - /* Check signature buffer size */ + /* Verify output buffer size. */ sig_size = wc_MlDsaKey_SigSize(&key); if (sig_size < 0) { wc_MlDsaKey_Free(&key); @@ -189,77 +295,222 @@ psa_status_t psa_ml_dsa_sign(psa_ml_dsa_parameter_t parameter, return PSA_ERROR_BUFFER_TOO_SMALL; } - ret = wc_InitRng(&rng); - if (ret != 0) { - wc_MlDsaKey_Free(&key); - return wc_error_to_psa_status(ret); + sig_len = (word32)signature_size; + + if (input_is_hash == 0) { + /* Signing over the raw message. */ + if (alg == PSA_ALG_ML_DSA) { + /* Hedged: requires an RNG. */ + ret = wc_InitRng(&rng); + if (ret != 0) { + wc_MlDsaKey_Free(&key); + return wc_error_to_psa_status(ret); + } + rng_inited = 1; + ret = wc_MlDsaKey_SignCtx(&key, ctx, ctx_len, + signature, &sig_len, + (const byte*)input, (word32)input_length, + &rng); + } + else if (alg == PSA_ALG_DETERMINISTIC_ML_DSA) { + /* Deterministic: rnd is 32 zero bytes per FIPS 204. */ + ret = wc_MlDsaKey_SignCtxWithSeed(&key, ctx, ctx_len, + signature, &sig_len, + (const byte*)input, + (word32)input_length, zero32); + } + else if (PSA_ALG_IS_HASH_ML_DSA(alg) || + PSA_ALG_IS_DETERMINISTIC_HASH_ML_DSA(alg)) { + /* Front-end always pre-hashes before calling here; receiving a + * raw message for HashML-DSA is a caller contract violation. */ + wc_MlDsaKey_Free(&key); + return PSA_ERROR_INVALID_ARGUMENT; + } + else { + wc_MlDsaKey_Free(&key); + return PSA_ERROR_NOT_SUPPORTED; + } + } + else { + /* input is a pre-computed hash digest. */ + psa_algorithm_t hash_alg; + int wc_hash; + + if (PSA_ALG_IS_HASH_ML_DSA(alg)) { + hash_alg = PSA_ALG_GET_HASH(alg); + wc_hash = mldsa_psa_hash_to_wc(hash_alg); + if (wc_hash == WC_HASH_TYPE_NONE) { + wc_MlDsaKey_Free(&key); + return PSA_ERROR_NOT_SUPPORTED; + } + ret = wc_InitRng(&rng); + if (ret != 0) { + wc_MlDsaKey_Free(&key); + return wc_error_to_psa_status(ret); + } + rng_inited = 1; + ret = wc_MlDsaKey_SignCtxHash(&key, ctx, ctx_len, + signature, &sig_len, + (const byte*)input, + (word32)input_length, + wc_hash, &rng); + } + else if (PSA_ALG_IS_DETERMINISTIC_HASH_ML_DSA(alg)) { + hash_alg = PSA_ALG_GET_HASH(alg); + wc_hash = mldsa_psa_hash_to_wc(hash_alg); + if (wc_hash == WC_HASH_TYPE_NONE) { + wc_MlDsaKey_Free(&key); + return PSA_ERROR_NOT_SUPPORTED; + } + ret = wc_MlDsaKey_SignCtxHashWithSeed(&key, ctx, ctx_len, + signature, &sig_len, + (const byte*)input, + (word32)input_length, + wc_hash, zero32); + } + else { + /* Pure ML-DSA with a pre-hashed input is a protocol error. */ + wc_MlDsaKey_Free(&key); + return PSA_ERROR_INVALID_ARGUMENT; + } } - /* Sign message (ML-DSA pure, empty context per FIPS 204) */ - sigLen = (word32)signature_size; - ret = wc_MlDsaKey_SignCtx(&key, NULL, 0, - signature, &sigLen, - message, (word32)message_length, &rng); - if (ret != 0) { + if (rng_inited) { wc_FreeRng(&rng); - wc_MlDsaKey_Free(&key); - return wc_error_to_psa_status(ret); } - *signature_length = sigLen; - - wc_FreeRng(&rng); wc_MlDsaKey_Free(&key); + if (ret != 0) { + return wc_error_to_psa_status(ret); + } + + *signature_length = (size_t)sig_len; return PSA_SUCCESS; } -/* Verify a signature with ML-DSA */ -psa_status_t psa_ml_dsa_verify(psa_ml_dsa_parameter_t parameter, - const uint8_t *public_key, - size_t public_key_size, - const uint8_t *message, - size_t message_length, - const uint8_t *signature, - size_t signature_length) +/* + * wolfpsa_mldsa_verify + * + * Verify an ML-DSA signature. Accepts both key-pair (seed) and bare public + * key material. The algorithm dispatch mirrors wolfpsa_mldsa_sign; hedged + * and deterministic variants produce identical signatures and verify the + * same way, so PSA_ALG_ML_DSA and PSA_ALG_DETERMINISTIC_ML_DSA both map to + * wc_MlDsaKey_VerifyCtx, and the Hash variants both map to + * wc_MlDsaKey_VerifyCtxHash. + */ +psa_status_t wolfpsa_mldsa_verify(size_t bits, psa_key_type_t key_type, + const uint8_t *key_data, size_t key_data_length, psa_algorithm_t alg, + const uint8_t *context, size_t context_length, + const uint8_t *input, size_t input_length, int input_is_hash, + const uint8_t *signature, size_t signature_length) { - int ret; + psa_status_t status; wc_MlDsaKey key; - int type; int verify_res = 0; + int ret; + const byte *ctx; + byte ctx_len; - /* Convert parameter to wolfCrypt key type */ - type = psa_ml_dsa_parameter_to_type(parameter); - if (type < 0) { - return PSA_ERROR_NOT_SUPPORTED; + if (context_length > 255u) { + return PSA_ERROR_INVALID_ARGUMENT; } - if ((wolfpsa_check_word32_length(public_key_size) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(message_length) != PSA_SUCCESS) || + if ((wolfpsa_check_word32_length(key_data_length) != PSA_SUCCESS) || + (wolfpsa_check_word32_length(input_length) != PSA_SUCCESS) || (wolfpsa_check_word32_length(signature_length) != PSA_SUCCESS)) { return PSA_ERROR_INVALID_ARGUMENT; } - /* Initialize ML-DSA key */ - ret = wc_MlDsaKey_Init(&key, NULL, wolfPSA_GetDefaultDevID()); - if (ret != 0) { - return wc_error_to_psa_status(ret); + /* Resolve context pointer: pass NULL when empty. */ + if (context_length == 0) { + ctx = NULL; + ctx_len = 0; } - ret = wc_MlDsaKey_SetParams(&key, (byte)type); - if (ret != 0) { - wc_MlDsaKey_Free(&key); - return wc_error_to_psa_status(ret); + else { + ctx = (const byte*)context; + ctx_len = (byte)context_length; } - /* Import public key */ - ret = wc_MlDsaKey_ImportPubRaw(&key, public_key, (word32)public_key_size); - if (ret != 0) { + status = mldsa_key_init(&key, bits); + if (status != PSA_SUCCESS) { + return status; + } + + /* Load the key depending on whether we have a key pair (seed) or a + * bare public key. */ + if (key_type == PSA_KEY_TYPE_ML_DSA_KEY_PAIR) { + /* key_data is the 32-byte seed; expand to obtain the public key. */ + if (key_data_length != WOLFPSA_MLDSA_SEED_SIZE) { + wc_MlDsaKey_Free(&key); + return PSA_ERROR_INVALID_ARGUMENT; + } + status = mldsa_expand_seed(&key, key_data); + if (status != PSA_SUCCESS) { + wc_MlDsaKey_Free(&key); + return status; + } + } + else if (key_type == PSA_KEY_TYPE_ML_DSA_PUBLIC_KEY) { + /* key_data is raw pk bytes; import directly. */ + ret = wc_MlDsaKey_ImportPubRaw(&key, (const byte*)key_data, + (word32)key_data_length); + if (ret != 0) { + wc_MlDsaKey_Free(&key); + return wc_error_to_psa_status(ret); + } + } + else { wc_MlDsaKey_Free(&key); - return wc_error_to_psa_status(ret); + return PSA_ERROR_INVALID_ARGUMENT; } - /* Verify signature (ML-DSA pure, empty context per FIPS 204) */ - ret = wc_MlDsaKey_VerifyCtx(&key, signature, (word32)signature_length, - NULL, 0, - message, (word32)message_length, &verify_res); + if (input_is_hash == 0) { + /* Verify over the raw message. */ + if (alg == PSA_ALG_ML_DSA || alg == PSA_ALG_DETERMINISTIC_ML_DSA) { + ret = wc_MlDsaKey_VerifyCtx(&key, + (const byte*)signature, + (word32)signature_length, + ctx, ctx_len, + (const byte*)input, + (word32)input_length, + &verify_res); + } + else if (PSA_ALG_IS_HASH_ML_DSA(alg) || + PSA_ALG_IS_DETERMINISTIC_HASH_ML_DSA(alg)) { + wc_MlDsaKey_Free(&key); + return PSA_ERROR_INVALID_ARGUMENT; + } + else { + wc_MlDsaKey_Free(&key); + return PSA_ERROR_NOT_SUPPORTED; + } + } + else { + /* Verify over a pre-computed hash digest. */ + psa_algorithm_t hash_alg; + int wc_hash; + + if (PSA_ALG_IS_HASH_ML_DSA(alg) || + PSA_ALG_IS_DETERMINISTIC_HASH_ML_DSA(alg)) { + hash_alg = PSA_ALG_GET_HASH(alg); + wc_hash = mldsa_psa_hash_to_wc(hash_alg); + if (wc_hash == WC_HASH_TYPE_NONE) { + wc_MlDsaKey_Free(&key); + return PSA_ERROR_NOT_SUPPORTED; + } + ret = wc_MlDsaKey_VerifyCtxHash(&key, + (const byte*)signature, + (word32)signature_length, + ctx, ctx_len, + (const byte*)input, + (word32)input_length, + wc_hash, &verify_res); + } + else { + /* Pure ML-DSA with a pre-hashed input is a protocol error. */ + wc_MlDsaKey_Free(&key); + return PSA_ERROR_INVALID_ARGUMENT; + } + } wc_MlDsaKey_Free(&key); diff --git a/src/psa_mlkem.c b/src/psa_mlkem.c index 5c4c985..f6516d0 100644 --- a/src/psa_mlkem.c +++ b/src/psa_mlkem.c @@ -25,12 +25,10 @@ #include -/* Quarantined pending the PSA 1.4 ML-KEM rewrite: this legacy code targets - * the removed API and references - * psa_ml_kem_parameter_t, which is not defined by any header. */ -#if 0 +#if defined(WOLFSSL_PSA_ENGINE) && defined(WOLFSSL_HAVE_MLKEM) #include +#include "psa_size.h" #include #include #include @@ -38,260 +36,282 @@ #include #include #include -#include +#include +#include "psa_pqc_internal.h" -/* Convert ML-KEM parameter to wolfCrypt key type */ -static int psa_ml_kem_parameter_to_type(psa_ml_kem_parameter_t parameter) +/* Map PSA security-strength bits to wolfCrypt ML-KEM type constant. */ +static int mlkem_bits_to_type(size_t bits) { - switch (parameter) { - case PSA_ML_KEM_PARAMETER_512: + switch (bits) { + case 512: return WC_ML_KEM_512; - case PSA_ML_KEM_PARAMETER_768: + case 768: return WC_ML_KEM_768; - case PSA_ML_KEM_PARAMETER_1024: + case 1024: return WC_ML_KEM_1024; default: return -1; } } -/* Generate an ML-KEM key pair */ -psa_status_t psa_ml_kem_generate_key(psa_ml_kem_parameter_t parameter, - uint8_t *private_key, - size_t private_key_size, - size_t *private_key_length, - uint8_t *public_key, - size_t public_key_size, - size_t *public_key_length) +/* + * wolfpsa_mlkem_generate_seed + * + * Generate a fresh 64-byte d||z seed (FIPS 203 ML-KEM.KeyGen_internal) for + * an ML-KEM key pair of the requested strength. The seed is validated by + * attempting a full key expansion before returning it to the caller. + */ +psa_status_t wolfpsa_mlkem_generate_seed(size_t bits, uint8_t *seed) { - int ret; + psa_status_t psa_ret; MlKemKey key; int type; - WC_RNG rng; - word32 privSz, pubSz; - - /* Convert parameter to wolfCrypt key type */ - type = psa_ml_kem_parameter_to_type(parameter); + int ret; + + type = mlkem_bits_to_type(bits); if (type < 0) { return PSA_ERROR_NOT_SUPPORTED; } - if (wolfpsa_check_word32_length(public_key_size) != PSA_SUCCESS) { - return PSA_ERROR_INVALID_ARGUMENT; + + /* Generate 64 bytes of randomness: d (32 bytes) || z (32 bytes). */ + psa_ret = psa_generate_random(seed, WOLFPSA_MLKEM_SEED_SIZE); + if (psa_ret != PSA_SUCCESS) { + return psa_ret; } - - /* Initialize ML-KEM key */ + + /* Validate the seed by expanding it into a full key pair. */ ret = wc_MlKemKey_Init(&key, type, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { + wc_ForceZero(seed, WOLFPSA_MLKEM_SEED_SIZE); return wc_error_to_psa_status(ret); } - - /* Initialize RNG */ - ret = wc_InitRng(&rng); + + /* MakeKeyWithRandom expects the 64-byte d||z buffer directly. */ + ret = wc_MlKemKey_MakeKeyWithRandom(&key, seed, WOLFPSA_MLKEM_SEED_SIZE); + wc_MlKemKey_Free(&key); if (ret != 0) { - wc_MlKemKey_Free(&key); + wc_ForceZero(seed, WOLFPSA_MLKEM_SEED_SIZE); return wc_error_to_psa_status(ret); } - - /* Generate key pair */ - ret = wc_MlKemKey_MakeKey(&key, &rng); + + return PSA_SUCCESS; +} + +/* + * wolfpsa_mlkem_export_public + * + * Expand a 64-byte seed into a key pair and encode the public key (ek) into + * the caller-supplied buffer. + */ +psa_status_t wolfpsa_mlkem_export_public(size_t bits, const uint8_t *seed, + uint8_t *out, size_t out_size, size_t *out_length) +{ + MlKemKey key; + int type; + int ret; + word32 pubSz; + + type = mlkem_bits_to_type(bits); + if (type < 0) { + return PSA_ERROR_NOT_SUPPORTED; + } + + ret = wc_MlKemKey_Init(&key, type, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { - wc_FreeRng(&rng); - wc_MlKemKey_Free(&key); return wc_error_to_psa_status(ret); } - - /* Get key sizes */ - ret = wc_MlKemKey_PrivateKeySize(&key, &privSz); + + ret = wc_MlKemKey_MakeKeyWithRandom(&key, seed, WOLFPSA_MLKEM_SEED_SIZE); if (ret != 0) { - wc_FreeRng(&rng); wc_MlKemKey_Free(&key); return wc_error_to_psa_status(ret); } - + ret = wc_MlKemKey_PublicKeySize(&key, &pubSz); if (ret != 0) { - wc_FreeRng(&rng); wc_MlKemKey_Free(&key); return wc_error_to_psa_status(ret); } - - /* Check buffer sizes */ - if (private_key_size < privSz || public_key_size < pubSz) { - wc_FreeRng(&rng); + + if (out_size < (size_t)pubSz) { wc_MlKemKey_Free(&key); return PSA_ERROR_BUFFER_TOO_SMALL; } - - /* Export private key */ - ret = wc_MlKemKey_EncodePrivateKey(&key, private_key, privSz); - if (ret != 0) { - wc_FreeRng(&rng); - wc_MlKemKey_Free(&key); - return wc_error_to_psa_status(ret); - } - *private_key_length = privSz; - - /* Export public key */ - ret = wc_MlKemKey_EncodePublicKey(&key, public_key, pubSz); + + ret = wc_MlKemKey_EncodePublicKey(&key, out, pubSz); + wc_MlKemKey_Free(&key); if (ret != 0) { - wc_FreeRng(&rng); - wc_MlKemKey_Free(&key); return wc_error_to_psa_status(ret); } - *public_key_length = pubSz; - - wc_FreeRng(&rng); - wc_MlKemKey_Free(&key); - + + *out_length = (size_t)pubSz; return PSA_SUCCESS; } -/* Encapsulate a shared secret */ -psa_status_t psa_ml_kem_encapsulate(psa_ml_kem_parameter_t parameter, - const uint8_t *public_key, - size_t public_key_size, - uint8_t *ciphertext, - size_t ciphertext_size, - size_t *ciphertext_length, - uint8_t *shared_secret, - size_t shared_secret_size, - size_t *shared_secret_length) +/* + * wolfpsa_mlkem_encapsulate + * + * Perform ML-KEM encapsulation. The caller supplies either a raw public key + * (PSA_KEY_TYPE_ML_KEM_PUBLIC_KEY) or a 64-byte seed representing a key pair + * (PSA_KEY_TYPE_ML_KEM_KEY_PAIR). The output ciphertext and shared secret + * buffers must be pre-allocated; the shared secret is exactly 32 bytes. + */ +psa_status_t wolfpsa_mlkem_encapsulate(size_t bits, psa_key_type_t key_type, + const uint8_t *key_data, size_t key_data_length, + uint8_t *ciphertext, size_t ciphertext_size, size_t *ciphertext_length, + uint8_t *shared_secret) { - int ret; MlKemKey key; - int type; WC_RNG rng; - word32 ctSz, ssSz; - - /* Convert parameter to wolfCrypt key type */ - type = psa_ml_kem_parameter_to_type(parameter); + int type; + int ret; + word32 ctSz; + word32 pubSz; + int rng_init = 0; + + type = mlkem_bits_to_type(bits); if (type < 0) { return PSA_ERROR_NOT_SUPPORTED; } - if (wolfpsa_check_word32_length(public_key_size) != PSA_SUCCESS) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - /* Initialize ML-KEM key */ + ret = wc_MlKemKey_Init(&key, type, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } - - /* Import public key */ - ret = wc_MlKemKey_DecodePublicKey(&key, public_key, (word32)public_key_size); - if (ret != 0) { - wc_MlKemKey_Free(&key); - return wc_error_to_psa_status(ret); + + if (key_type == PSA_KEY_TYPE_ML_KEM_PUBLIC_KEY) { + /* Validate the provided length against the expected public-key size. */ + ret = wc_MlKemKey_PublicKeySize(&key, &pubSz); + if (ret != 0) { + wc_MlKemKey_Free(&key); + return wc_error_to_psa_status(ret); + } + if (key_data_length != (size_t)pubSz) { + wc_MlKemKey_Free(&key); + return PSA_ERROR_INVALID_ARGUMENT; + } + if (wolfpsa_check_word32_length(key_data_length) != PSA_SUCCESS) { + wc_MlKemKey_Free(&key); + return PSA_ERROR_INVALID_ARGUMENT; + } + ret = wc_MlKemKey_DecodePublicKey(&key, key_data, + (word32)key_data_length); + if (ret != 0) { + wc_MlKemKey_Free(&key); + return wc_error_to_psa_status(ret); + } } - - /* Get ciphertext and shared secret sizes */ - ret = wc_MlKemKey_CipherTextSize(&key, &ctSz); - if (ret != 0) { + else if (key_type == PSA_KEY_TYPE_ML_KEM_KEY_PAIR) { + /* Key pair is stored as a 64-byte seed; expand it. */ + if (key_data_length != WOLFPSA_MLKEM_SEED_SIZE) { + wc_MlKemKey_Free(&key); + return PSA_ERROR_INVALID_ARGUMENT; + } + ret = wc_MlKemKey_MakeKeyWithRandom(&key, key_data, + WOLFPSA_MLKEM_SEED_SIZE); + if (ret != 0) { + wc_MlKemKey_Free(&key); + return wc_error_to_psa_status(ret); + } + } + else { wc_MlKemKey_Free(&key); - return wc_error_to_psa_status(ret); + return PSA_ERROR_INVALID_ARGUMENT; } - - ret = wc_MlKemKey_SharedSecretSize(&key, &ssSz); + + ret = wc_MlKemKey_CipherTextSize(&key, &ctSz); if (ret != 0) { wc_MlKemKey_Free(&key); return wc_error_to_psa_status(ret); } - - /* Check buffer sizes */ - if (ciphertext_size < ctSz || shared_secret_size < ssSz) { + + if (ciphertext_size < (size_t)ctSz) { wc_MlKemKey_Free(&key); return PSA_ERROR_BUFFER_TOO_SMALL; } - - /* Initialize RNG */ + ret = wc_InitRng(&rng); if (ret != 0) { wc_MlKemKey_Free(&key); return wc_error_to_psa_status(ret); } - - /* Encapsulate shared secret */ + rng_init = 1; + + /* wc_MlKemKey_Encapsulate(key, ct, ss, rng) */ ret = wc_MlKemKey_Encapsulate(&key, ciphertext, shared_secret, &rng); - if (ret != 0) { + + if (rng_init) { wc_FreeRng(&rng); - wc_MlKemKey_Free(&key); - return wc_error_to_psa_status(ret); } - - *ciphertext_length = ctSz; - *shared_secret_length = ssSz; - - wc_FreeRng(&rng); wc_MlKemKey_Free(&key); - + + if (ret != 0) { + wc_ForceZero(shared_secret, WOLFPSA_MLKEM_SS_SIZE); + return wc_error_to_psa_status(ret); + } + + *ciphertext_length = (size_t)ctSz; return PSA_SUCCESS; } -/* Decapsulate a shared secret */ -psa_status_t psa_ml_kem_decapsulate(psa_ml_kem_parameter_t parameter, - const uint8_t *private_key, - size_t private_key_size, - const uint8_t *ciphertext, - size_t ciphertext_size, - uint8_t *shared_secret, - size_t shared_secret_size, - size_t *shared_secret_length) +/* + * wolfpsa_mlkem_decapsulate + * + * Expand a 64-byte seed into a full key pair and decapsulate the ciphertext, + * producing a 32-byte shared secret. Per FIPS 203 implicit rejection: an + * invalid (but correctly sized) ciphertext will still produce a deterministic + * pseudorandom output rather than an error. + */ +psa_status_t wolfpsa_mlkem_decapsulate(size_t bits, const uint8_t *seed, + const uint8_t *ciphertext, size_t ciphertext_length, + uint8_t *shared_secret) { - int ret; MlKemKey key; int type; - word32 ssSz; - - /* Convert parameter to wolfCrypt key type */ - type = psa_ml_kem_parameter_to_type(parameter); + int ret; + word32 ctSz; + + type = mlkem_bits_to_type(bits); if (type < 0) { return PSA_ERROR_NOT_SUPPORTED; } - if ((wolfpsa_check_word32_length(private_key_size) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(ciphertext_size) != PSA_SUCCESS)) { + + if (wolfpsa_check_word32_length(ciphertext_length) != PSA_SUCCESS) { return PSA_ERROR_INVALID_ARGUMENT; } - - /* Initialize ML-KEM key */ + ret = wc_MlKemKey_Init(&key, type, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } - - /* Import private key */ - ret = wc_MlKemKey_DecodePrivateKey(&key, private_key, (word32)private_key_size); + + ret = wc_MlKemKey_MakeKeyWithRandom(&key, seed, WOLFPSA_MLKEM_SEED_SIZE); if (ret != 0) { wc_MlKemKey_Free(&key); return wc_error_to_psa_status(ret); } - - /* Get shared secret size */ - ret = wc_MlKemKey_SharedSecretSize(&key, &ssSz); + + ret = wc_MlKemKey_CipherTextSize(&key, &ctSz); if (ret != 0) { wc_MlKemKey_Free(&key); return wc_error_to_psa_status(ret); } - - /* Check buffer size */ - if (shared_secret_size < ssSz) { + + if ((word32)ciphertext_length != ctSz) { wc_MlKemKey_Free(&key); - return PSA_ERROR_BUFFER_TOO_SMALL; + return PSA_ERROR_INVALID_ARGUMENT; } - - /* Decapsulate shared secret */ - ret = wc_MlKemKey_Decapsulate(&key, shared_secret, ciphertext, - (word32)ciphertext_size); + + /* wc_MlKemKey_Decapsulate(key, ss, ct, len) */ + ret = wc_MlKemKey_Decapsulate(&key, shared_secret, ciphertext, + (word32)ciphertext_length); + wc_MlKemKey_Free(&key); if (ret != 0) { - wc_MlKemKey_Free(&key); + wc_ForceZero(shared_secret, WOLFPSA_MLKEM_SS_SIZE); return wc_error_to_psa_status(ret); } - - *shared_secret_length = ssSz; - - wc_MlKemKey_Free(&key); - + return PSA_SUCCESS; } -#endif /* WOLFSSL_PSA_ENGINE && (WOLFSSL_HAVE_KYBER || WOLFSSL_WC_MLKEM) */ +#endif /* WOLFSSL_PSA_ENGINE && WOLFSSL_HAVE_MLKEM */ From 8ef3aedaae7178234eeb0d6fcbed9c8e3f95f04f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 14:22:00 +0200 Subject: [PATCH 17/24] psa: KEM front-end, XOF, AES-KW, LMS/XMSS verify, 1.4 stubs psa_kem.c: psa_encapsulate/psa_decapsulate returning the ML-KEM shared secret as a new key created from caller attributes; ciphertext is staged locally so no partial result escapes on failure. psa_xof.c: incremental SHAKE128/256 XOF operations. wolfCrypt's Shake Absorb finalizes padding per call, so update() accumulates input and absorbs once at first output(); output is served at byte granularity over block-wise squeezes. Ascon XOFs report NOT_SUPPORTED. psa_key_wrap.c: psa_wrap_key/psa_unwrap_key with AES-KW (RFC 3394), mapping the integrity-check failure to PSA_ERROR_INVALID_SIGNATURE; KWP reports NOT_SUPPORTED (no wolfCrypt backend). psa_lms_xmss.c: verify-only LMS/HSS and XMSS/XMSS^MT backends importing raw public keys (XMSS retries the XMSS^MT OID table on lookup miss). psa_api_stub.c: NOT_SUPPORTED stubs for the interruptible operations, psa_attach_key and hash suspend/resume; trivial max-ops accessors; and psa_generate_key_custom / psa_key_derivation_output_key_custom delegating wrappers that accept only default parameters. --- src/psa_api_stub.c | 274 ++++++++++++++++++++++++++ src/psa_kem.c | 302 ++++++++++++++++++++++++++++ src/psa_key_wrap.c | 323 ++++++++++++++++++++++++++++++ src/psa_lms_xmss.c | 410 ++++++++++---------------------------- src/psa_xof.c | 477 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1479 insertions(+), 307 deletions(-) create mode 100644 src/psa_kem.c create mode 100644 src/psa_key_wrap.c create mode 100644 src/psa_xof.c diff --git a/src/psa_api_stub.c b/src/psa_api_stub.c index 1d1798c..2b0398f 100644 --- a/src/psa_api_stub.c +++ b/src/psa_api_stub.c @@ -131,6 +131,280 @@ psa_status_t psa_purge_key(psa_key_id_t key) { return wolfPSA_StubNotSupported(); } +/* --- Key attachment (hardware-bound keys) --- */ + +psa_status_t psa_attach_key(const psa_key_attributes_t *attributes, + const uint8_t *label, + size_t label_length, + psa_key_id_t *key) { + (void)attributes; + (void)label; + (void)label_length; + (void)key; + return wolfPSA_StubNotSupported(); +} + +/* --- Hash suspend/resume --- */ + +psa_status_t psa_hash_suspend(psa_hash_operation_t *operation, + uint8_t *hash_state, + size_t hash_state_size, + size_t *hash_state_length) { + (void)operation; + (void)hash_state; + (void)hash_state_size; + (void)hash_state_length; + return wolfPSA_StubNotSupported(); +} + +psa_status_t psa_hash_resume(psa_hash_operation_t *operation, + const uint8_t *hash_state, + size_t hash_state_length) { + (void)operation; + (void)hash_state; + (void)hash_state_length; + return wolfPSA_StubNotSupported(); +} + +/* --- Interruptible max-ops configuration --- */ + +static uint32_t wolfPSA_interruptible_max_ops = PSA_INTERRUPTIBLE_MAX_OPS_UNLIMITED; + +void psa_interruptible_set_max_ops(uint32_t max_ops) { + wolfPSA_interruptible_max_ops = max_ops; +} + +uint32_t psa_interruptible_get_max_ops(void) { + return wolfPSA_interruptible_max_ops; +} + +/* --- Interruptible sign/verify hash --- */ + +psa_status_t psa_sign_hash_start( + psa_sign_hash_interruptible_operation_t *operation, + psa_key_id_t key, psa_algorithm_t alg, + const uint8_t *hash, size_t hash_length) { + (void)operation; + (void)key; + (void)alg; + (void)hash; + (void)hash_length; + return wolfPSA_StubNotSupported(); +} + +psa_status_t psa_sign_hash_complete( + psa_sign_hash_interruptible_operation_t *operation, + uint8_t *signature, size_t signature_size, + size_t *signature_length) { + (void)operation; + (void)signature; + (void)signature_size; + (void)signature_length; + return wolfPSA_StubNotSupported(); +} + +psa_status_t psa_sign_hash_abort( + psa_sign_hash_interruptible_operation_t *operation) { + (void)operation; + if (!wolfPSA_CryptoIsInitialized()) { + return PSA_ERROR_BAD_STATE; + } + return PSA_SUCCESS; +} + +uint32_t psa_sign_hash_get_num_ops( + const psa_sign_hash_interruptible_operation_t *operation) { + (void)operation; + return 0; +} + +psa_status_t psa_verify_hash_start( + psa_verify_hash_interruptible_operation_t *operation, + psa_key_id_t key, psa_algorithm_t alg, + const uint8_t *hash, size_t hash_length, + const uint8_t *signature, size_t signature_length) { + (void)operation; + (void)key; + (void)alg; + (void)hash; + (void)hash_length; + (void)signature; + (void)signature_length; + return wolfPSA_StubNotSupported(); +} + +psa_status_t psa_verify_hash_complete( + psa_verify_hash_interruptible_operation_t *operation) { + (void)operation; + return wolfPSA_StubNotSupported(); +} + +psa_status_t psa_verify_hash_abort( + psa_verify_hash_interruptible_operation_t *operation) { + (void)operation; + if (!wolfPSA_CryptoIsInitialized()) { + return PSA_ERROR_BAD_STATE; + } + return PSA_SUCCESS; +} + +uint32_t psa_verify_hash_get_num_ops( + const psa_verify_hash_interruptible_operation_t *operation) { + (void)operation; + return 0; +} + +/* --- Interruptible key agreement (IOP) --- */ + +psa_status_t psa_key_agreement_iop_setup( + psa_key_agreement_iop_t *operation, + psa_key_id_t private_key, + const uint8_t *peer_key, + size_t peer_key_length, + psa_algorithm_t alg, + const psa_key_attributes_t *attributes) { + (void)operation; + (void)private_key; + (void)peer_key; + (void)peer_key_length; + (void)alg; + (void)attributes; + return wolfPSA_StubNotSupported(); +} + +psa_status_t psa_key_agreement_iop_complete( + psa_key_agreement_iop_t *operation, + psa_key_id_t *key) { + (void)operation; + (void)key; + return wolfPSA_StubNotSupported(); +} + +psa_status_t psa_key_agreement_iop_abort( + psa_key_agreement_iop_t *operation) { + (void)operation; + if (!wolfPSA_CryptoIsInitialized()) { + return PSA_ERROR_BAD_STATE; + } + return PSA_SUCCESS; +} + +uint32_t psa_key_agreement_iop_get_num_ops(psa_key_agreement_iop_t *operation) { + (void)operation; + return 0; +} + +/* --- Interruptible key generation (IOP) --- */ + +psa_status_t psa_generate_key_iop_setup( + psa_generate_key_iop_t *operation, + const psa_key_attributes_t *attributes) { + (void)operation; + (void)attributes; + return wolfPSA_StubNotSupported(); +} + +psa_status_t psa_generate_key_iop_complete( + psa_generate_key_iop_t *operation, + psa_key_id_t *key) { + (void)operation; + (void)key; + return wolfPSA_StubNotSupported(); +} + +psa_status_t psa_generate_key_iop_abort( + psa_generate_key_iop_t *operation) { + (void)operation; + if (!wolfPSA_CryptoIsInitialized()) { + return PSA_ERROR_BAD_STATE; + } + return PSA_SUCCESS; +} + +uint32_t psa_generate_key_iop_get_num_ops(psa_generate_key_iop_t *operation) { + (void)operation; + return 0; +} + +/* --- Interruptible export public key (IOP) --- */ + +psa_status_t psa_export_public_key_iop_setup( + psa_export_public_key_iop_t *operation, + psa_key_id_t key) { + (void)operation; + (void)key; + return wolfPSA_StubNotSupported(); +} + +psa_status_t psa_export_public_key_iop_complete( + psa_export_public_key_iop_t *operation, + uint8_t *data, + size_t data_size, + size_t *data_length) { + (void)operation; + (void)data; + (void)data_size; + (void)data_length; + return wolfPSA_StubNotSupported(); +} + +psa_status_t psa_export_public_key_iop_abort( + psa_export_public_key_iop_t *operation) { + (void)operation; + if (!wolfPSA_CryptoIsInitialized()) { + return PSA_ERROR_BAD_STATE; + } + return PSA_SUCCESS; +} + +uint32_t psa_export_public_key_iop_get_num_ops( + psa_export_public_key_iop_t *operation) { + (void)operation; + return 0; +} + +/* --- Custom key parameter wrappers (delegating) --- */ + +psa_status_t psa_generate_key_custom(const psa_key_attributes_t *attributes, + const psa_custom_key_parameters_t *custom, + const uint8_t *custom_data, + size_t custom_data_length, + psa_key_id_t *key) { + if (custom == NULL) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (custom->flags != 0) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (custom_data_length != 0) { + (void)custom_data; + return PSA_ERROR_INVALID_ARGUMENT; + } + (void)custom_data; + return psa_generate_key(attributes, key); +} + +psa_status_t psa_key_derivation_output_key_custom( + const psa_key_attributes_t *attributes, + psa_key_derivation_operation_t *operation, + const psa_custom_key_parameters_t *custom, + const uint8_t *custom_data, + size_t custom_data_length, + psa_key_id_t *key) { + if (custom == NULL) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (custom->flags != 0) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (custom_data_length != 0) { + (void)custom_data; + return PSA_ERROR_INVALID_ARGUMENT; + } + (void)custom_data; + return psa_key_derivation_output_key(attributes, operation, key); +} + void psa_reset_key_attributes(psa_key_attributes_t *attributes) { if (attributes == NULL) { return; diff --git a/src/psa_kem.c b/src/psa_kem.c new file mode 100644 index 0000000..2777adf --- /dev/null +++ b/src/psa_kem.c @@ -0,0 +1,302 @@ +/* psa_kem.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 + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_PSA_ENGINE) + +#include +#include +#include +#include "psa_pqc_internal.h" +#include "psa_trace.h" +#include +#include +#include + +#include + +/* Validate that an output-key attributes type is acceptable for a KEM shared + * secret (32 bytes of unstructured key material) and that the requested bit + * size is compatible (0 = let the import infer; 256 = explicit 32-byte key). */ +static psa_status_t wolfpsa_kem_check_output_attributes( + const psa_key_attributes_t *attributes) +{ + psa_key_type_t type = psa_get_key_type(attributes); + size_t bits = psa_get_key_bits(attributes); + + if (!PSA_KEY_TYPE_IS_UNSTRUCTURED(type)) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + if (bits != 0 && bits != (WOLFPSA_MLKEM_SS_SIZE * 8u)) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + return PSA_SUCCESS; +} + +/* Validate that the requested algorithm is a supported KEM algorithm. + * Returns: + * PSA_SUCCESS - alg == PSA_ALG_ML_KEM + * PSA_ERROR_NOT_SUPPORTED - alg is a known KEM but not ML-KEM + * PSA_ERROR_INVALID_ARGUMENT - alg is not a KEM algorithm at all + */ +static psa_status_t wolfpsa_kem_check_alg(psa_algorithm_t alg) +{ + if (alg == PSA_ALG_ML_KEM) { + return PSA_SUCCESS; + } + if (PSA_ALG_IS_KEY_ENCAPSULATION(alg)) { + /* Other KEM algorithms (e.g. PSA_ALG_ECIES_SEC1) are not supported. */ + return PSA_ERROR_NOT_SUPPORTED; + } + return PSA_ERROR_INVALID_ARGUMENT; +} + +psa_status_t psa_encapsulate(psa_key_id_t key, + psa_algorithm_t alg, + const psa_key_attributes_t *attributes, + psa_key_id_t *output_key, + uint8_t *ciphertext, + size_t ciphertext_size, + size_t *ciphertext_length) +{ + psa_key_attributes_t key_attr; + uint8_t *key_data = NULL; + size_t key_data_length = 0; + psa_key_type_t key_type; + size_t key_bits; + psa_key_usage_t key_usage; + psa_algorithm_t key_alg; + psa_status_t status; + uint8_t ss[WOLFPSA_MLKEM_SS_SIZE]; + /* Staging buffer: ciphertext is written here before the output key is + * successfully imported, so no partial results escape on failure. */ + uint8_t ct_buf[PSA_ENCAPSULATE_CIPHERTEXT_MAX_SIZE]; + size_t ct_len = 0; + + wolfpsa_trace("psa_encapsulate(key=%u alg=0x%08x)", + (unsigned)key, (unsigned)alg); + + /* --- Argument validation --- */ + if (output_key == NULL || ciphertext == NULL || + ciphertext_length == NULL || attributes == NULL) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + *output_key = PSA_KEY_ID_NULL; + *ciphertext_length = 0; + + status = wolfpsa_kem_check_alg(alg); + if (status != PSA_SUCCESS) { + return status; + } + + status = wolfpsa_kem_check_output_attributes(attributes); + if (status != PSA_SUCCESS) { + return status; + } + + if (ciphertext_size > PSA_ENCAPSULATE_CIPHERTEXT_MAX_SIZE) { + /* Oversized buffer is fine; we'll copy the actual length later. */ + } + + /* --- Load the KEM key and check policy --- */ + status = wolfpsa_get_key_data(key, &key_attr, &key_data, &key_data_length); + if (status != PSA_SUCCESS) { + return status; + } + + key_type = psa_get_key_type(&key_attr); + key_bits = psa_get_key_bits(&key_attr); + key_usage = psa_get_key_usage_flags(&key_attr); + key_alg = psa_get_key_algorithm(&key_attr); + + if (key_type != PSA_KEY_TYPE_ML_KEM_KEY_PAIR && + key_type != PSA_KEY_TYPE_ML_KEM_PUBLIC_KEY) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + + if ((key_usage & PSA_KEY_USAGE_ENCRYPT) == 0) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + + if (key_alg != alg && key_alg != PSA_ALG_NONE) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + if (key_alg == PSA_ALG_NONE) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + + /* Check caller buffer is large enough before invoking the backend. */ + { + size_t needed = PSA_ENCAPSULATE_CIPHERTEXT_SIZE(key_type, key_bits, alg); + if (ciphertext_size < needed) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_BUFFER_TOO_SMALL; + } + } + +#if defined(WOLFSSL_HAVE_MLKEM) + /* Encapsulate into the local staging buffer. */ + status = wolfpsa_mlkem_encapsulate(key_bits, key_type, + key_data, key_data_length, + ct_buf, sizeof(ct_buf), &ct_len, + ss); +#else + status = PSA_ERROR_NOT_SUPPORTED; + (void)ct_len; +#endif + + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + + if (status != PSA_SUCCESS) { + wc_ForceZero(ss, sizeof(ss)); + return status; + } + + /* Import the shared secret as the output key. */ + status = psa_import_key(attributes, ss, WOLFPSA_MLKEM_SS_SIZE, output_key); + wc_ForceZero(ss, sizeof(ss)); + + if (status != PSA_SUCCESS) { + *output_key = PSA_KEY_ID_NULL; + return status; + } + + /* Only expose the ciphertext to the caller after the key import succeeded. */ + memcpy(ciphertext, ct_buf, ct_len); + *ciphertext_length = ct_len; + + return PSA_SUCCESS; +} + +psa_status_t psa_decapsulate(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *ciphertext, + size_t ciphertext_length, + const psa_key_attributes_t *attributes, + psa_key_id_t *output_key) +{ + psa_key_attributes_t key_attr; + uint8_t *key_data = NULL; + size_t key_data_length = 0; + psa_key_type_t key_type; + size_t key_bits; + psa_key_usage_t key_usage; + psa_algorithm_t key_alg; + psa_status_t status; + uint8_t ss[WOLFPSA_MLKEM_SS_SIZE]; + + wolfpsa_trace("psa_decapsulate(key=%u alg=0x%08x ct_len=%zu)", + (unsigned)key, (unsigned)alg, ciphertext_length); + + /* --- Argument validation --- */ + if (output_key == NULL || ciphertext == NULL || attributes == NULL) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + *output_key = PSA_KEY_ID_NULL; + + status = wolfpsa_kem_check_alg(alg); + if (status != PSA_SUCCESS) { + return status; + } + + status = wolfpsa_kem_check_output_attributes(attributes); + if (status != PSA_SUCCESS) { + return status; + } + + /* --- Load the KEM key and check policy --- */ + status = wolfpsa_get_key_data(key, &key_attr, &key_data, &key_data_length); + if (status != PSA_SUCCESS) { + return status; + } + + key_type = psa_get_key_type(&key_attr); + key_bits = psa_get_key_bits(&key_attr); + key_usage = psa_get_key_usage_flags(&key_attr); + key_alg = psa_get_key_algorithm(&key_attr); + + /* Decapsulate requires the key pair (private key). */ + if (key_type != PSA_KEY_TYPE_ML_KEM_KEY_PAIR) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + + if ((key_usage & PSA_KEY_USAGE_DECRYPT) == 0) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + + if (key_alg != alg && key_alg != PSA_ALG_NONE) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + if (key_alg == PSA_ALG_NONE) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + + /* Key pair is stored as the 64-byte d||z seed. */ + if (key_data_length != WOLFPSA_MLKEM_SEED_SIZE) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + +#if defined(WOLFSSL_HAVE_MLKEM) + status = wolfpsa_mlkem_decapsulate(key_bits, key_data, + ciphertext, ciphertext_length, + ss); +#else + status = PSA_ERROR_NOT_SUPPORTED; +#endif + + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + + if (status != PSA_SUCCESS) { + wc_ForceZero(ss, sizeof(ss)); + return status; + } + + /* Import the shared secret as the output key. */ + status = psa_import_key(attributes, ss, WOLFPSA_MLKEM_SS_SIZE, output_key); + wc_ForceZero(ss, sizeof(ss)); + + if (status != PSA_SUCCESS) { + *output_key = PSA_KEY_ID_NULL; + return status; + } + + return PSA_SUCCESS; +} + +#endif /* WOLFSSL_PSA_ENGINE */ diff --git a/src/psa_key_wrap.c b/src/psa_key_wrap.c new file mode 100644 index 0000000..47bee6c --- /dev/null +++ b/src/psa_key_wrap.c @@ -0,0 +1,323 @@ +/* psa_key_wrap.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 + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_PSA_ENGINE) + +#include +#include "psa_trace.h" +#include +#include +#include +#include + +/* + * psa_wrap_key: RFC 3394 AES Key Wrap (PSA_ALG_KW). + * + * Exports the target key's raw material via psa_export_key() (which itself + * enforces PSA_KEY_USAGE_EXPORT on the target key), then encrypts it with + * the wrapping key using wc_AesKeyWrap(). + */ +psa_status_t psa_wrap_key(psa_key_id_t wrapping_key, + psa_algorithm_t alg, + psa_key_id_t key, + uint8_t *data, + size_t data_size, + size_t *data_length) +{ + psa_status_t status; + psa_key_attributes_t wrap_attr; + psa_key_attributes_t target_attr; + psa_key_type_t wrap_type; + psa_key_usage_t wrap_usage; + psa_algorithm_t wrap_alg; + uint8_t *kek = NULL; + size_t kek_len = 0; + uint8_t *plaintext = NULL; + size_t plain_max; + size_t exported_len = 0; + int wret; + + wolfpsa_trace("psa_wrap_key(wrapping_key=%u alg=0x%08x key=%u)", + (unsigned)wrapping_key, (unsigned)alg, (unsigned)key); + + /* Validate output pointers */ + if (data == NULL || data_length == NULL) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + /* Only key-wrap category algorithms are accepted */ + if (!PSA_ALG_IS_KEY_WRAP(alg)) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + /* KWP is not implemented by wolfCrypt */ + if (alg == PSA_ALG_KWP) { + return PSA_ERROR_NOT_SUPPORTED; + } + + /* Only PSA_ALG_KW remains valid at this point */ + if (alg != PSA_ALG_KW) { + return PSA_ERROR_INVALID_ARGUMENT; + } + +#ifdef HAVE_AES_KEYWRAP + /* Load and validate the wrapping key */ + status = wolfpsa_get_key_data(wrapping_key, &wrap_attr, &kek, &kek_len); + if (status != PSA_SUCCESS) { + return status; + } + + wrap_type = psa_get_key_type(&wrap_attr); + if (wrap_type != PSA_KEY_TYPE_AES) { + wolfpsa_forcezero_free_key_data(kek, kek_len); + return PSA_ERROR_INVALID_ARGUMENT; + } + + wrap_usage = psa_get_key_usage_flags(&wrap_attr); + if ((wrap_usage & PSA_KEY_USAGE_WRAP) == 0) { + wolfpsa_forcezero_free_key_data(kek, kek_len); + return PSA_ERROR_NOT_PERMITTED; + } + + wrap_alg = psa_get_key_algorithm(&wrap_attr); + if (wrap_alg == PSA_ALG_NONE || wrap_alg != alg) { + wolfpsa_forcezero_free_key_data(kek, kek_len); + return PSA_ERROR_NOT_PERMITTED; + } + + /* Obtain the target key's attributes to size the export buffer */ + status = psa_get_key_attributes(key, &target_attr); + if (status != PSA_SUCCESS) { + wolfpsa_forcezero_free_key_data(kek, kek_len); + return status; + } + + plain_max = PSA_EXPORT_KEY_OUTPUT_SIZE(psa_get_key_type(&target_attr), + psa_get_key_bits(&target_attr)); + if (plain_max == 0) { + /* Unknown key type — fall back to a reasonable maximum */ + plain_max = PSA_EXPORT_KEY_PAIR_OR_PUBLIC_MAX_SIZE; + } + + plaintext = (uint8_t *)XMALLOC(plain_max, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (plaintext == NULL) { + wolfpsa_forcezero_free_key_data(kek, kek_len); + return PSA_ERROR_INSUFFICIENT_MEMORY; + } + + /* + * psa_export_key enforces PSA_KEY_USAGE_EXPORT on the target key + * (verified in src/psa_key_storage.c at lines 1331-1334 for volatile + * keys and lines 1355-1357 for persistent keys). + */ + status = psa_export_key(key, plaintext, plain_max, &exported_len); + if (status != PSA_SUCCESS) { + wc_ForceZero(plaintext, plain_max); + XFREE(plaintext, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfpsa_forcezero_free_key_data(kek, kek_len); + return status; + } + + /* RFC 3394 requires the plaintext to be a multiple of 8 and >= 16 */ + if (exported_len < 16 || (exported_len % 8) != 0) { + wc_ForceZero(plaintext, plain_max); + XFREE(plaintext, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfpsa_forcezero_free_key_data(kek, kek_len); + return PSA_ERROR_INVALID_ARGUMENT; + } + + /* Check that the caller's output buffer is large enough */ + if (data_size < exported_len + 8) { + wc_ForceZero(plaintext, plain_max); + XFREE(plaintext, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfpsa_forcezero_free_key_data(kek, kek_len); + return PSA_ERROR_BUFFER_TOO_SMALL; + } + + /* Perform RFC 3394 key wrap; pass iv=NULL to use the default IV */ + wret = wc_AesKeyWrap(kek, (word32)kek_len, + plaintext, (word32)exported_len, + data, (word32)data_size, + NULL); + + wc_ForceZero(plaintext, plain_max); + XFREE(plaintext, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfpsa_forcezero_free_key_data(kek, kek_len); + + if (wret < 0) { + return wc_error_to_psa_status(wret); + } + + *data_length = (size_t)wret; + return PSA_SUCCESS; + +#else /* HAVE_AES_KEYWRAP */ + (void)wrapping_key; + (void)alg; + (void)key; + (void)data; + (void)data_size; + (void)data_length; + return PSA_ERROR_NOT_SUPPORTED; +#endif /* HAVE_AES_KEYWRAP */ +} + +/* + * psa_unwrap_key: RFC 3394 AES Key Unwrap (PSA_ALG_KW). + * + * Decrypts the wrapped blob with wc_AesKeyUnWrap() and imports the recovered + * plaintext as a new key via psa_import_key(). + */ +psa_status_t psa_unwrap_key(const psa_key_attributes_t *attributes, + psa_key_id_t wrapping_key, + psa_algorithm_t alg, + const uint8_t *data, + size_t data_length, + psa_key_id_t *key) +{ + psa_status_t status; + psa_key_attributes_t wrap_attr; + psa_key_type_t wrap_type; + psa_key_usage_t wrap_usage; + psa_algorithm_t wrap_alg; + uint8_t *kek = NULL; + size_t kek_len = 0; + uint8_t *plaintext = NULL; + size_t plain_len; + int wret; + + wolfpsa_trace("psa_unwrap_key(wrapping_key=%u alg=0x%08x data_len=%zu)", + (unsigned)wrapping_key, (unsigned)alg, data_length); + + /* Validate pointers */ + if (attributes == NULL || data == NULL || key == NULL) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + *key = PSA_KEY_ID_NULL; + + /* Only key-wrap category algorithms are accepted */ + if (!PSA_ALG_IS_KEY_WRAP(alg)) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + /* KWP is not implemented by wolfCrypt */ + if (alg == PSA_ALG_KWP) { + return PSA_ERROR_NOT_SUPPORTED; + } + + /* Only PSA_ALG_KW remains valid at this point */ + if (alg != PSA_ALG_KW) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + /* + * RFC 3394 ciphertext must be >= 24 bytes (16 plaintext + 8 ICV) + * and a multiple of 8. + */ + if (data_length < 24 || (data_length % 8) != 0) { + return PSA_ERROR_INVALID_ARGUMENT; + } + +#ifdef HAVE_AES_KEYWRAP + /* Load and validate the wrapping key */ + status = wolfpsa_get_key_data(wrapping_key, &wrap_attr, &kek, &kek_len); + if (status != PSA_SUCCESS) { + return status; + } + + wrap_type = psa_get_key_type(&wrap_attr); + if (wrap_type != PSA_KEY_TYPE_AES) { + wolfpsa_forcezero_free_key_data(kek, kek_len); + return PSA_ERROR_INVALID_ARGUMENT; + } + + wrap_usage = psa_get_key_usage_flags(&wrap_attr); + if ((wrap_usage & PSA_KEY_USAGE_UNWRAP) == 0) { + wolfpsa_forcezero_free_key_data(kek, kek_len); + return PSA_ERROR_NOT_PERMITTED; + } + + wrap_alg = psa_get_key_algorithm(&wrap_attr); + if (wrap_alg == PSA_ALG_NONE || wrap_alg != alg) { + wolfpsa_forcezero_free_key_data(kek, kek_len); + return PSA_ERROR_NOT_PERMITTED; + } + + plain_len = data_length - 8; + plaintext = (uint8_t *)XMALLOC(plain_len, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (plaintext == NULL) { + wolfpsa_forcezero_free_key_data(kek, kek_len); + return PSA_ERROR_INSUFFICIENT_MEMORY; + } + + /* Perform RFC 3394 key unwrap; pass iv=NULL for the default IV */ + wret = wc_AesKeyUnWrap(kek, (word32)kek_len, + data, (word32)data_length, + plaintext, (word32)plain_len, + NULL); + + wolfpsa_forcezero_free_key_data(kek, kek_len); + + if (wret < 0) { + wc_ForceZero(plaintext, plain_len); + XFREE(plaintext, NULL, DYNAMIC_TYPE_TMP_BUFFER); + /* + * BAD_KEYWRAP_IV_E (-240): the decrypted integrity check value did + * not match the expected default IV — the wrapped data is corrupt or + * was encrypted with a different key. + */ + if (wret == BAD_KEYWRAP_IV_E) { + return PSA_ERROR_INVALID_SIGNATURE; + } + return wc_error_to_psa_status(wret); + } + + /* Import the recovered plaintext as a new key */ + status = psa_import_key(attributes, plaintext, (size_t)wret, key); + + wc_ForceZero(plaintext, plain_len); + XFREE(plaintext, NULL, DYNAMIC_TYPE_TMP_BUFFER); + + if (status != PSA_SUCCESS) { + *key = PSA_KEY_ID_NULL; + } + + return status; + +#else /* HAVE_AES_KEYWRAP */ + (void)attributes; + (void)wrapping_key; + (void)alg; + (void)data; + (void)data_length; + (void)key; + return PSA_ERROR_NOT_SUPPORTED; +#endif /* HAVE_AES_KEYWRAP */ +} + +#endif /* WOLFSSL_PSA_ENGINE */ diff --git a/src/psa_lms_xmss.c b/src/psa_lms_xmss.c index 1db4a8e..24d29a8 100644 --- a/src/psa_lms_xmss.c +++ b/src/psa_lms_xmss.c @@ -25,367 +25,163 @@ #include -/* Quarantined pending the PSA 1.4 LMS/XMSS rewrite: this legacy code targets - * the removed / headers and - * key-generation/sign APIs that do not exist under the verify-only build. */ -#if 0 +#if defined(WOLFSSL_PSA_ENGINE) && \ + (defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS)) #include #include "psa_size.h" +#include "psa_pqc_internal.h" #include #include #include -#include -#include -#include -#include #ifdef WOLFSSL_HAVE_LMS -#include +#include -/* Generate an LMS key pair */ -psa_status_t psa_lms_generate_key(uint8_t *private_key, - size_t private_key_size, - size_t *private_key_length, - uint8_t *public_key, - size_t public_key_size, - size_t *public_key_length) +/* + * Verify an LMS/HSS signature. + * + * pub - raw HSS public key blob (RFC 8554 §6.1: u32str(L) || pub[0]) + * pub_len - byte length of pub + * msg - message that was signed + * msg_len - byte length of msg + * sig - raw HSS signature blob + * sig_len - byte length of sig + * + * The parameter set (levels/height/winternitz) is auto-derived from the + * leading type fields in the public key by wc_LmsKey_ImportPubRaw when + * the key is in INITED state (no SetParameters call required). + * + * wc_LmsKey_Verify() return convention: 0 = success, SIG_VERIFY_E = bad + * signature, other negative values = internal/argument error. There is + * no separate *res out-parameter in the current wc_lms.h API. + */ +psa_status_t wolfpsa_lms_verify(const uint8_t *pub, size_t pub_len, + const uint8_t *msg, size_t msg_len, const uint8_t *sig, size_t sig_len) { - int ret; - LmsKey key; - WC_RNG rng; - word32 priv_len32; - word32 pub_len32; - - if ((wolfpsa_check_word32_length(private_key_size) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(public_key_size) != PSA_SUCCESS)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - /* Initialize LMS key */ - ret = wc_LmsKey_Init(&key, NULL, wolfPSA_GetDefaultDevID()); - if (ret != 0) { - return wc_error_to_psa_status(ret); - } - - /* Initialize RNG */ - ret = wc_InitRng(&rng); - if (ret != 0) { - wc_LmsKey_Free(&key); - return wc_error_to_psa_status(ret); - } - - /* Generate key pair */ - ret = wc_LmsKey_MakeKey(&key, &rng); - if (ret != 0) { - wc_FreeRng(&rng); - wc_LmsKey_Free(&key); - return wc_error_to_psa_status(ret); - } - - /* Check buffer sizes */ - if (private_key_size < key.privKeyLen || public_key_size < key.pubKeyLen) { - wc_FreeRng(&rng); - wc_LmsKey_Free(&key); - return PSA_ERROR_BUFFER_TOO_SMALL; - } - - /* Export private key */ - priv_len32 = (word32)private_key_size; - ret = wc_LmsKey_ExportPrivate(&key, private_key, &priv_len32); - if (ret != 0) { - wc_FreeRng(&rng); - wc_LmsKey_Free(&key); - return wc_error_to_psa_status(ret); - } - - /* Export public key */ - pub_len32 = (word32)public_key_size; - ret = wc_LmsKey_ExportPublic(&key, public_key, &pub_len32); - if (ret != 0) { - wc_FreeRng(&rng); - wc_LmsKey_Free(&key); - return wc_error_to_psa_status(ret); - } - - wc_FreeRng(&rng); - wc_LmsKey_Free(&key); + int ret; + LmsKey key; - *private_key_length = (size_t)priv_len32; - *public_key_length = (size_t)pub_len32; - - return PSA_SUCCESS; -} - -/* Sign a message with LMS */ -psa_status_t psa_lms_sign(const uint8_t *private_key, - size_t private_key_size, - const uint8_t *message, - size_t message_length, - uint8_t *signature, - size_t signature_size, - size_t *signature_length) -{ - int ret; - LmsKey key; - word32 sig_len32; - - if ((wolfpsa_check_word32_length(private_key_size) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(message_length) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(signature_size) != PSA_SUCCESS)) { + if (wolfpsa_check_word32_length(pub_len) != PSA_SUCCESS || + wolfpsa_check_word32_length(msg_len) != PSA_SUCCESS || + wolfpsa_check_word32_length(sig_len) != PSA_SUCCESS) { return PSA_ERROR_INVALID_ARGUMENT; } - /* Initialize LMS key */ ret = wc_LmsKey_Init(&key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } - - /* Import private key */ - ret = wc_LmsKey_ImportPrivate(private_key, (word32)private_key_size, &key); - if (ret != 0) { - wc_LmsKey_Free(&key); - return wc_error_to_psa_status(ret); - } - - /* Sign message */ - sig_len32 = (word32)signature_size; - ret = wc_LmsKey_Sign(&key, signature, &sig_len32, - message, (word32)message_length); + + /* Import the raw public key; parameters are auto-derived from the + * L/lmsType/lmOtsType fields encoded in the blob. */ + ret = wc_LmsKey_ImportPubRaw(&key, pub, (word32)pub_len); if (ret != 0) { wc_LmsKey_Free(&key); return wc_error_to_psa_status(ret); } - - wc_LmsKey_Free(&key); - *signature_length = (size_t)sig_len32; - - return PSA_SUCCESS; -} + ret = wc_LmsKey_Verify(&key, sig, (word32)sig_len, + msg, (int)msg_len); -/* Verify a signature with LMS */ -psa_status_t psa_lms_verify(const uint8_t *public_key, - size_t public_key_size, - const uint8_t *message, - size_t message_length, - const uint8_t *signature, - size_t signature_length) -{ - int ret; - LmsKey key; - int verify_res = 0; - - if ((wolfpsa_check_word32_length(public_key_size) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(message_length) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(signature_length) != PSA_SUCCESS)) { - return PSA_ERROR_INVALID_ARGUMENT; - } + wc_LmsKey_Free(&key); - /* Initialize LMS key */ - ret = wc_LmsKey_Init(&key, NULL, wolfPSA_GetDefaultDevID()); - if (ret != 0) { - return wc_error_to_psa_status(ret); - } - - /* Import public key */ - ret = wc_LmsKey_ImportPublic(public_key, (word32)public_key_size, &key); - if (ret != 0) { - wc_LmsKey_Free(&key); - return wc_error_to_psa_status(ret); + if (ret == WC_NO_ERR_TRACE(SIG_VERIFY_E) || ret == -1) { + return PSA_ERROR_INVALID_SIGNATURE; } - - /* Verify signature */ - ret = wc_LmsKey_Verify(&key, signature, (word32)signature_length, - message, (word32)message_length, &verify_res); - - wc_LmsKey_Free(&key); - if (ret != 0) { - return wc_error_to_psa_status(ret); - } - - if (verify_res != 1) { return PSA_ERROR_INVALID_SIGNATURE; } - + return PSA_SUCCESS; } + #endif /* WOLFSSL_HAVE_LMS */ #ifdef WOLFSSL_HAVE_XMSS -#include +#include -/* Generate an XMSS key pair */ -psa_status_t psa_xmss_generate_key(uint8_t *private_key, - size_t private_key_size, - size_t *private_key_length, - uint8_t *public_key, - size_t public_key_size, - size_t *public_key_length) +/* + * Verify an XMSS or XMSS^MT signature. + * + * pub - raw XMSS/XMSS^MT public key blob (RFC 8391: OID || root || SEED) + * pub_len - byte length of pub + * msg - message that was signed + * msg_len - byte length of msg + * sig - raw XMSS/XMSS^MT signature blob + * sig_len - byte length of sig + * + * The parameter set is auto-derived from the 4-byte OID prefix in the public + * key by wc_XmssKey_ImportPubRaw_ex when the key is in INITED state. + * Because XMSS and XMSS^MT OID spaces overlap we try XMSS (is_xmssmt=0) + * first; if the OID is not found in the XMSS table we retry with + * is_xmssmt=1. A second Init/Free pair is used for the retry so that + * partial state from the first attempt does not carry over. + * + * wc_XmssKey_Verify() return convention: 0 = success, SIG_VERIFY_E / -1 = + * bad signature, other negative values = internal/argument error. There is + * no separate *res out-parameter in the current wc_xmss.h API. + */ +psa_status_t wolfpsa_xmss_verify(const uint8_t *pub, size_t pub_len, + const uint8_t *msg, size_t msg_len, const uint8_t *sig, size_t sig_len) { - int ret; - XmssKey key; - WC_RNG rng; - word32 priv_len32; - word32 pub_len32; - - if ((wolfpsa_check_word32_length(private_key_size) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(public_key_size) != PSA_SUCCESS)) { + int ret; + XmssKey key; + int is_xmssmt; + + if (wolfpsa_check_word32_length(pub_len) != PSA_SUCCESS || + wolfpsa_check_word32_length(msg_len) != PSA_SUCCESS || + wolfpsa_check_word32_length(sig_len) != PSA_SUCCESS) { return PSA_ERROR_INVALID_ARGUMENT; } - /* Initialize XMSS key */ - ret = wc_XmssKey_Init(&key, NULL, wolfPSA_GetDefaultDevID()); - if (ret != 0) { - return wc_error_to_psa_status(ret); - } - - /* Initialize RNG */ - ret = wc_InitRng(&rng); - if (ret != 0) { - wc_XmssKey_Free(&key); - return wc_error_to_psa_status(ret); - } - - /* Generate key pair */ - ret = wc_XmssKey_MakeKey(&key, &rng); - if (ret != 0) { - wc_FreeRng(&rng); - wc_XmssKey_Free(&key); - return wc_error_to_psa_status(ret); - } - - /* Check buffer sizes */ - if (private_key_size < key.privKeyLen || public_key_size < key.pubKeyLen) { - wc_FreeRng(&rng); - wc_XmssKey_Free(&key); - return PSA_ERROR_BUFFER_TOO_SMALL; - } - - /* Export private key */ - priv_len32 = (word32)private_key_size; - ret = wc_XmssKey_ExportPrivate(&key, private_key, &priv_len32); - if (ret != 0) { - wc_FreeRng(&rng); - wc_XmssKey_Free(&key); - return wc_error_to_psa_status(ret); - } - - /* Export public key */ - pub_len32 = (word32)public_key_size; - ret = wc_XmssKey_ExportPublic(&key, public_key, &pub_len32); - if (ret != 0) { - wc_FreeRng(&rng); - wc_XmssKey_Free(&key); - return wc_error_to_psa_status(ret); - } - - wc_FreeRng(&rng); - wc_XmssKey_Free(&key); + /* Try XMSS first (is_xmssmt = 0). On OID lookup failure + * (NOT_COMPILED_IN or BUFFER_E from a size mismatch) retry as + * XMSS^MT (is_xmssmt = 1). */ + for (is_xmssmt = 0; is_xmssmt <= 1; is_xmssmt++) { + ret = wc_XmssKey_Init(&key, NULL, wolfPSA_GetDefaultDevID()); + if (ret != 0) { + return wc_error_to_psa_status(ret); + } - *private_key_length = (size_t)priv_len32; - *public_key_length = (size_t)pub_len32; - - return PSA_SUCCESS; -} + ret = wc_XmssKey_ImportPubRaw_ex(&key, pub, (word32)pub_len, + is_xmssmt); + if (ret == WC_NO_ERR_TRACE(NOT_COMPILED_IN) || + ret == WC_NO_ERR_TRACE(BUFFER_E)) { + /* OID not found in this family's table; try the other. */ + wc_XmssKey_Free(&key); + continue; + } + if (ret != 0) { + /* Genuine import error (bad length, bad state, etc.). */ + wc_XmssKey_Free(&key); + return wc_error_to_psa_status(ret); + } -/* Sign a message with XMSS */ -psa_status_t psa_xmss_sign(const uint8_t *private_key, - size_t private_key_size, - const uint8_t *message, - size_t message_length, - uint8_t *signature, - size_t signature_size, - size_t *signature_length) -{ - int ret; - XmssKey key; - word32 sig_len32; - - if ((wolfpsa_check_word32_length(private_key_size) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(message_length) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(signature_size) != PSA_SUCCESS)) { - return PSA_ERROR_INVALID_ARGUMENT; + /* Import succeeded; proceed to verify. */ + break; } - /* Initialize XMSS key */ - ret = wc_XmssKey_Init(&key, NULL, wolfPSA_GetDefaultDevID()); - if (ret != 0) { - return wc_error_to_psa_status(ret); - } - - /* Import private key */ - ret = wc_XmssKey_ImportPrivate(private_key, (word32)private_key_size, &key); - if (ret != 0) { - wc_XmssKey_Free(&key); - return wc_error_to_psa_status(ret); - } - - /* Sign message */ - sig_len32 = (word32)signature_size; - ret = wc_XmssKey_Sign(&key, signature, &sig_len32, - message, (word32)message_length); if (ret != 0) { - wc_XmssKey_Free(&key); - return wc_error_to_psa_status(ret); + /* Neither XMSS nor XMSS^MT table recognised the OID. */ + return PSA_ERROR_INVALID_ARGUMENT; } - - wc_XmssKey_Free(&key); - *signature_length = (size_t)sig_len32; - - return PSA_SUCCESS; -} + ret = wc_XmssKey_Verify(&key, sig, (word32)sig_len, + msg, (int)msg_len); -/* Verify a signature with XMSS */ -psa_status_t psa_xmss_verify(const uint8_t *public_key, - size_t public_key_size, - const uint8_t *message, - size_t message_length, - const uint8_t *signature, - size_t signature_length) -{ - int ret; - XmssKey key; - int verify_res = 0; - - if ((wolfpsa_check_word32_length(public_key_size) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(message_length) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(signature_length) != PSA_SUCCESS)) { - return PSA_ERROR_INVALID_ARGUMENT; - } + wc_XmssKey_Free(&key); - /* Initialize XMSS key */ - ret = wc_XmssKey_Init(&key, NULL, wolfPSA_GetDefaultDevID()); - if (ret != 0) { - return wc_error_to_psa_status(ret); - } - - /* Import public key */ - ret = wc_XmssKey_ImportPublic(public_key, (word32)public_key_size, &key); - if (ret != 0) { - wc_XmssKey_Free(&key); - return wc_error_to_psa_status(ret); + if (ret == WC_NO_ERR_TRACE(SIG_VERIFY_E) || ret == -1) { + return PSA_ERROR_INVALID_SIGNATURE; } - - /* Verify signature */ - ret = wc_XmssKey_Verify(&key, signature, (word32)signature_length, - message, (word32)message_length, &verify_res); - - wc_XmssKey_Free(&key); - if (ret != 0) { - return wc_error_to_psa_status(ret); - } - - if (verify_res != 1) { return PSA_ERROR_INVALID_SIGNATURE; } - + return PSA_SUCCESS; } + #endif /* WOLFSSL_HAVE_XMSS */ #endif /* WOLFSSL_PSA_ENGINE && (WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS) */ diff --git a/src/psa_xof.c b/src/psa_xof.c new file mode 100644 index 0000000..d9aa200 --- /dev/null +++ b/src/psa_xof.c @@ -0,0 +1,477 @@ +/* psa_xof.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 + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_PSA_ENGINE) + +#include +#include "psa_trace.h" +#include "psa_size.h" +#include +#include +#include +#include +#include +#include +#include +#ifndef NO_INLINE + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +#if defined(WOLFSSL_SHAKE128) || defined(WOLFSSL_SHAKE256) +#include +#endif + +extern int wolfPSA_CryptoIsInitialized(void); + +/* + * Absorb strategy: wc_Shake128_Absorb / wc_Shake256_Absorb call Sha3Final() + * internally on every invocation, which applies SHAKE padding and finalises the + * sponge state in one shot. Calling Absorb more than once would produce + * incorrect output. Therefore all input fed via psa_xof_update() is + * accumulated in a dynamically allocated byte buffer (ibuf / ibuf_len / + * ibuf_cap), and the single Absorb() is deferred to the first call of + * psa_xof_output() just before the first SqueezeBlocks(). + * + * Output is served from a one-block staging buffer (buf / buf_off) so that + * callers asking for less than one block still get correct output. For output + * requests >= block_size that are block-aligned the blocks are squeezed + * directly into the caller's buffer. + */ + +#if defined(WOLFSSL_SHAKE128) || defined(WOLFSSL_SHAKE256) + +/* SHAKE128 rate = 1600 - 256 = 1344 bits = 168 bytes */ +#define SHAKE128_BLOCK_SIZE 168u +/* SHAKE256 rate = 1600 - 512 = 1088 bits = 136 bytes */ +#define SHAKE256_BLOCK_SIZE 136u + +typedef struct psa_xof_operation_ctx { + psa_algorithm_t alg; + + /* wc_Shake is typedef'd to wc_Sha3 */ + wc_Shake shake; + + /* 0 = still absorbing input; 1 = squeeze phase started */ + int squeezing; + + /* rate (block) size in bytes */ + word32 block_size; + + /* one-block staging buffer for squeeze tail */ + uint8_t buf[SHAKE128_BLOCK_SIZE]; /* sized to the larger rate */ + word32 buf_off; /* next unread byte in buf */ + word32 buf_len; /* valid bytes in buf (== block_size once filled) */ + + /* accumulated input buffer (absorb-once strategy) */ + uint8_t *ibuf; + word32 ibuf_len; /* bytes written */ + word32 ibuf_cap; /* bytes allocated */ +} psa_xof_operation_ctx_t; + +/* ------------------------------------------------------------------ helpers */ + +static psa_xof_operation_ctx_t *psa_xof_get_ctx(psa_xof_operation_t *op) +{ + if (op == NULL) + return NULL; + return (psa_xof_operation_ctx_t *)(uintptr_t)op->opaque; +} + +static void psa_xof_free_ctx(psa_xof_operation_ctx_t *ctx) +{ + if (ctx == NULL) + return; + + /* free wolfCrypt shake state */ + switch (ctx->alg) { +#ifdef WOLFSSL_SHAKE128 + case PSA_ALG_SHAKE128: + wc_Shake128_Free(&ctx->shake); + break; +#endif +#ifdef WOLFSSL_SHAKE256 + case PSA_ALG_SHAKE256: + wc_Shake256_Free(&ctx->shake); + break; +#endif + default: + break; + } + + /* free input accumulation buffer */ + if (ctx->ibuf != NULL) { + wc_ForceZero(ctx->ibuf, ctx->ibuf_cap); + XFREE(ctx->ibuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ctx->ibuf = NULL; + } +} + +/* + * Grow the input accumulation buffer to hold at least need_cap bytes total. + * Returns 0 on success, -1 on allocation failure. + */ +static int psa_xof_ibuf_grow(psa_xof_operation_ctx_t *ctx, word32 need_cap) +{ + uint8_t *newbuf; + word32 new_cap; + + if (need_cap <= ctx->ibuf_cap) + return 0; + + /* double-or-fit growth */ + new_cap = ctx->ibuf_cap ? ctx->ibuf_cap : 256u; + while (new_cap < need_cap) + new_cap *= 2u; + + newbuf = (uint8_t *)XMALLOC(new_cap, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (newbuf == NULL) + return -1; + + if (ctx->ibuf != NULL && ctx->ibuf_len > 0) + XMEMCPY(newbuf, ctx->ibuf, ctx->ibuf_len); + + if (ctx->ibuf != NULL) { + wc_ForceZero(ctx->ibuf, ctx->ibuf_cap); + XFREE(ctx->ibuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + + ctx->ibuf = newbuf; + ctx->ibuf_cap = new_cap; + return 0; +} + +psa_status_t psa_xof_abort(psa_xof_operation_t *operation); + +static psa_status_t wolfpsa_xof_fail(psa_xof_operation_t *op, + psa_status_t status) +{ + (void)psa_xof_abort(op); + return status; +} + +/* ============================================================ psa_xof_setup */ + +psa_status_t psa_xof_setup(psa_xof_operation_t *operation, + psa_algorithm_t alg) +{ + psa_xof_operation_ctx_t *ctx; + int ret; + + if (operation == NULL) + return PSA_ERROR_INVALID_ARGUMENT; + + wolfpsa_trace("psa_xof_setup(alg=0x%08x)", (unsigned)alg); + + if (!wolfPSA_CryptoIsInitialized()) + return PSA_ERROR_BAD_STATE; + + /* reject non-XOF algorithms */ + if (!PSA_ALG_IS_XOF(alg)) + return PSA_ERROR_INVALID_ARGUMENT; + + /* Ascon XOF: not supported by wolfCrypt */ + if (alg == PSA_ALG_ASCON_XOF128 || alg == PSA_ALG_ASCON_CXOF128) + return PSA_ERROR_NOT_SUPPORTED; + + /* only SHAKE128 / SHAKE256 are supported */ + if (alg != PSA_ALG_SHAKE128 && alg != PSA_ALG_SHAKE256) + return PSA_ERROR_NOT_SUPPORTED; + + /* operation must be inactive */ + if (operation->opaque != (uintptr_t)NULL) + return PSA_ERROR_BAD_STATE; + + ctx = (psa_xof_operation_ctx_t *)XMALLOC(sizeof(*ctx), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (ctx == NULL) + return PSA_ERROR_INSUFFICIENT_MEMORY; + + XMEMSET(ctx, 0, sizeof(*ctx)); + ctx->alg = alg; + + switch (alg) { +#ifdef WOLFSSL_SHAKE128 + case PSA_ALG_SHAKE128: + ctx->block_size = SHAKE128_BLOCK_SIZE; + ret = wc_InitShake128(&ctx->shake, NULL, wolfPSA_GetDefaultDevID()); + break; +#endif +#ifdef WOLFSSL_SHAKE256 + case PSA_ALG_SHAKE256: + ctx->block_size = SHAKE256_BLOCK_SIZE; + ret = wc_InitShake256(&ctx->shake, NULL, wolfPSA_GetDefaultDevID()); + break; +#endif + default: + XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return PSA_ERROR_NOT_SUPPORTED; + } + + if (ret != 0) { + wc_ForceZero(ctx, sizeof(*ctx)); + XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return wc_error_to_psa_status(ret); + } + + operation->opaque = (uintptr_t)ctx; + return PSA_SUCCESS; +} + +/* ======================================================= psa_xof_set_context */ + +psa_status_t psa_xof_set_context(psa_xof_operation_t *operation, + const uint8_t *context, + size_t context_length) +{ + psa_xof_operation_ctx_t *ctx = psa_xof_get_ctx(operation); + + if (operation == NULL) + return PSA_ERROR_INVALID_ARGUMENT; + + wolfpsa_trace("psa_xof_set_context(context_length=%zu)", context_length); + + if (ctx == NULL) + return PSA_ERROR_BAD_STATE; + + /* + * PSA 1.4: set_context is only valid for algorithms where + * PSA_ALG_XOF_HAS_CONTEXT is true. Neither SHAKE128 nor SHAKE256 + * has a context field, so always return INVALID_ARGUMENT here. + */ + if (!PSA_ALG_XOF_HAS_CONTEXT(ctx->alg)) + return PSA_ERROR_INVALID_ARGUMENT; + + /* Unreachable for SHAKE (kept for future context-supporting algs) */ + if (ctx->squeezing || ctx->ibuf_len > 0) + return PSA_ERROR_BAD_STATE; + + (void)context; + (void)context_length; + return PSA_ERROR_INVALID_ARGUMENT; +} + +/* ========================================================= psa_xof_update */ + +psa_status_t psa_xof_update(psa_xof_operation_t *operation, + const uint8_t *input, + size_t input_length) +{ + psa_xof_operation_ctx_t *ctx = psa_xof_get_ctx(operation); + + if (operation == NULL || (input == NULL && input_length > 0)) + return wolfpsa_xof_fail(operation, PSA_ERROR_INVALID_ARGUMENT); + + if (ctx == NULL) + return wolfpsa_xof_fail(operation, PSA_ERROR_BAD_STATE); + + /* cannot update after squeezing has started */ + if (ctx->squeezing) + return wolfpsa_xof_fail(operation, PSA_ERROR_BAD_STATE); + + /* zero-length update is a no-op */ + if (input_length == 0) + return PSA_SUCCESS; + + if (wolfpsa_check_word32_length(input_length) != PSA_SUCCESS) + return wolfpsa_xof_fail(operation, PSA_ERROR_INVALID_ARGUMENT); + + /* accumulate input for the deferred single Absorb() call */ + if (psa_xof_ibuf_grow(ctx, ctx->ibuf_len + (word32)input_length) != 0) + return wolfpsa_xof_fail(operation, PSA_ERROR_INSUFFICIENT_MEMORY); + + XMEMCPY(ctx->ibuf + ctx->ibuf_len, input, input_length); + ctx->ibuf_len += (word32)input_length; + + return PSA_SUCCESS; +} + +/* ========================================================= psa_xof_output */ + +/* + * Absorb all buffered input (exactly once), then serve output_length bytes + * from the squeezed keystream. + * + * Block-aligned chunks that fill the caller buffer directly are squeezed + * straight into the output pointer. A one-block staging buffer handles tails. + */ +psa_status_t psa_xof_output(psa_xof_operation_t *operation, + uint8_t *output, + size_t output_length) +{ + psa_xof_operation_ctx_t *ctx = psa_xof_get_ctx(operation); + int ret; + + if (operation == NULL || (output == NULL && output_length > 0)) + return wolfpsa_xof_fail(operation, PSA_ERROR_INVALID_ARGUMENT); + + if (ctx == NULL) + return wolfpsa_xof_fail(operation, PSA_ERROR_BAD_STATE); + + /* zero-length output is fine */ + if (output_length == 0) + return PSA_SUCCESS; + + /* Transition to squeezing: call Absorb exactly once. */ + if (!ctx->squeezing) { + const uint8_t *absorb_data = (ctx->ibuf != NULL) ? ctx->ibuf + : (const uint8_t *)""; + word32 absorb_len = ctx->ibuf_len; + + switch (ctx->alg) { +#ifdef WOLFSSL_SHAKE128 + case PSA_ALG_SHAKE128: + ret = wc_Shake128_Absorb(&ctx->shake, absorb_data, absorb_len); + break; +#endif +#ifdef WOLFSSL_SHAKE256 + case PSA_ALG_SHAKE256: + ret = wc_Shake256_Absorb(&ctx->shake, absorb_data, absorb_len); + break; +#endif + default: + return wolfpsa_xof_fail(operation, PSA_ERROR_NOT_SUPPORTED); + } + + if (ret != 0) + return wolfpsa_xof_fail(operation, wc_error_to_psa_status(ret)); + + /* release the input accumulation buffer now that we've absorbed */ + if (ctx->ibuf != NULL) { + wc_ForceZero(ctx->ibuf, ctx->ibuf_cap); + XFREE(ctx->ibuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ctx->ibuf = NULL; + } + ctx->ibuf_len = 0; + ctx->ibuf_cap = 0; + + ctx->squeezing = 1; + ctx->buf_off = 0; + ctx->buf_len = 0; + } + + /* Serve bytes from the staging buffer and/or direct squeeze. */ + while (output_length > 0) { + /* 1. Drain the staging buffer first. */ + if (ctx->buf_off < ctx->buf_len) { + word32 avail = ctx->buf_len - ctx->buf_off; + word32 take = (output_length < (size_t)avail) + ? (word32)output_length + : avail; + XMEMCPY(output, ctx->buf + ctx->buf_off, take); + output += take; + output_length -= take; + ctx->buf_off += take; + continue; + } + + /* Staging buffer is empty; reset it. */ + ctx->buf_off = 0; + ctx->buf_len = 0; + + /* 2. If caller wants >= one full block, squeeze directly. */ + if (output_length >= (size_t)ctx->block_size) { + word32 n_blocks = (word32)(output_length / ctx->block_size); + + switch (ctx->alg) { +#ifdef WOLFSSL_SHAKE128 + case PSA_ALG_SHAKE128: + ret = wc_Shake128_SqueezeBlocks(&ctx->shake, output, + n_blocks); + break; +#endif +#ifdef WOLFSSL_SHAKE256 + case PSA_ALG_SHAKE256: + ret = wc_Shake256_SqueezeBlocks(&ctx->shake, output, + n_blocks); + break; +#endif + default: + return wolfpsa_xof_fail(operation, PSA_ERROR_NOT_SUPPORTED); + } + + if (ret != 0) + return wolfpsa_xof_fail(operation, + wc_error_to_psa_status(ret)); + + word32 produced = n_blocks * ctx->block_size; + output += produced; + output_length -= produced; + continue; + } + + /* 3. Tail: squeeze one block into the staging buffer. */ + switch (ctx->alg) { +#ifdef WOLFSSL_SHAKE128 + case PSA_ALG_SHAKE128: + ret = wc_Shake128_SqueezeBlocks(&ctx->shake, ctx->buf, 1); + break; +#endif +#ifdef WOLFSSL_SHAKE256 + case PSA_ALG_SHAKE256: + ret = wc_Shake256_SqueezeBlocks(&ctx->shake, ctx->buf, 1); + break; +#endif + default: + return wolfpsa_xof_fail(operation, PSA_ERROR_NOT_SUPPORTED); + } + + if (ret != 0) + return wolfpsa_xof_fail(operation, wc_error_to_psa_status(ret)); + + ctx->buf_len = ctx->block_size; + ctx->buf_off = 0; + /* loop back to drain */ + } + + return PSA_SUCCESS; +} + +/* ========================================================= psa_xof_abort */ + +psa_status_t psa_xof_abort(psa_xof_operation_t *operation) +{ + psa_xof_operation_ctx_t *ctx; + + if (operation == NULL) + return PSA_ERROR_INVALID_ARGUMENT; + + ctx = psa_xof_get_ctx(operation); + if (ctx == NULL) + return PSA_SUCCESS; /* already inactive: PSA_SUCCESS per spec */ + + psa_xof_free_ctx(ctx); + wc_ForceZero(ctx, sizeof(*ctx)); + XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); + operation->opaque = (uintptr_t)NULL; + + return PSA_SUCCESS; +} + +#endif /* WOLFSSL_SHAKE128 || WOLFSSL_SHAKE256 */ + +#endif /* WOLFSSL_PSA_ENGINE */ From 73f0f540edc0ee7f31b2a15dff05ee56b88795b9 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 14:37:02 +0200 Subject: [PATCH 18/24] psa: wire PQC and 1.4 signature features into key storage and dispatch psa_key_storage.c: ML-DSA/ML-KEM key lifecycle (generate seeds via the PQC backends, import validation for seed and raw public formats, bit inference from public-key lengths, public-key derivation from seeds in psa_export_public_key), LMS/HSS/XMSS public-key import, XChaCha20 keys, WRAP/UNWRAP usage flags and the new psa_check_key_usage. psa_asymmetric_api.c: refactor sign/verify into context-aware workers and add the four *_with_context entry points (context <= 255 bytes, non-empty context only for EDDSA_CTX/Ed25519ph/Ed448ph/Ed448 pure and the ML-DSA family). ML-DSA dispatches before the pre-hash step so raw messages are signed directly; HashML-DSA pre-hashes via PSA_ALG_GET_HASH; pure ML-DSA is rejected in the hash entry points. LMS/HSS/XMSS/XMSS^MT verify through psa_verify_message. Policy gains the HashML-DSA ANY_HASH wildcard and the 1.4 rule that ECDSA and deterministic ECDSA are equivalent for verification. psa_ed25519_ed448.c: thread the signing context through to wolfCrypt (Ed25519ctx via wc_ed25519_sign_msg_ex, Ed448 context parameters). psa_pq.c: ML-DSA sizes are now 128/192/256; LMS/HSS/XMSS/XMSS^MT are public-key-only with bits = hash output length; SLH-DSA reports NOT_SUPPORTED. wolfpsa.map: export the new 1.4 entry points, drop psa_ml_dsa_*. --- src/psa_asymmetric_api.c | 659 ++++++++++++++++++++++++++++++++++++--- src/psa_ed25519_ed448.c | 294 +++++++++++------ src/psa_key_storage.c | 365 +++++++++++++++++++++- src/psa_pq.c | 70 +++-- wolfpsa.map | 44 ++- 5 files changed, 1250 insertions(+), 182 deletions(-) diff --git a/src/psa_asymmetric_api.c b/src/psa_asymmetric_api.c index 8cc96d9..799812d 100644 --- a/src/psa_asymmetric_api.c +++ b/src/psa_asymmetric_api.c @@ -30,6 +30,7 @@ #include #include "psa_size.h" #include "psa_trace.h" +#include "psa_pqc_internal.h" #include #include #include @@ -133,7 +134,9 @@ psa_status_t psa_asymmetric_sign_ed25519(psa_key_type_t key_type, size_t hash_length, uint8_t *signature, size_t signature_size, - size_t *signature_length); + size_t *signature_length, + const uint8_t *context, + size_t context_length); psa_status_t psa_asymmetric_verify_ed25519(psa_key_type_t key_type, size_t key_bits, const uint8_t *key_buffer, @@ -142,7 +145,9 @@ psa_status_t psa_asymmetric_verify_ed25519(psa_key_type_t key_type, const uint8_t *hash, size_t hash_length, const uint8_t *signature, - size_t signature_length); + size_t signature_length, + const uint8_t *context, + size_t context_length); #endif #ifdef HAVE_ED448 psa_status_t psa_asymmetric_sign_ed448(psa_key_type_t key_type, @@ -154,7 +159,9 @@ psa_status_t psa_asymmetric_sign_ed448(psa_key_type_t key_type, size_t hash_length, uint8_t *signature, size_t signature_size, - size_t *signature_length); + size_t *signature_length, + const uint8_t *context, + size_t context_length); psa_status_t psa_asymmetric_verify_ed448(psa_key_type_t key_type, size_t key_bits, const uint8_t *key_buffer, @@ -163,7 +170,9 @@ psa_status_t psa_asymmetric_verify_ed448(psa_key_type_t key_type, const uint8_t *hash, size_t hash_length, const uint8_t *signature, - size_t signature_length); + size_t signature_length, + const uint8_t *context, + size_t context_length); #endif /* Return non-zero if a key whose permitted-algorithm policy is the @@ -189,22 +198,55 @@ static int wolfpsa_key_agreement_alg_permitted(psa_algorithm_t key_alg, } /* Return non-zero if a key whose permitted-algorithm policy is 'key_alg' may be - * used for the requested signature/encryption algorithm 'alg'. The common case - * is exact equality. In addition, a hash-and-sign policy whose hash component is - * the PSA_ALG_ANY_HASH wildcard (e.g. PSA_ALG_ECDSA(PSA_ALG_ANY_HASH) or - * PSA_ALG_RSA_PSS(PSA_ALG_ANY_HASH)) authorizes any concrete hash-and-sign - * algorithm of the same base family, as required by the PSA Crypto API. */ + * used for the requested signature/encryption algorithm 'alg' under the given + * 'requested_usage'. The common case is exact equality. In addition: + * - A hash-and-sign policy whose hash component is PSA_ALG_ANY_HASH (e.g. + * PSA_ALG_ECDSA(PSA_ALG_ANY_HASH) or PSA_ALG_RSA_PSS(PSA_ALG_ANY_HASH)) + * authorizes any concrete hash-and-sign algorithm of the same base family, + * as required by the PSA Crypto API. + * - A HashML-DSA/DeterministicHashML-DSA policy with PSA_ALG_ANY_HASH permits + * any concrete hash variant of the same family. + * - For VERIFY usages, PSA_ALG_ECDSA(h) in the policy permits + * PSA_ALG_DETERMINISTIC_ECDSA(h) requests and vice versa (same hash), per + * PSA 1.4 verify-equivalence. */ static int wolfpsa_sign_alg_permitted(psa_algorithm_t key_alg, - psa_algorithm_t alg) + psa_algorithm_t alg, + psa_key_usage_t requested_usage) { if (key_alg == alg) { return 1; } + /* Standard PSA_ALG_ANY_HASH wildcard for hash-and-sign families */ if (PSA_ALG_IS_SIGN_HASH(alg) && PSA_ALG_SIGN_GET_HASH(key_alg) == PSA_ALG_ANY_HASH) { return (PSA_ALG_SIGN_GET_HASH(alg) != PSA_ALG_ANY_HASH) && ((key_alg & ~PSA_ALG_HASH_MASK) == (alg & ~PSA_ALG_HASH_MASK)); } + /* PSA_ALG_ANY_HASH wildcard for HashML-DSA and DeterministicHashML-DSA */ + if (PSA_ALG_IS_HASH_ML_DSA(alg) && + PSA_ALG_IS_HASH_ML_DSA(key_alg) && + PSA_ALG_GET_HASH(key_alg) == PSA_ALG_ANY_HASH) { + return (PSA_ALG_GET_HASH(alg) != PSA_ALG_ANY_HASH) && + ((key_alg & ~0x000001ffU) == (alg & ~0x000001ffU)); + } + if (PSA_ALG_IS_DETERMINISTIC_HASH_ML_DSA(alg) && + PSA_ALG_IS_DETERMINISTIC_HASH_ML_DSA(key_alg) && + PSA_ALG_GET_HASH(key_alg) == PSA_ALG_ANY_HASH) { + return (PSA_ALG_GET_HASH(alg) != PSA_ALG_ANY_HASH) && + ((key_alg & ~0x000000ffU) == (alg & ~0x000000ffU)); + } + /* PSA 1.4 ECDSA verify-equivalence: for verify usages, ECDSA and + * DETERMINISTIC_ECDSA with the same hash are interchangeable. */ + if ((requested_usage & (PSA_KEY_USAGE_VERIFY_HASH | + PSA_KEY_USAGE_VERIFY_MESSAGE)) != 0) { + if (PSA_ALG_IS_ECDSA(alg) && PSA_ALG_IS_ECDSA(key_alg)) { + /* Same hash, different determinism bit */ + if ((PSA_ALG_GET_HASH(alg) == PSA_ALG_GET_HASH(key_alg)) && + (PSA_ALG_GET_HASH(alg) != PSA_ALG_NONE)) { + return 1; + } + } + } return 0; } @@ -249,7 +291,7 @@ static psa_status_t wolfpsa_asymmetric_check_key(psa_key_id_t key, return PSA_ERROR_NOT_PERMITTED; } } - else if (!wolfpsa_sign_alg_permitted(key_alg, alg)) { + else if (!wolfpsa_sign_alg_permitted(key_alg, alg, usage)) { wolfpsa_forcezero_free_key_data(*key_data, *key_data_length); *key_data = NULL; *key_data_length = 0; @@ -259,6 +301,54 @@ static psa_status_t wolfpsa_asymmetric_check_key(psa_key_id_t key, return PSA_SUCCESS; } +/* Validate context parameter against algorithm/key constraints. + * context_length > 255 is always rejected (RFC 8032 / FIPS 204 limit). + * A non-empty context is only permitted for: + * PSA_ALG_EDDSA_CTX, PSA_ALG_ED25519PH, PSA_ALG_ED448PH, + * PSA_ALG_PURE_EDDSA when the key is Ed448 (bits==448), + * PSA_ALG_IS_ML_DSA / PSA_ALG_IS_HASH_ML_DSA / + * PSA_ALG_IS_DETERMINISTIC_HASH_ML_DSA families. + * All other algorithms with context_length != 0 return + * PSA_ERROR_INVALID_ARGUMENT. */ +static psa_status_t wolfpsa_check_context(psa_algorithm_t alg, + psa_key_type_t key_type, + size_t key_bits, + const uint8_t *context, + size_t context_length) +{ + (void)context; + + if (context_length > 255) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (context_length == 0) { + return PSA_SUCCESS; + } + /* Non-empty context: check permitted algorithms */ + if (alg == PSA_ALG_EDDSA_CTX || + alg == PSA_ALG_ED25519PH || + alg == PSA_ALG_ED448PH) { + return PSA_SUCCESS; + } + if (alg == PSA_ALG_PURE_EDDSA) { + /* Ed448 pure EdDSA accepts a context per RFC 8032 */ + if ((key_type == PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) || + key_type == PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_TWISTED_EDWARDS)) && + key_bits == 448) { + return PSA_SUCCESS; + } + return PSA_ERROR_INVALID_ARGUMENT; + } +#if defined(WOLFSSL_HAVE_MLDSA) + if (PSA_ALG_IS_ML_DSA(alg) || + PSA_ALG_IS_HASH_ML_DSA(alg) || + PSA_ALG_IS_DETERMINISTIC_HASH_ML_DSA(alg)) { + return PSA_SUCCESS; + } +#endif + return PSA_ERROR_INVALID_ARGUMENT; +} + psa_status_t psa_asymmetric_encrypt(psa_key_id_t key, psa_algorithm_t alg, const uint8_t *input, @@ -349,16 +439,17 @@ psa_status_t psa_asymmetric_decrypt(psa_key_id_t key, return status; } -psa_status_t psa_sign_hash(psa_key_id_t key, - psa_algorithm_t alg, - const uint8_t *hash, - size_t hash_length, - uint8_t *signature, - size_t signature_size, - size_t *signature_length) +/* Internal worker: sign a pre-computed hash, with optional context. */ +static psa_status_t wolfpsa_sign_hash_worker(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *hash, + size_t hash_length, + const uint8_t *context, + size_t context_length, + uint8_t *signature, + size_t signature_size, + size_t *signature_length) { - wolfpsa_trace("psa_sign_hash(key=%u alg=0x%08x hash_len=%zu)", - (unsigned)key, (unsigned)alg, hash_length); psa_key_attributes_t attributes; uint8_t *key_data = NULL; size_t key_data_length = 0; @@ -374,6 +465,56 @@ psa_status_t psa_sign_hash(psa_key_id_t key, return status; } + status = wolfpsa_check_context(alg, attributes.type, attributes.bits, + context, context_length); + if (status != PSA_SUCCESS) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return status; + } + +#if defined(WOLFSSL_HAVE_MLDSA) + if (PSA_KEY_TYPE_IS_ML_DSA(attributes.type)) { + /* Pure ML-DSA (sign_hash is not applicable for pure ML-DSA) */ + if (PSA_ALG_IS_ML_DSA(alg)) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + /* HashML-DSA variants: pass pre-computed hash directly */ + if (PSA_ALG_IS_HASH_ML_DSA(alg) || + PSA_ALG_IS_DETERMINISTIC_HASH_ML_DSA(alg)) { + if (attributes.type != PSA_KEY_TYPE_ML_DSA_KEY_PAIR) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + status = wolfpsa_mldsa_sign(attributes.bits, + key_data, key_data_length, + alg, context, context_length, + hash, hash_length, /*input_is_hash*/1, + signature, signature_size, + signature_length); + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return status; + } + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } +#endif /* WOLFSSL_HAVE_MLDSA */ + +#if defined(WOLFSSL_HAVE_LMS) + if (attributes.type == PSA_KEY_TYPE_LMS_PUBLIC_KEY || + attributes.type == PSA_KEY_TYPE_HSS_PUBLIC_KEY) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } +#endif +#if defined(WOLFSSL_HAVE_XMSS) + if (attributes.type == PSA_KEY_TYPE_XMSS_PUBLIC_KEY || + attributes.type == PSA_KEY_TYPE_XMSS_MT_PUBLIC_KEY) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } +#endif + if (PSA_KEY_TYPE_IS_RSA(attributes.type)) { status = psa_asymmetric_sign_rsa(attributes.type, attributes.bits, key_data, key_data_length, @@ -389,7 +530,8 @@ psa_status_t psa_sign_hash(psa_key_id_t key, key_data, key_data_length, alg, hash, hash_length, signature, signature_size, - signature_length); + signature_length, + context, context_length); } else #endif @@ -399,7 +541,8 @@ psa_status_t psa_sign_hash(psa_key_id_t key, key_data, key_data_length, alg, hash, hash_length, signature, signature_size, - signature_length); + signature_length, + context, context_length); } else #endif @@ -423,15 +566,17 @@ psa_status_t psa_sign_hash(psa_key_id_t key, return status; } -psa_status_t psa_verify_hash(psa_key_id_t key, - psa_algorithm_t alg, - const uint8_t *hash, - size_t hash_length, - const uint8_t *signature, - size_t signature_length) +/* Internal worker: verify a signature over a pre-computed hash, with optional + * context. */ +static psa_status_t wolfpsa_verify_hash_worker(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *hash, + size_t hash_length, + const uint8_t *context, + size_t context_length, + const uint8_t *signature, + size_t signature_length) { - wolfpsa_trace("psa_verify_hash(key=%u alg=0x%08x hash_len=%zu sig_len=%zu)", - (unsigned)key, (unsigned)alg, hash_length, signature_length); psa_key_attributes_t attributes; uint8_t *key_data = NULL; size_t key_data_length = 0; @@ -447,6 +592,51 @@ psa_status_t psa_verify_hash(psa_key_id_t key, return status; } + status = wolfpsa_check_context(alg, attributes.type, attributes.bits, + context, context_length); + if (status != PSA_SUCCESS) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return status; + } + +#if defined(WOLFSSL_HAVE_MLDSA) + if (PSA_KEY_TYPE_IS_ML_DSA(attributes.type)) { + /* Pure ML-DSA (verify_hash is not applicable for pure ML-DSA) */ + if (PSA_ALG_IS_ML_DSA(alg)) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + /* HashML-DSA variants: pass pre-computed hash directly */ + if (PSA_ALG_IS_HASH_ML_DSA(alg) || + PSA_ALG_IS_DETERMINISTIC_HASH_ML_DSA(alg)) { + status = wolfpsa_mldsa_verify(attributes.bits, attributes.type, + key_data, key_data_length, + alg, context, context_length, + hash, hash_length, /*input_is_hash*/1, + signature, signature_length); + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return status; + } + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } +#endif /* WOLFSSL_HAVE_MLDSA */ + +#if defined(WOLFSSL_HAVE_LMS) + if (attributes.type == PSA_KEY_TYPE_LMS_PUBLIC_KEY || + attributes.type == PSA_KEY_TYPE_HSS_PUBLIC_KEY) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } +#endif +#if defined(WOLFSSL_HAVE_XMSS) + if (attributes.type == PSA_KEY_TYPE_XMSS_PUBLIC_KEY || + attributes.type == PSA_KEY_TYPE_XMSS_MT_PUBLIC_KEY) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } +#endif + if (PSA_KEY_TYPE_IS_RSA(attributes.type)) { status = psa_asymmetric_verify_rsa(attributes.type, attributes.bits, key_data, key_data_length, @@ -461,7 +651,8 @@ psa_status_t psa_verify_hash(psa_key_id_t key, status = psa_asymmetric_verify_ed25519(attributes.type, attributes.bits, key_data, key_data_length, alg, hash, hash_length, - signature, signature_length); + signature, signature_length, + context, context_length); } else #endif @@ -470,7 +661,8 @@ psa_status_t psa_verify_hash(psa_key_id_t key, status = psa_asymmetric_verify_ed448(attributes.type, attributes.bits, key_data, key_data_length, alg, hash, hash_length, - signature, signature_length); + signature, signature_length, + context, context_length); } else #endif @@ -493,13 +685,81 @@ psa_status_t psa_verify_hash(psa_key_id_t key, return status; } -psa_status_t psa_sign_message(psa_key_id_t key, - psa_algorithm_t alg, - const uint8_t *input, - size_t input_length, - uint8_t *signature, - size_t signature_size, - size_t *signature_length) +psa_status_t psa_sign_hash(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *hash, + size_t hash_length, + uint8_t *signature, + size_t signature_size, + size_t *signature_length) +{ + wolfpsa_trace("psa_sign_hash(key=%u alg=0x%08x hash_len=%zu)", + (unsigned)key, (unsigned)alg, hash_length); + return wolfpsa_sign_hash_worker(key, alg, hash, hash_length, + NULL, 0, + signature, signature_size, + signature_length); +} + +psa_status_t psa_verify_hash(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *hash, + size_t hash_length, + const uint8_t *signature, + size_t signature_length) +{ + wolfpsa_trace("psa_verify_hash(key=%u alg=0x%08x hash_len=%zu sig_len=%zu)", + (unsigned)key, (unsigned)alg, hash_length, signature_length); + return wolfpsa_verify_hash_worker(key, alg, hash, hash_length, + NULL, 0, + signature, signature_length); +} + +psa_status_t psa_sign_hash_with_context(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *hash, + size_t hash_length, + const uint8_t *context, + size_t context_length, + uint8_t *signature, + size_t signature_size, + size_t *signature_length) +{ + wolfpsa_trace("psa_sign_hash_with_context(key=%u alg=0x%08x hash_len=%zu ctx_len=%zu)", + (unsigned)key, (unsigned)alg, hash_length, context_length); + return wolfpsa_sign_hash_worker(key, alg, hash, hash_length, + context, context_length, + signature, signature_size, + signature_length); +} + +psa_status_t psa_verify_hash_with_context(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *hash, + size_t hash_length, + const uint8_t *context, + size_t context_length, + const uint8_t *signature, + size_t signature_length) +{ + wolfpsa_trace("psa_verify_hash_with_context(key=%u alg=0x%08x hash_len=%zu ctx_len=%zu)", + (unsigned)key, (unsigned)alg, hash_length, context_length); + return wolfpsa_verify_hash_worker(key, alg, hash, hash_length, + context, context_length, + signature, signature_length); +} + +/* Internal worker: sign a message (hash-then-sign or pure EdDSA/ML-DSA), + * with optional context. */ +static psa_status_t wolfpsa_sign_message_worker(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *input, + size_t input_length, + const uint8_t *context, + size_t context_length, + uint8_t *signature, + size_t signature_size, + size_t *signature_length) { psa_key_attributes_t attributes; uint8_t *key_data = NULL; @@ -519,6 +779,109 @@ psa_status_t psa_sign_message(psa_key_id_t key, return status; } + status = wolfpsa_check_context(alg, attributes.type, attributes.bits, + context, context_length); + if (status != PSA_SUCCESS) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return status; + } + +#if defined(WOLFSSL_HAVE_MLDSA) + if (PSA_KEY_TYPE_IS_ML_DSA(attributes.type)) { + if (!PSA_ALG_IS_ML_DSA(alg) && + !PSA_ALG_IS_HASH_ML_DSA(alg) && + !PSA_ALG_IS_DETERMINISTIC_HASH_ML_DSA(alg)) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + if (attributes.type != PSA_KEY_TYPE_ML_DSA_KEY_PAIR) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + if (PSA_ALG_IS_ML_DSA(alg)) { + /* Pure ML-DSA: sign the raw message */ + status = wolfpsa_mldsa_sign(attributes.bits, + key_data, key_data_length, + alg, context, context_length, + input, input_length, /*input_is_hash*/0, + signature, signature_size, + signature_length); + } + else { + /* HashML-DSA: pre-hash the message then pass digest */ + uint8_t mldsa_hash[PSA_HASH_MAX_SIZE]; + size_t mldsa_hash_length = 0; + + hash_alg = PSA_ALG_GET_HASH(alg); + status = psa_hash_compute(hash_alg, input, input_length, + mldsa_hash, sizeof(mldsa_hash), + &mldsa_hash_length); + if (status == PSA_SUCCESS) { + status = wolfpsa_mldsa_sign(attributes.bits, + key_data, key_data_length, + alg, context, context_length, + mldsa_hash, mldsa_hash_length, + /*input_is_hash*/1, + signature, signature_size, + signature_length); + } + wc_ForceZero(mldsa_hash, sizeof(mldsa_hash)); + } + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return status; + } +#endif /* WOLFSSL_HAVE_MLDSA */ + +#if defined(WOLFSSL_HAVE_LMS) + if (attributes.type == PSA_KEY_TYPE_LMS_PUBLIC_KEY || + attributes.type == PSA_KEY_TYPE_HSS_PUBLIC_KEY) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } +#endif +#if defined(WOLFSSL_HAVE_XMSS) + if (attributes.type == PSA_KEY_TYPE_XMSS_PUBLIC_KEY || + attributes.type == PSA_KEY_TYPE_XMSS_MT_PUBLIC_KEY) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } +#endif + + /* For pure EdDSA (PSA_ALG_PURE_EDDSA and PSA_ALG_EDDSA_CTX), bypass + * the pre-hash step and pass the raw message directly to the backend. */ +#if defined(HAVE_ED25519) || defined(HAVE_ED448) + if ((alg == PSA_ALG_PURE_EDDSA || alg == PSA_ALG_EDDSA_CTX) && + attributes.type == PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS)) { + #ifdef HAVE_ED25519 + if (attributes.bits == 255) { + status = psa_asymmetric_sign_ed25519(attributes.type, attributes.bits, + key_data, key_data_length, + alg, input, input_length, + signature, signature_size, + signature_length, + context, context_length); + } + else + #endif + #ifdef HAVE_ED448 + if (attributes.bits == 448) { + status = psa_asymmetric_sign_ed448(attributes.type, attributes.bits, + key_data, key_data_length, + alg, input, input_length, + signature, signature_size, + signature_length, + context, context_length); + } + else + #endif + { + status = PSA_ERROR_NOT_SUPPORTED; + } + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return status; + } +#endif /* HAVE_ED25519 || HAVE_ED448 */ + if (alg == PSA_ALG_RSA_PKCS1V15_SIGN_RAW) { hash_alg = 0; hash_length = input_length; @@ -557,7 +920,8 @@ psa_status_t psa_sign_message(psa_key_id_t key, key_data, key_data_length, alg, hash, hash_length, signature, signature_size, - signature_length); + signature_length, + context, context_length); } else #endif @@ -567,7 +931,8 @@ psa_status_t psa_sign_message(psa_key_id_t key, key_data, key_data_length, alg, hash, hash_length, signature, signature_size, - signature_length); + signature_length, + context, context_length); } else #endif @@ -593,12 +958,15 @@ psa_status_t psa_sign_message(psa_key_id_t key, return status; } -psa_status_t psa_verify_message(psa_key_id_t key, - psa_algorithm_t alg, - const uint8_t *input, - size_t input_length, - const uint8_t *signature, - size_t signature_length) +/* Internal worker: verify a signature over a message, with optional context. */ +static psa_status_t wolfpsa_verify_message_worker(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *input, + size_t input_length, + const uint8_t *context, + size_t context_length, + const uint8_t *signature, + size_t signature_length) { psa_key_attributes_t attributes; uint8_t *key_data = NULL; @@ -618,6 +986,137 @@ psa_status_t psa_verify_message(psa_key_id_t key, return status; } + status = wolfpsa_check_context(alg, attributes.type, attributes.bits, + context, context_length); + if (status != PSA_SUCCESS) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return status; + } + +#if defined(WOLFSSL_HAVE_MLDSA) + if (PSA_KEY_TYPE_IS_ML_DSA(attributes.type)) { + if (!PSA_ALG_IS_ML_DSA(alg) && + !PSA_ALG_IS_HASH_ML_DSA(alg) && + !PSA_ALG_IS_DETERMINISTIC_HASH_ML_DSA(alg)) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + if (PSA_ALG_IS_ML_DSA(alg)) { + /* Pure ML-DSA: verify the raw message */ + status = wolfpsa_mldsa_verify(attributes.bits, attributes.type, + key_data, key_data_length, + alg, context, context_length, + input, input_length, /*input_is_hash*/0, + signature, signature_length); + } + else { + /* HashML-DSA: pre-hash the message then pass digest */ + uint8_t mldsa_hash[PSA_HASH_MAX_SIZE]; + size_t mldsa_hash_length = 0; + + hash_alg = PSA_ALG_GET_HASH(alg); + status = psa_hash_compute(hash_alg, input, input_length, + mldsa_hash, sizeof(mldsa_hash), + &mldsa_hash_length); + if (status == PSA_SUCCESS) { + status = wolfpsa_mldsa_verify(attributes.bits, attributes.type, + key_data, key_data_length, + alg, context, context_length, + mldsa_hash, mldsa_hash_length, + /*input_is_hash*/1, + signature, signature_length); + } + wc_ForceZero(mldsa_hash, sizeof(mldsa_hash)); + } + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return status; + } +#endif /* WOLFSSL_HAVE_MLDSA */ + +#if defined(WOLFSSL_HAVE_LMS) + if (attributes.type == PSA_KEY_TYPE_LMS_PUBLIC_KEY) { + if (alg != PSA_ALG_LMS) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + status = wolfpsa_lms_verify(key_data, key_data_length, + input, input_length, + signature, signature_length); + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return status; + } + if (attributes.type == PSA_KEY_TYPE_HSS_PUBLIC_KEY) { + if (alg != PSA_ALG_HSS) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + status = wolfpsa_lms_verify(key_data, key_data_length, + input, input_length, + signature, signature_length); + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return status; + } +#endif /* WOLFSSL_HAVE_LMS */ + +#if defined(WOLFSSL_HAVE_XMSS) + if (attributes.type == PSA_KEY_TYPE_XMSS_PUBLIC_KEY) { + if (alg != PSA_ALG_XMSS) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + status = wolfpsa_xmss_verify(key_data, key_data_length, + input, input_length, + signature, signature_length); + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return status; + } + if (attributes.type == PSA_KEY_TYPE_XMSS_MT_PUBLIC_KEY) { + if (alg != PSA_ALG_XMSS_MT) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + status = wolfpsa_xmss_verify(key_data, key_data_length, + input, input_length, + signature, signature_length); + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return status; + } +#endif /* WOLFSSL_HAVE_XMSS */ + + /* For pure EdDSA (PSA_ALG_PURE_EDDSA and PSA_ALG_EDDSA_CTX), bypass + * the pre-hash step and pass the raw message directly to the backend. */ +#if defined(HAVE_ED25519) || defined(HAVE_ED448) + if ((alg == PSA_ALG_PURE_EDDSA || alg == PSA_ALG_EDDSA_CTX) && + (attributes.type == PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) || + attributes.type == PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_TWISTED_EDWARDS))) { + #ifdef HAVE_ED25519 + if (attributes.bits == 255) { + status = psa_asymmetric_verify_ed25519(attributes.type, attributes.bits, + key_data, key_data_length, + alg, input, input_length, + signature, signature_length, + context, context_length); + } + else + #endif + #ifdef HAVE_ED448 + if (attributes.bits == 448) { + status = psa_asymmetric_verify_ed448(attributes.type, attributes.bits, + key_data, key_data_length, + alg, input, input_length, + signature, signature_length, + context, context_length); + } + else + #endif + { + status = PSA_ERROR_NOT_SUPPORTED; + } + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return status; + } +#endif /* HAVE_ED25519 || HAVE_ED448 */ + if (alg == PSA_ALG_RSA_PKCS1V15_SIGN_RAW) { hash_alg = 0; hash_length = input_length; @@ -655,7 +1154,8 @@ psa_status_t psa_verify_message(psa_key_id_t key, status = psa_asymmetric_verify_ed25519(attributes.type, attributes.bits, key_data, key_data_length, alg, hash, hash_length, - signature, signature_length); + signature, signature_length, + context, context_length); } else #endif @@ -664,7 +1164,8 @@ psa_status_t psa_verify_message(psa_key_id_t key, status = psa_asymmetric_verify_ed448(attributes.type, attributes.bits, key_data, key_data_length, alg, hash, hash_length, - signature, signature_length); + signature, signature_length, + context, context_length); } else #endif @@ -689,6 +1190,66 @@ psa_status_t psa_verify_message(psa_key_id_t key, return status; } +psa_status_t psa_sign_message(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *input, + size_t input_length, + uint8_t *signature, + size_t signature_size, + size_t *signature_length) +{ + return wolfpsa_sign_message_worker(key, alg, input, input_length, + NULL, 0, + signature, signature_size, + signature_length); +} + +psa_status_t psa_verify_message(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *input, + size_t input_length, + const uint8_t *signature, + size_t signature_length) +{ + return wolfpsa_verify_message_worker(key, alg, input, input_length, + NULL, 0, + signature, signature_length); +} + +psa_status_t psa_sign_message_with_context(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *input, + size_t input_length, + const uint8_t *context, + size_t context_length, + uint8_t *signature, + size_t signature_size, + size_t *signature_length) +{ + wolfpsa_trace("psa_sign_message_with_context(key=%u alg=0x%08x in_len=%zu ctx_len=%zu)", + (unsigned)key, (unsigned)alg, input_length, context_length); + return wolfpsa_sign_message_worker(key, alg, input, input_length, + context, context_length, + signature, signature_size, + signature_length); +} + +psa_status_t psa_verify_message_with_context(psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *input, + size_t input_length, + const uint8_t *context, + size_t context_length, + const uint8_t *signature, + size_t signature_length) +{ + wolfpsa_trace("psa_verify_message_with_context(key=%u alg=0x%08x in_len=%zu ctx_len=%zu)", + (unsigned)key, (unsigned)alg, input_length, context_length); + return wolfpsa_verify_message_worker(key, alg, input, input_length, + context, context_length, + signature, signature_length); +} + /* Compute the raw ECDH shared secret after verifying that the private key's * policy permits the full key-agreement algorithm 'alg'. 'alg' is the complete * key-agreement algorithm requested by the caller (raw PSA_ALG_ECDH, or a diff --git a/src/psa_ed25519_ed448.c b/src/psa_ed25519_ed448.c index 1c21861..2a15926 100644 --- a/src/psa_ed25519_ed448.c +++ b/src/psa_ed25519_ed448.c @@ -56,18 +56,34 @@ psa_status_t psa_asymmetric_sign_ed25519(psa_key_type_t key_type, size_t hash_length, uint8_t *signature, size_t signature_size, - size_t *signature_length) + size_t *signature_length, + const uint8_t *context, + size_t context_length) { int ret; ed25519_key ed_key; word32 sig_len32; - - if (alg != PSA_ALG_ED25519PH) { + const byte *ctx_ptr; + byte ctx_len; + + if (alg != PSA_ALG_PURE_EDDSA && + alg != PSA_ALG_EDDSA_CTX && + alg != PSA_ALG_ED25519PH) { return PSA_ERROR_NOT_SUPPORTED; } + /* PSA_ALG_PURE_EDDSA forbids any context */ + if (alg == PSA_ALG_PURE_EDDSA && context_length != 0) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + /* context must fit in a byte for all Ed25519 variants */ + if (context_length > 255u) { + return PSA_ERROR_INVALID_ARGUMENT; + } + /* Check if key type is ED25519 key pair */ - if (key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) || + if (key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) || key_bits != 255) { return PSA_ERROR_INVALID_ARGUMENT; } @@ -76,12 +92,17 @@ psa_status_t psa_asymmetric_sign_ed25519(psa_key_type_t key_type, (wolfpsa_check_word32_length(signature_size) != PSA_SUCCESS)) { return PSA_ERROR_INVALID_ARGUMENT; } + /* HashEdDSA (Ed25519ph) signs the SHA-512 prehash, which is exactly * PSA_HASH_LENGTH(PSA_ALG_SHA_512) (64) bytes. */ - if (hash_length != PSA_HASH_LENGTH(PSA_ALG_SHA_512)) { + if (alg == PSA_ALG_ED25519PH && + hash_length != PSA_HASH_LENGTH(PSA_ALG_SHA_512)) { return PSA_ERROR_INVALID_ARGUMENT; } + ctx_ptr = (context_length > 0u) ? (const byte *)context : NULL; + ctx_len = (byte)context_length; + /* Initialize ED25519 key */ ret = wc_ed25519_init(&ed_key); if (ret != 0) { @@ -98,20 +119,31 @@ psa_status_t psa_asymmetric_sign_ed25519(psa_key_type_t key_type, wc_ed25519_free(&ed_key); return wc_error_to_psa_status(ret); } - - /* Sign message */ + sig_len32 = (word32)signature_size; - ret = wc_ed25519ph_sign_hash(hash, (word32)hash_length, signature, - &sig_len32, &ed_key, NULL, 0); - + + if (alg == PSA_ALG_ED25519PH) { + /* HashEdDSA: sign the prehash with optional context */ + ret = wc_ed25519ph_sign_hash(hash, (word32)hash_length, signature, + &sig_len32, &ed_key, ctx_ptr, ctx_len); + } + else { + /* PURE_EDDSA (Ed25519, no context) or EDDSA_CTX (Ed25519ctx, with + * optional context): use the _ex variant selecting the right type. */ + byte wc_type = (alg == PSA_ALG_EDDSA_CTX) ? Ed25519ctx : Ed25519; + ret = wc_ed25519_sign_msg_ex(hash, (word32)hash_length, signature, + &sig_len32, &ed_key, wc_type, + ctx_ptr, ctx_len); + } + wc_ed25519_free(&ed_key); - + if (ret != 0) { return wc_error_to_psa_status(ret); } *signature_length = (size_t)sig_len32; - + return PSA_SUCCESS; } @@ -124,16 +156,32 @@ psa_status_t psa_asymmetric_verify_ed25519(psa_key_type_t key_type, const uint8_t *hash, size_t hash_length, const uint8_t *signature, - size_t signature_length) + size_t signature_length, + const uint8_t *context, + size_t context_length) { int ret; ed25519_key ed_key; int verify_res = 0; - - if (alg != PSA_ALG_ED25519PH) { + const byte *ctx_ptr; + byte ctx_len; + + if (alg != PSA_ALG_PURE_EDDSA && + alg != PSA_ALG_EDDSA_CTX && + alg != PSA_ALG_ED25519PH) { return PSA_ERROR_NOT_SUPPORTED; } + /* PSA_ALG_PURE_EDDSA forbids any context */ + if (alg == PSA_ALG_PURE_EDDSA && context_length != 0) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + /* context must fit in a byte for all Ed25519 variants */ + if (context_length > 255u) { + return PSA_ERROR_INVALID_ARGUMENT; + } + /* Check if key type is ED25519 */ if ((key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) && key_type != PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_TWISTED_EDWARDS)) || @@ -145,18 +193,23 @@ psa_status_t psa_asymmetric_verify_ed25519(psa_key_type_t key_type, (wolfpsa_check_word32_length(signature_length) != PSA_SUCCESS)) { return PSA_ERROR_INVALID_ARGUMENT; } + /* HashEdDSA (Ed25519ph) verifies over the SHA-512 prehash, which is * exactly PSA_HASH_LENGTH(PSA_ALG_SHA_512) (64) bytes. */ - if (hash_length != PSA_HASH_LENGTH(PSA_ALG_SHA_512)) { + if (alg == PSA_ALG_ED25519PH && + hash_length != PSA_HASH_LENGTH(PSA_ALG_SHA_512)) { return PSA_ERROR_INVALID_ARGUMENT; } + ctx_ptr = (context_length > 0u) ? (const byte *)context : NULL; + ctx_len = (byte)context_length; + /* Initialize ED25519 key */ ret = wc_ed25519_init(&ed_key); if (ret != 0) { return wc_error_to_psa_status(ret); } - + /* Import the key material needed for verification. */ if (key_type == PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS)) { ret = wc_ed25519_import_private_only(key_buffer, (word32)key_buffer_size, @@ -173,22 +226,33 @@ psa_status_t psa_asymmetric_verify_ed25519(psa_key_type_t key_type, wc_ed25519_free(&ed_key); return wc_error_to_psa_status(ret); } - - /* Verify signature */ - ret = wc_ed25519ph_verify_hash(signature, (word32)signature_length, - hash, (word32)hash_length, - &verify_res, &ed_key, NULL, 0); - + + if (alg == PSA_ALG_ED25519PH) { + /* HashEdDSA: verify the prehash with optional context */ + ret = wc_ed25519ph_verify_hash(signature, (word32)signature_length, + hash, (word32)hash_length, + &verify_res, &ed_key, ctx_ptr, ctx_len); + } + else { + /* PURE_EDDSA (Ed25519, no context) or EDDSA_CTX (Ed25519ctx, with + * optional context): use the _ex variant selecting the right type. */ + byte wc_type = (alg == PSA_ALG_EDDSA_CTX) ? Ed25519ctx : Ed25519; + ret = wc_ed25519_verify_msg_ex(signature, (word32)signature_length, + hash, (word32)hash_length, + &verify_res, &ed_key, wc_type, + ctx_ptr, ctx_len); + } + wc_ed25519_free(&ed_key); - + if (ret != 0) { return wc_error_to_psa_status(ret); } - + if (verify_res != 1) { return PSA_ERROR_INVALID_SIGNATURE; } - + return PSA_SUCCESS; } @@ -207,9 +271,9 @@ psa_status_t psa_asymmetric_generate_key_ed25519(psa_key_type_t key_type, WC_RNG rng; word32 priv_len32; word32 pub_len32; - + /* Check if key type is ED25519 key pair */ - if (key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) || + if (key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) || key_bits != 255) { return PSA_ERROR_INVALID_ARGUMENT; } @@ -217,20 +281,20 @@ psa_status_t psa_asymmetric_generate_key_ed25519(psa_key_type_t key_type, (wolfpsa_check_word32_length(public_key_size) != PSA_SUCCESS)) { return PSA_ERROR_INVALID_ARGUMENT; } - + /* Initialize ED25519 key */ ret = wc_ed25519_init(&ed_key); if (ret != 0) { return wc_error_to_psa_status(ret); } - + /* Initialize RNG */ ret = wc_InitRng(&rng); if (ret != 0) { wc_ed25519_free(&ed_key); return wc_error_to_psa_status(ret); } - + /* Generate key pair */ ret = wc_ed25519_make_key(&rng, ED25519_KEY_SIZE, &ed_key); if (ret != 0) { @@ -238,7 +302,7 @@ psa_status_t psa_asymmetric_generate_key_ed25519(psa_key_type_t key_type, wc_ed25519_free(&ed_key); return wc_error_to_psa_status(ret); } - + /* Export private key */ priv_len32 = (word32)private_key_size; ret = wc_ed25519_export_private_only(&ed_key, private_key, &priv_len32); @@ -247,7 +311,7 @@ psa_status_t psa_asymmetric_generate_key_ed25519(psa_key_type_t key_type, wc_ed25519_free(&ed_key); return wc_error_to_psa_status(ret); } - + /* Export public key */ pub_len32 = (word32)public_key_size; ret = wc_ed25519_export_public(&ed_key, public_key, &pub_len32); @@ -256,13 +320,13 @@ psa_status_t psa_asymmetric_generate_key_ed25519(psa_key_type_t key_type, wc_ed25519_free(&ed_key); return wc_error_to_psa_status(ret); } - + wc_FreeRng(&rng); wc_ed25519_free(&ed_key); *private_key_length = (size_t)priv_len32; *public_key_length = (size_t)pub_len32; - + return PSA_SUCCESS; } @@ -278,10 +342,10 @@ psa_status_t psa_asymmetric_export_public_key_ed25519(psa_key_type_t key_type, int ret; ed25519_key ed_key; word32 out_len32; - + /* Check if key type is ED25519 */ - if ((key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) && - key_type != PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_TWISTED_EDWARDS)) || + if ((key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) && + key_type != PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_TWISTED_EDWARDS)) || key_bits != 255) { return PSA_ERROR_INVALID_ARGUMENT; } @@ -289,13 +353,13 @@ psa_status_t psa_asymmetric_export_public_key_ed25519(psa_key_type_t key_type, (wolfpsa_check_word32_length(output_size) != PSA_SUCCESS)) { return PSA_ERROR_INVALID_ARGUMENT; } - + /* Initialize ED25519 key */ ret = wc_ed25519_init(&ed_key); if (ret != 0) { return wc_error_to_psa_status(ret); } - + /* Import key */ if (key_type == PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS)) { ret = wc_ed25519_import_private_only(key_buffer, (word32)key_buffer_size, @@ -308,24 +372,24 @@ psa_status_t psa_asymmetric_export_public_key_ed25519(psa_key_type_t key_type, else { ret = wc_ed25519_import_public(key_buffer, (word32)key_buffer_size, &ed_key); } - + if (ret != 0) { wc_ed25519_free(&ed_key); return wc_error_to_psa_status(ret); } - + /* Export public key */ out_len32 = (word32)output_size; ret = wc_ed25519_export_public(&ed_key, output, &out_len32); - + wc_ed25519_free(&ed_key); - + if (ret != 0) { return wc_error_to_psa_status(ret); } *output_length = (size_t)out_len32; - + return PSA_SUCCESS; } #endif /* HAVE_ED25519 */ @@ -341,18 +405,29 @@ psa_status_t psa_asymmetric_sign_ed448(psa_key_type_t key_type, size_t hash_length, uint8_t *signature, size_t signature_size, - size_t *signature_length) + size_t *signature_length, + const uint8_t *context, + size_t context_length) { int ret; ed448_key ed_key; word32 sig_len32; - - if (alg != PSA_ALG_ED448PH) { + const byte *ctx_ptr; + byte ctx_len; + + if (alg != PSA_ALG_PURE_EDDSA && + alg != PSA_ALG_EDDSA_CTX && + alg != PSA_ALG_ED448PH) { return PSA_ERROR_NOT_SUPPORTED; } + /* context must fit in a byte for all Ed448 variants */ + if (context_length > 255u) { + return PSA_ERROR_INVALID_ARGUMENT; + } + /* Check if key type is ED448 key pair */ - if (key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) || + if (key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) || key_bits != 448) { return PSA_ERROR_INVALID_ARGUMENT; } @@ -361,18 +436,22 @@ psa_status_t psa_asymmetric_sign_ed448(psa_key_type_t key_type, (wolfpsa_check_word32_length(signature_size) != PSA_SUCCESS)) { return PSA_ERROR_INVALID_ARGUMENT; } + /* HashEdDSA (Ed448ph) signs the 64-byte SHAKE256 prehash * (PSA_HASH_LENGTH(PSA_ALG_SHAKE256_512)). */ - if (hash_length != 64u) { + if (alg == PSA_ALG_ED448PH && hash_length != 64u) { return PSA_ERROR_INVALID_ARGUMENT; } + ctx_ptr = (context_length > 0u) ? (const byte *)context : NULL; + ctx_len = (byte)context_length; + /* Initialize ED448 key */ ret = wc_ed448_init(&ed_key); if (ret != 0) { return wc_error_to_psa_status(ret); } - + /* Import the stored private seed and derive the public component. */ ret = wc_ed448_import_private_only(key_buffer, (word32)key_buffer_size, &ed_key); @@ -383,20 +462,30 @@ psa_status_t psa_asymmetric_sign_ed448(psa_key_type_t key_type, wc_ed448_free(&ed_key); return wc_error_to_psa_status(ret); } - - /* Sign message */ + sig_len32 = (word32)signature_size; - ret = wc_ed448ph_sign_hash(hash, (word32)hash_length, signature, - &sig_len32, &ed_key, NULL, 0); - + + if (alg == PSA_ALG_ED448PH) { + /* HashEdDSA: sign the prehash with optional context */ + ret = wc_ed448ph_sign_hash(hash, (word32)hash_length, signature, + &sig_len32, &ed_key, ctx_ptr, ctx_len); + } + else { + /* PURE_EDDSA and EDDSA_CTX both map to Ed448 PureEdDSA with context + * (RFC 8032: Ed448 has no separate ctx variant, context is always + * part of the domain separation string). */ + ret = wc_ed448_sign_msg(hash, (word32)hash_length, signature, + &sig_len32, &ed_key, ctx_ptr, ctx_len); + } + wc_ed448_free(&ed_key); - + if (ret != 0) { return wc_error_to_psa_status(ret); } *signature_length = (size_t)sig_len32; - + return PSA_SUCCESS; } @@ -409,16 +498,27 @@ psa_status_t psa_asymmetric_verify_ed448(psa_key_type_t key_type, const uint8_t *hash, size_t hash_length, const uint8_t *signature, - size_t signature_length) + size_t signature_length, + const uint8_t *context, + size_t context_length) { int ret; ed448_key ed_key; int verify_res = 0; - - if (alg != PSA_ALG_ED448PH) { + const byte *ctx_ptr; + byte ctx_len; + + if (alg != PSA_ALG_PURE_EDDSA && + alg != PSA_ALG_EDDSA_CTX && + alg != PSA_ALG_ED448PH) { return PSA_ERROR_NOT_SUPPORTED; } + /* context must fit in a byte for all Ed448 variants */ + if (context_length > 255u) { + return PSA_ERROR_INVALID_ARGUMENT; + } + /* Check if key type is ED448 */ if ((key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) && key_type != PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_TWISTED_EDWARDS)) || @@ -430,18 +530,22 @@ psa_status_t psa_asymmetric_verify_ed448(psa_key_type_t key_type, (wolfpsa_check_word32_length(signature_length) != PSA_SUCCESS)) { return PSA_ERROR_INVALID_ARGUMENT; } + /* HashEdDSA (Ed448ph) verifies over the 64-byte SHAKE256 prehash * (PSA_HASH_LENGTH(PSA_ALG_SHAKE256_512)). */ - if (hash_length != 64u) { + if (alg == PSA_ALG_ED448PH && hash_length != 64u) { return PSA_ERROR_INVALID_ARGUMENT; } + ctx_ptr = (context_length > 0u) ? (const byte *)context : NULL; + ctx_len = (byte)context_length; + /* Initialize ED448 key */ ret = wc_ed448_init(&ed_key); if (ret != 0) { return wc_error_to_psa_status(ret); } - + /* Import the key material needed for verification. */ if (key_type == PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS)) { ret = wc_ed448_import_private_only(key_buffer, (word32)key_buffer_size, @@ -457,22 +561,30 @@ psa_status_t psa_asymmetric_verify_ed448(psa_key_type_t key_type, wc_ed448_free(&ed_key); return wc_error_to_psa_status(ret); } - - /* Verify signature */ - ret = wc_ed448ph_verify_hash(signature, (word32)signature_length, - hash, (word32)hash_length, - &verify_res, &ed_key, NULL, 0); - + + if (alg == PSA_ALG_ED448PH) { + /* HashEdDSA: verify the prehash with optional context */ + ret = wc_ed448ph_verify_hash(signature, (word32)signature_length, + hash, (word32)hash_length, + &verify_res, &ed_key, ctx_ptr, ctx_len); + } + else { + /* PURE_EDDSA and EDDSA_CTX both map to Ed448 PureEdDSA with context. */ + ret = wc_ed448_verify_msg(signature, (word32)signature_length, + hash, (word32)hash_length, + &verify_res, &ed_key, ctx_ptr, ctx_len); + } + wc_ed448_free(&ed_key); - + if (ret != 0) { return wc_error_to_psa_status(ret); } - + if (verify_res != 1) { return PSA_ERROR_INVALID_SIGNATURE; } - + return PSA_SUCCESS; } @@ -491,9 +603,9 @@ psa_status_t psa_asymmetric_generate_key_ed448(psa_key_type_t key_type, WC_RNG rng; word32 priv_len32; word32 pub_len32; - + /* Check if key type is ED448 key pair */ - if (key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) || + if (key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) || key_bits != 448) { return PSA_ERROR_INVALID_ARGUMENT; } @@ -501,20 +613,20 @@ psa_status_t psa_asymmetric_generate_key_ed448(psa_key_type_t key_type, (wolfpsa_check_word32_length(public_key_size) != PSA_SUCCESS)) { return PSA_ERROR_INVALID_ARGUMENT; } - + /* Initialize ED448 key */ ret = wc_ed448_init(&ed_key); if (ret != 0) { return wc_error_to_psa_status(ret); } - + /* Initialize RNG */ ret = wc_InitRng(&rng); if (ret != 0) { wc_ed448_free(&ed_key); return wc_error_to_psa_status(ret); } - + /* Generate key pair */ ret = wc_ed448_make_key(&rng, ED448_KEY_SIZE, &ed_key); if (ret != 0) { @@ -522,7 +634,7 @@ psa_status_t psa_asymmetric_generate_key_ed448(psa_key_type_t key_type, wc_ed448_free(&ed_key); return wc_error_to_psa_status(ret); } - + /* Export private key */ priv_len32 = (word32)private_key_size; ret = wc_ed448_export_private_only(&ed_key, private_key, &priv_len32); @@ -531,7 +643,7 @@ psa_status_t psa_asymmetric_generate_key_ed448(psa_key_type_t key_type, wc_ed448_free(&ed_key); return wc_error_to_psa_status(ret); } - + /* Export public key */ pub_len32 = (word32)public_key_size; ret = wc_ed448_export_public(&ed_key, public_key, &pub_len32); @@ -540,13 +652,13 @@ psa_status_t psa_asymmetric_generate_key_ed448(psa_key_type_t key_type, wc_ed448_free(&ed_key); return wc_error_to_psa_status(ret); } - + wc_FreeRng(&rng); wc_ed448_free(&ed_key); *private_key_length = (size_t)priv_len32; *public_key_length = (size_t)pub_len32; - + return PSA_SUCCESS; } @@ -562,10 +674,10 @@ psa_status_t psa_asymmetric_export_public_key_ed448(psa_key_type_t key_type, int ret; ed448_key ed_key; word32 out_len32; - + /* Check if key type is ED448 */ - if ((key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) && - key_type != PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_TWISTED_EDWARDS)) || + if ((key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS) && + key_type != PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_TWISTED_EDWARDS)) || key_bits != 448) { return PSA_ERROR_INVALID_ARGUMENT; } @@ -573,13 +685,13 @@ psa_status_t psa_asymmetric_export_public_key_ed448(psa_key_type_t key_type, (wolfpsa_check_word32_length(output_size) != PSA_SUCCESS)) { return PSA_ERROR_INVALID_ARGUMENT; } - + /* Initialize ED448 key */ ret = wc_ed448_init(&ed_key); if (ret != 0) { return wc_error_to_psa_status(ret); } - + /* Import key */ if (key_type == PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS)) { ret = wc_ed448_import_private_only(key_buffer, (word32)key_buffer_size, @@ -591,24 +703,24 @@ psa_status_t psa_asymmetric_export_public_key_ed448(psa_key_type_t key_type, else { ret = wc_ed448_import_public(key_buffer, (word32)key_buffer_size, &ed_key); } - + if (ret != 0) { wc_ed448_free(&ed_key); return wc_error_to_psa_status(ret); } - + /* Export public key */ out_len32 = (word32)output_size; ret = wc_ed448_export_public(&ed_key, output, &out_len32); - + wc_ed448_free(&ed_key); - + if (ret != 0) { return wc_error_to_psa_status(ret); } *output_length = (size_t)out_len32; - + return PSA_SUCCESS; } #endif /* HAVE_ED448 */ diff --git a/src/psa_key_storage.c b/src/psa_key_storage.c index c60ed92..58edfa5 100644 --- a/src/psa_key_storage.c +++ b/src/psa_key_storage.c @@ -42,6 +42,8 @@ #include #include #include +#include +#include "psa_pqc_internal.h" #ifdef WOLFPSA_DEBUG_IMPORT #include @@ -259,7 +261,9 @@ static int wolfpsa_usage_flags_valid(psa_key_usage_t usage) PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH | PSA_KEY_USAGE_DERIVE | - PSA_KEY_USAGE_VERIFY_DERIVATION; + PSA_KEY_USAGE_VERIFY_DERIVATION | + PSA_KEY_USAGE_WRAP | + PSA_KEY_USAGE_UNWRAP; return (usage & ~mask) == 0; } @@ -418,6 +422,7 @@ static psa_status_t wolfpsa_infer_key_bits(psa_key_attributes_t* attr, attr->type == PSA_KEY_TYPE_HMAC || attr->type == PSA_KEY_TYPE_RAW_DATA || attr->type == PSA_KEY_TYPE_CHACHA20 || + attr->type == PSA_KEY_TYPE_XCHACHA20 || attr->type == PSA_KEY_TYPE_DERIVE || attr->type == PSA_KEY_TYPE_PASSWORD || attr->type == PSA_KEY_TYPE_PASSWORD_HASH || @@ -495,6 +500,61 @@ static psa_status_t wolfpsa_infer_key_bits(psa_key_attributes_t* attr, return PSA_SUCCESS; } + /* ML-DSA: key pairs store a 32-byte seed — ambiguous across parameter sets, + * caller must set bits explicitly. Public keys have unambiguous sizes. */ + if (PSA_KEY_TYPE_IS_ML_DSA(attr->type)) { + if (attr->type == PSA_KEY_TYPE_ML_DSA_PUBLIC_KEY) { + switch (data_length) { + case 1312: attr->bits = 128; return PSA_SUCCESS; + case 1952: attr->bits = 192; return PSA_SUCCESS; + case 2592: attr->bits = 256; return PSA_SUCCESS; + default: return PSA_ERROR_INVALID_ARGUMENT; + } + } + /* Key pair: 32-byte seed is ambiguous — bits must be set by caller. */ + return PSA_ERROR_INVALID_ARGUMENT; + } + + /* ML-KEM: key pairs store a 64-byte seed — ambiguous. Public key sizes + * are unambiguous. */ + if (PSA_KEY_TYPE_IS_ML_KEM(attr->type)) { + if (attr->type == PSA_KEY_TYPE_ML_KEM_PUBLIC_KEY) { + switch (data_length) { + case 800: attr->bits = 512; return PSA_SUCCESS; + case 1184: attr->bits = 768; return PSA_SUCCESS; + case 1568: attr->bits = 1024; return PSA_SUCCESS; + default: return PSA_ERROR_INVALID_ARGUMENT; + } + } + /* Key pair: 64-byte seed is ambiguous — bits must be set by caller. */ + return PSA_ERROR_INVALID_ARGUMENT; + } + + /* LMS / HSS / XMSS / XMSS^MT — public-key-only types, infer bits from + * blob length (hash-output length: 192-bit or 256-bit parameter sets). */ + if (attr->type == PSA_KEY_TYPE_LMS_PUBLIC_KEY) { + switch (data_length) { + case 48: attr->bits = 192; return PSA_SUCCESS; + case 56: attr->bits = 256; return PSA_SUCCESS; + default: return PSA_ERROR_INVALID_ARGUMENT; + } + } + if (attr->type == PSA_KEY_TYPE_HSS_PUBLIC_KEY) { + switch (data_length) { + case 52: attr->bits = 192; return PSA_SUCCESS; + case 60: attr->bits = 256; return PSA_SUCCESS; + default: return PSA_ERROR_INVALID_ARGUMENT; + } + } + if (attr->type == PSA_KEY_TYPE_XMSS_PUBLIC_KEY || + attr->type == PSA_KEY_TYPE_XMSS_MT_PUBLIC_KEY) { + switch (data_length) { + case 52: attr->bits = 192; return PSA_SUCCESS; + case 68: attr->bits = 256; return PSA_SUCCESS; + default: return PSA_ERROR_INVALID_ARGUMENT; + } + } + return PSA_ERROR_INVALID_ARGUMENT; } @@ -911,13 +971,16 @@ psa_status_t psa_import_key( return status; } } - if (attr.type == PSA_KEY_TYPE_CHACHA20) { + if (attr.type == PSA_KEY_TYPE_CHACHA20 || + attr.type == PSA_KEY_TYPE_XCHACHA20) { if (attr.bits != 256) { - wolfpsa_debug_import_reason("invalid ChaCha20 key bits", &attr, data_length); + wolfpsa_debug_import_reason("invalid ChaCha20/XChaCha20 key bits", &attr, + data_length); return PSA_ERROR_INVALID_ARGUMENT; } - if (attr.bits != (psa_key_bits_t)(data_length * 8U)) { - wolfpsa_debug_import_reason("ChaCha20 bits/length mismatch", &attr, data_length); + if (data_length != 32) { + wolfpsa_debug_import_reason("ChaCha20/XChaCha20 key length must be 32", &attr, + data_length); return PSA_ERROR_INVALID_ARGUMENT; } } @@ -952,7 +1015,120 @@ psa_status_t psa_import_key( return PSA_ERROR_INVALID_ARGUMENT; } } - + else if (PSA_KEY_TYPE_IS_ML_DSA(attr.type)) { + if (attr.type == PSA_KEY_TYPE_ML_DSA_KEY_PAIR) { + if (attr.bits != 128 && attr.bits != 192 && attr.bits != 256) { + wolfpsa_debug_import_reason("invalid ML-DSA key pair bits", &attr, + data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + if (data_length != 32) { + wolfpsa_debug_import_reason("ML-DSA key pair must be 32-byte seed", + &attr, data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + } + else { + /* PSA_KEY_TYPE_ML_DSA_PUBLIC_KEY */ + size_t expected; + switch (attr.bits) { + case 128: expected = 1312; break; + case 192: expected = 1952; break; + case 256: expected = 2592; break; + default: + wolfpsa_debug_import_reason("invalid ML-DSA public key bits", &attr, + data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + if (data_length != expected) { + wolfpsa_debug_import_reason("ML-DSA public key length mismatch", &attr, + data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + } + } + else if (PSA_KEY_TYPE_IS_ML_KEM(attr.type)) { + if (attr.type == PSA_KEY_TYPE_ML_KEM_KEY_PAIR) { + if (attr.bits != 512 && attr.bits != 768 && attr.bits != 1024) { + wolfpsa_debug_import_reason("invalid ML-KEM key pair bits", &attr, + data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + if (data_length != 64) { + wolfpsa_debug_import_reason("ML-KEM key pair must be 64-byte seed", + &attr, data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + } + else { + /* PSA_KEY_TYPE_ML_KEM_PUBLIC_KEY */ + size_t expected; + switch (attr.bits) { + case 512: expected = 800; break; + case 768: expected = 1184; break; + case 1024: expected = 1568; break; + default: + wolfpsa_debug_import_reason("invalid ML-KEM public key bits", &attr, + data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + if (data_length != expected) { + wolfpsa_debug_import_reason("ML-KEM public key length mismatch", &attr, + data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + } + } + else if (attr.type == PSA_KEY_TYPE_LMS_PUBLIC_KEY) { + size_t expected; + switch (attr.bits) { + case 192: expected = 48; break; + case 256: expected = 56; break; + default: + wolfpsa_debug_import_reason("invalid LMS public key bits", &attr, + data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + if (data_length != expected) { + wolfpsa_debug_import_reason("LMS public key length mismatch", &attr, + data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + } + else if (attr.type == PSA_KEY_TYPE_HSS_PUBLIC_KEY) { + size_t expected; + switch (attr.bits) { + case 192: expected = 52; break; + case 256: expected = 60; break; + default: + wolfpsa_debug_import_reason("invalid HSS public key bits", &attr, + data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + if (data_length != expected) { + wolfpsa_debug_import_reason("HSS public key length mismatch", &attr, + data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + } + else if (attr.type == PSA_KEY_TYPE_XMSS_PUBLIC_KEY || + attr.type == PSA_KEY_TYPE_XMSS_MT_PUBLIC_KEY) { + size_t expected; + switch (attr.bits) { + case 192: expected = 52; break; + case 256: expected = 68; break; + default: + wolfpsa_debug_import_reason("invalid XMSS/XMSS^MT public key bits", + &attr, data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + if (data_length != expected) { + wolfpsa_debug_import_reason("XMSS/XMSS^MT public key length mismatch", + &attr, data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + } + /* Check if the key storage is initialized */ status = psa_key_storage_check_init(); if (status != PSA_SUCCESS) { @@ -1089,7 +1265,8 @@ psa_status_t psa_generate_key( key_type == PSA_KEY_TYPE_HMAC || key_type == PSA_KEY_TYPE_AES || key_type == PSA_KEY_TYPE_DES || - key_type == PSA_KEY_TYPE_CHACHA20) { + key_type == PSA_KEY_TYPE_CHACHA20 || + key_type == PSA_KEY_TYPE_XCHACHA20) { key_data_length = PSA_BITS_TO_BYTES(key_bits); if (key_data_length == 0) { return PSA_ERROR_INVALID_ARGUMENT; @@ -1267,6 +1444,46 @@ psa_status_t psa_generate_key( #endif } +#if defined(WOLFSSL_HAVE_MLDSA) + if (key_type == PSA_KEY_TYPE_ML_DSA_KEY_PAIR) { + uint8_t seed[WOLFPSA_MLDSA_SEED_SIZE]; + + if (key_bits != 128 && key_bits != 192 && key_bits != 256) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + status = wolfpsa_mldsa_generate_seed((size_t)key_bits, seed); + if (status != PSA_SUCCESS) { + wc_ForceZero(seed, sizeof(seed)); + return status; + } + + status = psa_import_key(attributes, seed, sizeof(seed), key_id); + wc_ForceZero(seed, sizeof(seed)); + return status; + } +#endif /* WOLFSSL_HAVE_MLDSA */ + +#if defined(WOLFSSL_HAVE_MLKEM) + if (key_type == PSA_KEY_TYPE_ML_KEM_KEY_PAIR) { + uint8_t seed[WOLFPSA_MLKEM_SEED_SIZE]; + + if (key_bits != 512 && key_bits != 768 && key_bits != 1024) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + status = wolfpsa_mlkem_generate_seed((size_t)key_bits, seed); + if (status != PSA_SUCCESS) { + wc_ForceZero(seed, sizeof(seed)); + return status; + } + + status = psa_import_key(attributes, seed, sizeof(seed), key_id); + wc_ForceZero(seed, sizeof(seed)); + return status; + } +#endif /* WOLFSSL_HAVE_MLKEM */ + return PSA_ERROR_NOT_SUPPORTED; } @@ -1463,7 +1680,13 @@ psa_status_t psa_export_public_key( } if (!PSA_KEY_TYPE_IS_RSA(attributes.type) && - !PSA_KEY_TYPE_IS_ECC(attributes.type)) { + !PSA_KEY_TYPE_IS_ECC(attributes.type) && + !PSA_KEY_TYPE_IS_ML_DSA(attributes.type) && + !PSA_KEY_TYPE_IS_ML_KEM(attributes.type) && + attributes.type != PSA_KEY_TYPE_LMS_PUBLIC_KEY && + attributes.type != PSA_KEY_TYPE_HSS_PUBLIC_KEY && + attributes.type != PSA_KEY_TYPE_XMSS_PUBLIC_KEY && + attributes.type != PSA_KEY_TYPE_XMSS_MT_PUBLIC_KEY) { if (use_volatile) { wolfpsa_forcezero_free_key_data(key_data, key_data_length); } @@ -1672,6 +1895,66 @@ psa_status_t psa_export_public_key( #else status = PSA_ERROR_NOT_SUPPORTED; #endif + /* PQC key types — reached when neither RSA nor ECC matched the type + * gate above. */ +#if defined(WOLFSSL_HAVE_MLDSA) + if (PSA_KEY_TYPE_IS_ML_DSA(attributes.type)) { + if (attributes.type == PSA_KEY_TYPE_ML_DSA_PUBLIC_KEY) { + /* Public key already stored as raw bytes — copy directly. */ + if (data_size < key_data_length) { + status = PSA_ERROR_BUFFER_TOO_SMALL; + } + else { + XMEMCPY(data, key_data, key_data_length); + *data_length = key_data_length; + status = PSA_SUCCESS; + } + } + else { + /* Key pair: stored as 32-byte seed — derive public key. */ + status = wolfpsa_mldsa_export_public((size_t)attributes.bits, + key_data, data, data_size, + data_length); + } + } + else +#endif /* WOLFSSL_HAVE_MLDSA */ +#if defined(WOLFSSL_HAVE_MLKEM) + if (PSA_KEY_TYPE_IS_ML_KEM(attributes.type)) { + if (attributes.type == PSA_KEY_TYPE_ML_KEM_PUBLIC_KEY) { + /* Public key already stored as raw bytes — copy directly. */ + if (data_size < key_data_length) { + status = PSA_ERROR_BUFFER_TOO_SMALL; + } + else { + XMEMCPY(data, key_data, key_data_length); + *data_length = key_data_length; + status = PSA_SUCCESS; + } + } + else { + /* Key pair: stored as 64-byte seed — derive public key. */ + status = wolfpsa_mlkem_export_public((size_t)attributes.bits, + key_data, data, data_size, + data_length); + } + } + else +#endif /* WOLFSSL_HAVE_MLKEM */ + if (attributes.type == PSA_KEY_TYPE_LMS_PUBLIC_KEY || + attributes.type == PSA_KEY_TYPE_HSS_PUBLIC_KEY || + attributes.type == PSA_KEY_TYPE_XMSS_PUBLIC_KEY || + attributes.type == PSA_KEY_TYPE_XMSS_MT_PUBLIC_KEY) { + /* Public-key-only types: stored bytes are the raw public key. */ + if (data_size < key_data_length) { + status = PSA_ERROR_BUFFER_TOO_SMALL; + } + else { + XMEMCPY(data, key_data, key_data_length); + *data_length = key_data_length; + status = PSA_SUCCESS; + } + } } wolfpsa_forcezero_free_key_data(key_data, key_data_length); @@ -1892,4 +2175,70 @@ psa_status_t psa_copy_key( return status; } +/* Check whether a key supports a given algorithm and usage combination. + * + * PSA Crypto API 1.4 semantics: + * - Returns PSA_SUCCESS iff the key exists and its policy permits using + * algorithm 'alg' with ALL of the requested usage flags. + * - alg == PSA_ALG_NONE: skip algorithm check (only usage is validated). + * - Wildcard-hash policy (PSA_ALG_ANY_HASH in key policy) authorises any + * concrete hash-and-sign algorithm of the same base family. + * - No key material is loaded. + */ +psa_status_t psa_check_key_usage(psa_key_id_t key, + psa_algorithm_t alg, + psa_key_usage_t usage) +{ + wolfpsa_trace("psa_check_key_usage(key=%u alg=0x%08x usage=0x%08x)", + (unsigned)key, (unsigned)alg, (unsigned)usage); + psa_status_t status; + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_key_usage_t key_usage; + psa_algorithm_t key_alg; + + status = psa_get_key_attributes(key, &attributes); + if (status != PSA_SUCCESS) { + return status; + } + + key_usage = psa_get_key_usage_flags(&attributes); + if ((key_usage & usage) != usage) { + psa_reset_key_attributes(&attributes); + return PSA_ERROR_NOT_PERMITTED; + } + + if (alg != PSA_ALG_NONE) { + key_alg = psa_get_key_algorithm(&attributes); + + /* A key with no permitted algorithm cannot be used for any + * cryptographic operation. */ + if (key_alg == PSA_ALG_NONE) { + psa_reset_key_attributes(&attributes); + return PSA_ERROR_NOT_PERMITTED; + } + + /* Exact match is always accepted. Otherwise apply the ANY_HASH + * wildcard rule: a sign-hash policy using PSA_ALG_ANY_HASH authorises + * any concrete hash-and-sign algorithm of the same base family. */ + if (key_alg != alg) { + int alg_ok = 0; + + if (PSA_ALG_IS_SIGN_HASH(alg) && + PSA_ALG_SIGN_GET_HASH(key_alg) == PSA_ALG_ANY_HASH && + PSA_ALG_SIGN_GET_HASH(alg) != PSA_ALG_ANY_HASH && + ((key_alg & ~PSA_ALG_HASH_MASK) == (alg & ~PSA_ALG_HASH_MASK))) { + alg_ok = 1; + } + + if (!alg_ok) { + psa_reset_key_attributes(&attributes); + return PSA_ERROR_NOT_PERMITTED; + } + } + } + + psa_reset_key_attributes(&attributes); + return PSA_SUCCESS; +} + #endif /* WOLFSSL_PSA_ENGINE */ diff --git a/src/psa_pq.c b/src/psa_pq.c index e78df4d..82b44d1 100644 --- a/src/psa_pq.c +++ b/src/psa_pq.c @@ -39,6 +39,13 @@ /* Check if a key type is a post-quantum key type */ psa_status_t psa_pq_check_key_type_supported(psa_key_type_t type) { + /* SLH-DSA is a family of values (key pair 0x7180..0x71ff, public + * 0x4180..0x41ff) and cannot appear as case labels. wolfCrypt has no + * SLH-DSA backend, so reject the whole family here before the switch. */ + if (PSA_KEY_TYPE_IS_SLH_DSA(type)) { + return PSA_ERROR_NOT_SUPPORTED; + } + switch (type) { #if defined(WOLFSSL_HAVE_MLKEM) case PSA_KEY_TYPE_ML_KEM_KEY_PAIR: @@ -50,20 +57,16 @@ psa_status_t psa_pq_check_key_type_supported(psa_key_type_t type) case PSA_KEY_TYPE_ML_DSA_PUBLIC_KEY: return PSA_SUCCESS; #endif -#if defined(WOLFSSL_HAVE_LMS) && defined(PSA_KEY_TYPE_LMS_KEY_PAIR) - case PSA_KEY_TYPE_LMS_KEY_PAIR: - return PSA_SUCCESS; -#endif -#if defined(WOLFSSL_HAVE_LMS) && defined(PSA_KEY_TYPE_LMS_PUBLIC_KEY) + /* LMS and HSS are public-key-only types in PSA 1.4 */ +#if defined(WOLFSSL_HAVE_LMS) case PSA_KEY_TYPE_LMS_PUBLIC_KEY: + case PSA_KEY_TYPE_HSS_PUBLIC_KEY: return PSA_SUCCESS; #endif -#if defined(WOLFSSL_HAVE_XMSS) && defined(PSA_KEY_TYPE_XMSS_KEY_PAIR) - case PSA_KEY_TYPE_XMSS_KEY_PAIR: - return PSA_SUCCESS; -#endif -#if defined(WOLFSSL_HAVE_XMSS) && defined(PSA_KEY_TYPE_XMSS_PUBLIC_KEY) + /* XMSS and XMSS^MT are public-key-only types in PSA 1.4 */ +#if defined(WOLFSSL_HAVE_XMSS) case PSA_KEY_TYPE_XMSS_PUBLIC_KEY: + case PSA_KEY_TYPE_XMSS_MT_PUBLIC_KEY: return PSA_SUCCESS; #endif default: @@ -74,14 +77,16 @@ psa_status_t psa_pq_check_key_type_supported(psa_key_type_t type) /* Check if a key size is valid for the given post-quantum key type */ psa_status_t psa_pq_check_key_size_valid(psa_key_type_t type, size_t bits) { - (void)type; - (void)bits; + /* SLH-DSA is unsupported; reject before the switch (family of values). */ + if (PSA_KEY_TYPE_IS_SLH_DSA(type)) { + return PSA_ERROR_NOT_SUPPORTED; + } switch (type) { #if defined(WOLFSSL_HAVE_MLKEM) case PSA_KEY_TYPE_ML_KEM_KEY_PAIR: case PSA_KEY_TYPE_ML_KEM_PUBLIC_KEY: - /* ML-KEM key sizes: 512, 768, 1024 */ + /* ML-KEM parameter sets: 512, 768, 1024 */ if (bits == 512 || bits == 768 || bits == 1024) { return PSA_SUCCESS; } @@ -90,31 +95,34 @@ psa_status_t psa_pq_check_key_size_valid(psa_key_type_t type, size_t bits) #if defined(WOLFSSL_HAVE_MLDSA) case PSA_KEY_TYPE_ML_DSA_KEY_PAIR: case PSA_KEY_TYPE_ML_DSA_PUBLIC_KEY: - /* ML-DSA key sizes: 2, 3, 5 (security levels) */ - if (bits == 2 || bits == 3 || bits == 5) { + /* ML-DSA security strengths in bits: 128 (ML-DSA-44), + * 192 (ML-DSA-65), 256 (ML-DSA-87) */ + if (bits == 128 || bits == 192 || bits == 256) { return PSA_SUCCESS; } return PSA_ERROR_INVALID_ARGUMENT; #endif -#if defined(WOLFSSL_HAVE_LMS) && defined(PSA_KEY_TYPE_LMS_KEY_PAIR) - case PSA_KEY_TYPE_LMS_KEY_PAIR: - /* LMS doesn't have specific key sizes in the same way */ - return PSA_SUCCESS; -#endif -#if defined(WOLFSSL_HAVE_LMS) && defined(PSA_KEY_TYPE_LMS_PUBLIC_KEY) + /* LMS and HSS are public-key-only types in PSA 1.4. + * bits = output length in bits of the parameter set's hash function: + * 192 (SHA-256/192 or SHAKE-256/192) or 256 (SHA-256 or SHAKE-256). */ +#if defined(WOLFSSL_HAVE_LMS) case PSA_KEY_TYPE_LMS_PUBLIC_KEY: - /* LMS doesn't have specific key sizes in the same way */ - return PSA_SUCCESS; -#endif -#if defined(WOLFSSL_HAVE_XMSS) && defined(PSA_KEY_TYPE_XMSS_KEY_PAIR) - case PSA_KEY_TYPE_XMSS_KEY_PAIR: - /* XMSS doesn't have specific key sizes in the same way */ - return PSA_SUCCESS; + case PSA_KEY_TYPE_HSS_PUBLIC_KEY: + if (bits == 192 || bits == 256) { + return PSA_SUCCESS; + } + return PSA_ERROR_INVALID_ARGUMENT; #endif -#if defined(WOLFSSL_HAVE_XMSS) && defined(PSA_KEY_TYPE_XMSS_PUBLIC_KEY) + /* XMSS and XMSS^MT are public-key-only types in PSA 1.4. + * bits = output length in bits of the parameter set's hash function: + * 192 (SHA-256/192 or SHAKE-256/192) or 256 (SHA-256 or SHAKE-256). */ +#if defined(WOLFSSL_HAVE_XMSS) case PSA_KEY_TYPE_XMSS_PUBLIC_KEY: - /* XMSS doesn't have specific key sizes in the same way */ - return PSA_SUCCESS; + case PSA_KEY_TYPE_XMSS_MT_PUBLIC_KEY: + if (bits == 192 || bits == 256) { + return PSA_SUCCESS; + } + return PSA_ERROR_INVALID_ARGUMENT; #endif default: return PSA_ERROR_NOT_SUPPORTED; diff --git a/wolfpsa.map b/wolfpsa.map index 8609099..e389b90 100644 --- a/wolfpsa.map +++ b/wolfpsa.map @@ -1,6 +1,47 @@ WOLFPSA_1.0 { global: psa_aead_abort; + psa_attach_key; + psa_check_key_usage; + psa_decapsulate; + psa_encapsulate; + psa_export_public_key_iop_abort; + psa_export_public_key_iop_complete; + psa_export_public_key_iop_get_num_ops; + psa_export_public_key_iop_setup; + psa_generate_key_custom; + psa_generate_key_iop_abort; + psa_generate_key_iop_complete; + psa_generate_key_iop_get_num_ops; + psa_generate_key_iop_setup; + psa_hash_resume; + psa_hash_suspend; + psa_interruptible_get_max_ops; + psa_interruptible_set_max_ops; + psa_key_agreement_iop_abort; + psa_key_agreement_iop_complete; + psa_key_agreement_iop_get_num_ops; + psa_key_agreement_iop_setup; + psa_key_derivation_output_key_custom; + psa_sign_hash_abort; + psa_sign_hash_complete; + psa_sign_hash_get_num_ops; + psa_sign_hash_start; + psa_sign_hash_with_context; + psa_sign_message_with_context; + psa_unwrap_key; + psa_verify_hash_abort; + psa_verify_hash_complete; + psa_verify_hash_get_num_ops; + psa_verify_hash_start; + psa_verify_hash_with_context; + psa_verify_message_with_context; + psa_wrap_key; + psa_xof_abort; + psa_xof_output; + psa_xof_set_context; + psa_xof_setup; + psa_xof_update; psa_aead_decrypt; psa_aead_decrypt_setup; psa_aead_encrypt; @@ -65,9 +106,6 @@ WOLFPSA_1.0 { psa_mac_verify; psa_mac_verify_finish; psa_mac_verify_setup; - psa_ml_dsa_generate_key; - psa_ml_dsa_sign; - psa_ml_dsa_verify; psa_pake_abort; psa_pake_cs_get_algorithm; psa_pake_cs_get_key_confirmation; From aa470dd48763a12e6cd4061430ae0f5cdca6a5df Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 14:37:02 +0200 Subject: [PATCH 19/24] crypto: Ascon, XChaCha20-Poly1305 and SP800-108 counter KDFs Hash engine: Ascon-Hash256 (one-shot and multipart, POD clone). AEAD: one-shot XChaCha20-Poly1305 (24-byte nonce) and Ascon-AEAD128 (16-byte key/nonce/tag); multipart setup reports NOT_SUPPORTED for both. Key derivation: SP800-108r1 counter-mode KDF in HMAC and AES-CMAC variants per the PSA 1.4 construction (4-byte counter, label, zero separator, context, output length in bits; the CMAC variant mixes in K(0)), with L snapshotted from the operation capacity at first output. Headers: PSA_HASH_LENGTH arm for Ascon-Hash256, AEAD nonce lengths for XChaCha20/Ascon (nonce max now 24), default-length-tag cases for the new AEAD algorithms and PSA_KEY_DERIVATION_INPUT_CONTEXT. --- src/psa_aead.c | 431 ++++++++++++++++++++++++++++++++++++ src/psa_hash_engine.c | 43 ++++ src/psa_key_derivation.c | 363 +++++++++++++++++++++++++++++- wolfpsa/psa/crypto_sizes.h | 7 +- wolfpsa/psa/crypto_values.h | 3 + 5 files changed, 845 insertions(+), 2 deletions(-) diff --git a/src/psa_aead.c b/src/psa_aead.c index a11d4d8..9ab0bba 100644 --- a/src/psa_aead.c +++ b/src/psa_aead.c @@ -33,6 +33,12 @@ #include #include #include +#ifdef HAVE_XCHACHA +#include +#endif +#ifdef HAVE_ASCON +#include +#endif #include "psa_aead_internal.h" #include "psa_size.h" @@ -140,6 +146,26 @@ static psa_status_t wolfpsa_aead_check_key(psa_key_id_t key, return PSA_ERROR_INVALID_ARGUMENT; } } +#ifdef HAVE_XCHACHA + else if (PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_XCHACHA20_POLY1305)) { + if (attributes->type != PSA_KEY_TYPE_XCHACHA20) { + wolfpsa_forcezero_free_key_data(*key_data, *key_data_length); + *key_data = NULL; + *key_data_length = 0; + return PSA_ERROR_INVALID_ARGUMENT; + } + } +#endif /* HAVE_XCHACHA */ +#ifdef HAVE_ASCON + else if (PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_ASCON_AEAD128)) { + if (attributes->type != PSA_KEY_TYPE_ASCON) { + wolfpsa_forcezero_free_key_data(*key_data, *key_data_length); + *key_data = NULL; + *key_data_length = 0; + return PSA_ERROR_INVALID_ARGUMENT; + } + } +#endif /* HAVE_ASCON */ else { wolfpsa_forcezero_free_key_data(*key_data, *key_data_length); *key_data = NULL; @@ -240,6 +266,12 @@ static psa_status_t wolfpsa_aead_setup(psa_aead_operation_t *operation, return PSA_ERROR_NOT_SUPPORTED; } #endif + /* XChaCha20-Poly1305 and Ascon-AEAD128 are one-shot only; wolfCrypt + * provides no streaming API for these algorithms. */ + if (PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_XCHACHA20_POLY1305) || + PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_ASCON_AEAD128)) { + return PSA_ERROR_NOT_SUPPORTED; + } status = wolfpsa_aead_check_key(key, usage, alg, &attributes, &key_data, &key_data_length); @@ -840,6 +872,373 @@ psa_status_t psa_aead_verify(psa_aead_operation_t *operation, return status; } +#ifdef HAVE_XCHACHA +/* One-shot encrypt for XChaCha20-Poly1305. + * ciphertext = plaintext || tag (16-byte Poly1305 tag appended). */ +static psa_status_t wolfpsa_xchacha_oneshot_encrypt( + psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *nonce, size_t nonce_length, + const uint8_t *additional_data, size_t additional_data_length, + const uint8_t *plaintext, size_t plaintext_length, + uint8_t *ciphertext, size_t ciphertext_size, size_t *ciphertext_length) +{ + psa_key_attributes_t attributes; + uint8_t *key_data = NULL; + size_t key_data_length = 0; + psa_status_t status; + psa_key_usage_t key_usage; + psa_algorithm_t key_alg; + int ret; + /* Encrypt produces plaintext_length bytes of ciphertext plus 16-byte tag. */ + size_t out_len; + + if (nonce_length != XCHACHA20_POLY1305_AEAD_NONCE_SIZE) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (ciphertext_length == NULL) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + /* Check for overflow before output-size check. */ + if (plaintext_length > SIZE_MAX - CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE) { + return PSA_ERROR_INVALID_ARGUMENT; + } + out_len = plaintext_length + CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE; + if (ciphertext_size < out_len) { + return PSA_ERROR_BUFFER_TOO_SMALL; + } + + status = wolfpsa_get_key_data(key, &attributes, &key_data, &key_data_length); + if (status != PSA_SUCCESS) { + return status; + } + + if (attributes.type != PSA_KEY_TYPE_XCHACHA20 || + key_data_length != CHACHA20_POLY1305_AEAD_KEYSIZE) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + + key_usage = psa_get_key_usage_flags(&attributes); + if ((key_usage & PSA_KEY_USAGE_ENCRYPT) == 0) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + + key_alg = psa_get_key_algorithm(&attributes); + if (!PSA_ALG_AEAD_EQUAL(key_alg, PSA_ALG_XCHACHA20_POLY1305)) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + if (!PSA_ALG_AEAD_EQUAL(key_alg, alg)) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + + ret = wc_XChaCha20Poly1305_Encrypt( + ciphertext, ciphertext_size, + plaintext, plaintext_length, + additional_data, additional_data_length, + nonce, nonce_length, + key_data, key_data_length); + + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + + if (ret != 0) { + return wc_error_to_psa_status(ret); + } + + *ciphertext_length = out_len; + return PSA_SUCCESS; +} + +/* One-shot decrypt for XChaCha20-Poly1305. + * ciphertext = ciphertext_body || tag (16-byte Poly1305 tag appended). */ +static psa_status_t wolfpsa_xchacha_oneshot_decrypt( + psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *nonce, size_t nonce_length, + const uint8_t *additional_data, size_t additional_data_length, + const uint8_t *ciphertext, size_t ciphertext_length, + uint8_t *plaintext, size_t plaintext_size, size_t *plaintext_length) +{ + psa_key_attributes_t attributes; + uint8_t *key_data = NULL; + size_t key_data_length = 0; + psa_status_t status; + psa_key_usage_t key_usage; + psa_algorithm_t key_alg; + int ret; + size_t pt_len; + + if (nonce_length != XCHACHA20_POLY1305_AEAD_NONCE_SIZE) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (plaintext_length == NULL) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (ciphertext_length < CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE) { + return PSA_ERROR_INVALID_ARGUMENT; + } + pt_len = ciphertext_length - CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE; + if (plaintext_size < pt_len) { + return PSA_ERROR_BUFFER_TOO_SMALL; + } + + status = wolfpsa_get_key_data(key, &attributes, &key_data, &key_data_length); + if (status != PSA_SUCCESS) { + return status; + } + + if (attributes.type != PSA_KEY_TYPE_XCHACHA20 || + key_data_length != CHACHA20_POLY1305_AEAD_KEYSIZE) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + + key_usage = psa_get_key_usage_flags(&attributes); + if ((key_usage & PSA_KEY_USAGE_DECRYPT) == 0) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + + key_alg = psa_get_key_algorithm(&attributes); + if (!PSA_ALG_AEAD_EQUAL(key_alg, PSA_ALG_XCHACHA20_POLY1305)) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + if (!PSA_ALG_AEAD_EQUAL(key_alg, alg)) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + + /* wc_XChaCha20Poly1305_Decrypt expects src = ciphertext || tag and + * dst_space must accommodate pt_len output bytes. */ + ret = wc_XChaCha20Poly1305_Decrypt( + plaintext, plaintext_size, + ciphertext, ciphertext_length, + additional_data, additional_data_length, + nonce, nonce_length, + key_data, key_data_length); + + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + + if (ret == MAC_CMP_FAILED_E) { + return PSA_ERROR_INVALID_SIGNATURE; + } + if (ret != 0) { + return wc_error_to_psa_status(ret); + } + + *plaintext_length = pt_len; + return PSA_SUCCESS; +} +#endif /* HAVE_XCHACHA */ + +#ifdef HAVE_ASCON +/* One-shot encrypt for Ascon-AEAD128. + * ciphertext = encrypted_body || tag (16-byte tag appended). */ +static psa_status_t wolfpsa_ascon_oneshot_encrypt( + psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *nonce, size_t nonce_length, + const uint8_t *additional_data, size_t additional_data_length, + const uint8_t *plaintext, size_t plaintext_length, + uint8_t *ciphertext, size_t ciphertext_size, size_t *ciphertext_length) +{ + psa_key_attributes_t attributes; + uint8_t *key_data = NULL; + size_t key_data_length = 0; + psa_status_t status; + psa_key_usage_t key_usage; + psa_algorithm_t key_alg; + wc_AsconAEAD128 ascon; + int ret; + size_t out_len; + + if (nonce_length != ASCON_AEAD128_NONCE_SZ) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (ciphertext_length == NULL) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + if (plaintext_length > SIZE_MAX - ASCON_AEAD128_TAG_SZ) { + return PSA_ERROR_INVALID_ARGUMENT; + } + out_len = plaintext_length + ASCON_AEAD128_TAG_SZ; + if (ciphertext_size < out_len) { + return PSA_ERROR_BUFFER_TOO_SMALL; + } + + if ((wolfpsa_check_word32_length(plaintext_length) != PSA_SUCCESS) || + (wolfpsa_check_word32_length(additional_data_length) != PSA_SUCCESS)) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + status = wolfpsa_get_key_data(key, &attributes, &key_data, &key_data_length); + if (status != PSA_SUCCESS) { + return status; + } + + if (attributes.type != PSA_KEY_TYPE_ASCON || + key_data_length != ASCON_AEAD128_KEY_SZ) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + + key_usage = psa_get_key_usage_flags(&attributes); + if ((key_usage & PSA_KEY_USAGE_ENCRYPT) == 0) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + + key_alg = psa_get_key_algorithm(&attributes); + if (!PSA_ALG_AEAD_EQUAL(key_alg, PSA_ALG_ASCON_AEAD128)) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + if (!PSA_ALG_AEAD_EQUAL(key_alg, alg)) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + + ret = wc_AsconAEAD128_Init(&ascon); + if (ret == 0) { + ret = wc_AsconAEAD128_SetKey(&ascon, key_data); + } + if (ret == 0) { + ret = wc_AsconAEAD128_SetNonce(&ascon, nonce); + } + if (ret == 0) { + ret = wc_AsconAEAD128_SetAD(&ascon, additional_data, + (word32)additional_data_length); + } + if (ret == 0) { + ret = wc_AsconAEAD128_EncryptUpdate(&ascon, ciphertext, + plaintext, (word32)plaintext_length); + } + if (ret == 0) { + /* Tag is written to ciphertext + plaintext_length. */ + ret = wc_AsconAEAD128_EncryptFinal(&ascon, + ciphertext + plaintext_length); + } + + wc_AsconAEAD128_Clear(&ascon); + wc_ForceZero(&ascon, sizeof(ascon)); + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + + if (ret != 0) { + return wc_error_to_psa_status(ret); + } + + *ciphertext_length = out_len; + return PSA_SUCCESS; +} + +/* One-shot decrypt for Ascon-AEAD128. + * ciphertext = encrypted_body || tag (16-byte tag appended). */ +static psa_status_t wolfpsa_ascon_oneshot_decrypt( + psa_key_id_t key, + psa_algorithm_t alg, + const uint8_t *nonce, size_t nonce_length, + const uint8_t *additional_data, size_t additional_data_length, + const uint8_t *ciphertext, size_t ciphertext_length, + uint8_t *plaintext, size_t plaintext_size, size_t *plaintext_length) +{ + psa_key_attributes_t attributes; + uint8_t *key_data = NULL; + size_t key_data_length = 0; + psa_status_t status; + psa_key_usage_t key_usage; + psa_algorithm_t key_alg; + wc_AsconAEAD128 ascon; + int ret; + size_t ct_len; /* ciphertext body length (without tag) */ + + if (nonce_length != ASCON_AEAD128_NONCE_SZ) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (plaintext_length == NULL) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (ciphertext_length < ASCON_AEAD128_TAG_SZ) { + return PSA_ERROR_INVALID_ARGUMENT; + } + ct_len = ciphertext_length - ASCON_AEAD128_TAG_SZ; + if (plaintext_size < ct_len) { + return PSA_ERROR_BUFFER_TOO_SMALL; + } + + if ((wolfpsa_check_word32_length(ct_len) != PSA_SUCCESS) || + (wolfpsa_check_word32_length(additional_data_length) != PSA_SUCCESS)) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + status = wolfpsa_get_key_data(key, &attributes, &key_data, &key_data_length); + if (status != PSA_SUCCESS) { + return status; + } + + if (attributes.type != PSA_KEY_TYPE_ASCON || + key_data_length != ASCON_AEAD128_KEY_SZ) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_INVALID_ARGUMENT; + } + + key_usage = psa_get_key_usage_flags(&attributes); + if ((key_usage & PSA_KEY_USAGE_DECRYPT) == 0) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + + key_alg = psa_get_key_algorithm(&attributes); + if (!PSA_ALG_AEAD_EQUAL(key_alg, PSA_ALG_ASCON_AEAD128)) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + if (!PSA_ALG_AEAD_EQUAL(key_alg, alg)) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + return PSA_ERROR_NOT_PERMITTED; + } + + ret = wc_AsconAEAD128_Init(&ascon); + if (ret == 0) { + ret = wc_AsconAEAD128_SetKey(&ascon, key_data); + } + if (ret == 0) { + ret = wc_AsconAEAD128_SetNonce(&ascon, nonce); + } + if (ret == 0) { + ret = wc_AsconAEAD128_SetAD(&ascon, additional_data, + (word32)additional_data_length); + } + if (ret == 0) { + ret = wc_AsconAEAD128_DecryptUpdate(&ascon, plaintext, + ciphertext, (word32)ct_len); + } + if (ret == 0) { + /* Tag is at ciphertext + ct_len. */ + ret = wc_AsconAEAD128_DecryptFinal(&ascon, ciphertext + ct_len); + } + + wc_AsconAEAD128_Clear(&ascon); + wc_ForceZero(&ascon, sizeof(ascon)); + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + + if (ret == MAC_CMP_FAILED_E) { + return PSA_ERROR_INVALID_SIGNATURE; + } + if (ret != 0) { + return wc_error_to_psa_status(ret); + } + + *plaintext_length = ct_len; + return PSA_SUCCESS; +} +#endif /* HAVE_ASCON */ + psa_status_t psa_aead_encrypt(psa_key_id_t key, psa_algorithm_t alg, const uint8_t *nonce, @@ -858,6 +1257,22 @@ psa_status_t psa_aead_encrypt(psa_key_id_t key, size_t tag_length = 0; size_t out_len = 0; + /* One-shot-only algorithms: bypass the multipart state machine. */ +#ifdef HAVE_XCHACHA + if (PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_XCHACHA20_POLY1305)) { + return wolfpsa_xchacha_oneshot_encrypt(key, alg, nonce, nonce_length, + additional_data, additional_data_length, plaintext, plaintext_length, + ciphertext, ciphertext_size, ciphertext_length); + } +#endif /* HAVE_XCHACHA */ +#ifdef HAVE_ASCON + if (PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_ASCON_AEAD128)) { + return wolfpsa_ascon_oneshot_encrypt(key, alg, nonce, nonce_length, + additional_data, additional_data_length, plaintext, plaintext_length, + ciphertext, ciphertext_size, ciphertext_length); + } +#endif /* HAVE_ASCON */ + status = psa_aead_encrypt_setup(&operation, key, alg); if (status != PSA_SUCCESS) { return status; @@ -932,6 +1347,22 @@ psa_status_t psa_aead_decrypt(psa_key_id_t key, size_t tag_length; size_t ct_len; + /* One-shot-only algorithms: bypass the multipart state machine. */ +#ifdef HAVE_XCHACHA + if (PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_XCHACHA20_POLY1305)) { + return wolfpsa_xchacha_oneshot_decrypt(key, alg, nonce, nonce_length, + additional_data, additional_data_length, ciphertext, ciphertext_length, + plaintext, plaintext_size, plaintext_length); + } +#endif /* HAVE_XCHACHA */ +#ifdef HAVE_ASCON + if (PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_ASCON_AEAD128)) { + return wolfpsa_ascon_oneshot_decrypt(key, alg, nonce, nonce_length, + additional_data, additional_data_length, ciphertext, ciphertext_length, + plaintext, plaintext_size, plaintext_length); + } +#endif /* HAVE_ASCON */ + status = psa_aead_decrypt_setup(&operation, key, alg); if (status != PSA_SUCCESS) { return status; diff --git a/src/psa_hash_engine.c b/src/psa_hash_engine.c index 0ab24e8..7ca8709 100644 --- a/src/psa_hash_engine.c +++ b/src/psa_hash_engine.c @@ -71,6 +71,10 @@ #include #endif +#ifdef HAVE_ASCON +#include +#endif + extern int wolfPSA_CryptoIsInitialized(void); typedef struct psa_hash_operation_ctx { @@ -104,6 +108,9 @@ typedef struct psa_hash_operation_ctx { #endif #ifdef WOLFSSL_RIPEMD RipeMd ripemd; /* RIPEMD-160 context */ +#endif +#ifdef HAVE_ASCON + wc_AsconHash256 ascon256; /* Ascon-Hash256 context */ #endif } ctx; } psa_hash_operation_ctx_t; @@ -202,6 +209,11 @@ static void psa_hash_cleanup_ctx(psa_hash_operation_ctx_t *ctx) case PSA_ALG_SHAKE256_512: wc_Shake256_Free((wc_Shake *)&ctx->ctx.sha3); break; +#endif +#ifdef HAVE_ASCON + case PSA_ALG_ASCON_HASH256: + wc_AsconHash256_Clear(&ctx->ctx.ascon256); + break; #endif default: break; @@ -259,6 +271,10 @@ psa_status_t psa_hash_check_alg_supported(psa_algorithm_t alg) #ifdef WOLFSSL_SHAKE256 case PSA_ALG_SHAKE256_512: return PSA_SUCCESS; + #endif + #ifdef HAVE_ASCON + case PSA_ALG_ASCON_HASH256: + return PSA_SUCCESS; #endif default: return PSA_ERROR_NOT_SUPPORTED; @@ -318,6 +334,10 @@ static size_t psa_hash_get_size(psa_algorithm_t alg) #ifdef WOLFSSL_SHAKE256 case PSA_ALG_SHAKE256_512: return 64u; + #endif + #ifdef HAVE_ASCON + case PSA_ALG_ASCON_HASH256: + return ASCON_HASH256_SZ; #endif default: return 0; @@ -432,6 +452,11 @@ psa_status_t psa_hash_setup(psa_hash_operation_t *operation, ret = wc_InitShake256((wc_Shake *)&ctx->ctx.sha3, NULL, wolfPSA_GetDefaultDevID()); break; +#endif +#ifdef HAVE_ASCON + case PSA_ALG_ASCON_HASH256: + ret = wc_AsconHash256_Init(&ctx->ctx.ascon256); + break; #endif default: XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -545,6 +570,12 @@ psa_status_t psa_hash_update(psa_hash_operation_t *operation, ret = wc_Shake256_Update((wc_Shake *)&ctx->ctx.sha3, input, (word32)input_length); break; +#endif +#ifdef HAVE_ASCON + case PSA_ALG_ASCON_HASH256: + ret = wc_AsconHash256_Update(&ctx->ctx.ascon256, input, + (word32)input_length); + break; #endif default: return wolfpsa_hash_fail(operation, PSA_ERROR_NOT_SUPPORTED); @@ -655,6 +686,11 @@ psa_status_t psa_hash_finish(psa_hash_operation_t *operation, case PSA_ALG_SHAKE256_512: ret = wc_Shake256_Final((wc_Shake *)&ctx->ctx.sha3, hash, 64u); break; +#endif +#ifdef HAVE_ASCON + case PSA_ALG_ASCON_HASH256: + ret = wc_AsconHash256_Final(&ctx->ctx.ascon256, hash); + break; #endif default: return wolfpsa_hash_fail(operation, PSA_ERROR_NOT_SUPPORTED); @@ -849,6 +885,13 @@ psa_status_t psa_hash_clone(const psa_hash_operation_t *source_operation, ret = wc_Shake256_Copy((wc_Shake *)(uintptr_t)&source_ctx->ctx.sha3, &target_ctx->ctx.sha3); break; +#endif +#ifdef HAVE_ASCON + case PSA_ALG_ASCON_HASH256: + XMEMCPY(&target_ctx->ctx.ascon256, &source_ctx->ctx.ascon256, + sizeof(wc_AsconHash256)); + ret = 0; + break; #endif default: wc_ForceZero(target_ctx, sizeof(*target_ctx)); diff --git a/src/psa_key_derivation.c b/src/psa_key_derivation.c index 8b9581a..e9d8bee 100644 --- a/src/psa_key_derivation.c +++ b/src/psa_key_derivation.c @@ -71,11 +71,15 @@ typedef struct wolfpsa_kdf_ctx { size_t info_length; uint8_t *label; size_t label_length; + uint8_t *context; + size_t context_length; uint8_t *seed; size_t seed_length; uint8_t *password; size_t password_length; uint32_t cost; + size_t sp800_108_L_bytes; /* total output length in bytes, snapshotted at + * first output_bytes() call for SP800-108 */ int is_key_agreement; int is_raw_kdf; int output_started; @@ -83,6 +87,13 @@ typedef struct wolfpsa_kdf_ctx { size_t output_cache_length; } wolfpsa_kdf_ctx_t; +/* PSA_KEY_DERIVATION_INPUT_CONTEXT (0x0206) is defined in PSA 1.4 but not + * yet present in this header set; provide a local fallback so the SP800-108 + * implementation can refer to it without touching the shared headers. */ +#ifndef PSA_KEY_DERIVATION_INPUT_CONTEXT +#define PSA_KEY_DERIVATION_INPUT_CONTEXT ((psa_key_derivation_step_t) 0x0206) +#endif + #define WOLFPSA_KDF_STEP_SECRET (1u << 0) #define WOLFPSA_KDF_STEP_OTHER_SECRET (1u << 1) #define WOLFPSA_KDF_STEP_SALT (1u << 2) @@ -91,6 +102,7 @@ typedef struct wolfpsa_kdf_ctx { #define WOLFPSA_KDF_STEP_SEED (1u << 5) #define WOLFPSA_KDF_STEP_PASSWORD (1u << 6) #define WOLFPSA_KDF_STEP_COST (1u << 7) +#define WOLFPSA_KDF_STEP_CONTEXT (1u << 8) static wolfpsa_kdf_ctx_t* wolfpsa_kdf_get_ctx(psa_key_derivation_operation_t *operation) { @@ -177,6 +189,9 @@ static int wolfpsa_hash_type_from_alg(psa_algorithm_t alg) else if (PSA_ALG_IS_PBKDF2_HMAC(alg)) { hash_alg = PSA_ALG_PBKDF2_HMAC_GET_HASH(alg); } + else if (PSA_ALG_IS_SP800_108_COUNTER_HMAC(alg)) { + hash_alg = PSA_ALG_GET_HASH(alg); + } switch (hash_alg) { case PSA_ALG_SHA_1: @@ -231,6 +246,8 @@ static uint32_t wolfpsa_kdf_step_mask(psa_key_derivation_step_t step) return WOLFPSA_KDF_STEP_PASSWORD; case PSA_KEY_DERIVATION_INPUT_COST: return WOLFPSA_KDF_STEP_COST; + case PSA_KEY_DERIVATION_INPUT_CONTEXT: + return WOLFPSA_KDF_STEP_CONTEXT; default: return 0; } @@ -361,6 +378,18 @@ static psa_status_t wolfpsa_kdf_validate_step(wolfpsa_kdf_ctx_t *ctx, } return PSA_SUCCESS; } + else if (PSA_ALG_IS_SP800_108_COUNTER_HMAC(ctx->alg) || + ctx->alg == PSA_ALG_SP800_108_COUNTER_CMAC) { + /* Allowed steps: SECRET (mandatory), LABEL (optional), CONTEXT + * (optional). SECRET must be provided before output; each step + * can only be set once. */ + if (step != PSA_KEY_DERIVATION_INPUT_SECRET && + step != PSA_KEY_DERIVATION_INPUT_LABEL && + step != PSA_KEY_DERIVATION_INPUT_CONTEXT) { + return PSA_ERROR_INVALID_ARGUMENT; + } + return PSA_SUCCESS; + } return PSA_ERROR_NOT_SUPPORTED; } @@ -392,6 +421,8 @@ psa_status_t psa_key_derivation_setup(psa_key_derivation_operation_t *operation, if (!(PSA_ALG_IS_ANY_HKDF(kdf_alg) || PSA_ALG_IS_TLS12_PRF(kdf_alg) || PSA_ALG_IS_TLS12_PSK_TO_MS(kdf_alg) || PSA_ALG_IS_PBKDF2(kdf_alg) || + PSA_ALG_IS_SP800_108_COUNTER_HMAC(kdf_alg) || + kdf_alg == PSA_ALG_SP800_108_COUNTER_CMAC || kdf_alg == PSA_ALG_CATEGORY_KEY_DERIVATION)) { return PSA_ERROR_NOT_SUPPORTED; } @@ -411,9 +442,20 @@ psa_status_t psa_key_derivation_setup(psa_key_derivation_operation_t *operation, return PSA_ERROR_NOT_SUPPORTED; } #endif +#if defined(NO_HMAC) + if (PSA_ALG_IS_SP800_108_COUNTER_HMAC(kdf_alg)) { + return PSA_ERROR_NOT_SUPPORTED; + } +#endif +#if !defined(WOLFSSL_CMAC) || defined(NO_AES) + if (kdf_alg == PSA_ALG_SP800_108_COUNTER_CMAC) { + return PSA_ERROR_NOT_SUPPORTED; + } +#endif if (PSA_ALG_IS_ANY_HKDF(kdf_alg) || PSA_ALG_IS_TLS12_PRF(kdf_alg) || - PSA_ALG_IS_TLS12_PSK_TO_MS(kdf_alg) || PSA_ALG_IS_PBKDF2_HMAC(kdf_alg)) { + PSA_ALG_IS_TLS12_PSK_TO_MS(kdf_alg) || PSA_ALG_IS_PBKDF2_HMAC(kdf_alg) || + PSA_ALG_IS_SP800_108_COUNTER_HMAC(kdf_alg)) { hash_type = wolfpsa_hash_type_from_alg(kdf_alg); if (hash_type == WC_HASH_TYPE_NONE) { return PSA_ERROR_NOT_SUPPORTED; @@ -449,6 +491,11 @@ psa_status_t psa_key_derivation_setup(psa_key_derivation_operation_t *operation, ctx->capacity = 255u * (size_t)hash_len; } } + /* SP 800-108 counter-mode: default capacity is 2^29 - 1 bytes per spec */ + if (PSA_ALG_IS_SP800_108_COUNTER_HMAC(kdf_alg) || + kdf_alg == PSA_ALG_SP800_108_COUNTER_CMAC) { + ctx->capacity = (size_t)0x1fffffffu; + } operation->opaque = (uintptr_t)ctx; return PSA_SUCCESS; @@ -468,6 +515,7 @@ psa_status_t psa_key_derivation_abort(psa_key_derivation_operation_t *operation) wolfpsa_kdf_free_buf(&ctx->salt, &ctx->salt_length); wolfpsa_kdf_free_buf(&ctx->info, &ctx->info_length); wolfpsa_kdf_free_buf(&ctx->label, &ctx->label_length); + wolfpsa_kdf_free_buf(&ctx->context, &ctx->context_length); wolfpsa_kdf_free_buf(&ctx->seed, &ctx->seed_length); wolfpsa_kdf_free_buf(&ctx->password, &ctx->password_length); if (ctx->output_cache != NULL) { @@ -577,6 +625,10 @@ psa_status_t psa_key_derivation_input_bytes(psa_key_derivation_operation_t *oper status = wolfpsa_kdf_append(&ctx->label, &ctx->label_length, data, data_length); break; + case PSA_KEY_DERIVATION_INPUT_CONTEXT: + status = wolfpsa_kdf_append(&ctx->context, &ctx->context_length, + data, data_length); + break; case PSA_KEY_DERIVATION_INPUT_SEED: status = wolfpsa_kdf_append(&ctx->seed, &ctx->seed_length, data, data_length); @@ -1169,6 +1221,295 @@ static psa_status_t wolfpsa_kdf_pbkdf2(wolfpsa_kdf_ctx_t *ctx, return PSA_ERROR_NOT_SUPPORTED; } +/* + * SP 800-108r1 counter-mode KDF — HMAC variant. + * + * Fixed-input construction (PSA 1.4 §10.8): + * K(i) = HMAC(K_IN, [i]_4 || Label || 0x00 || Context || [L]_4) + * + * Where: + * [i]_4 — 4-byte big-endian counter starting at 1 + * Label — PSA_KEY_DERIVATION_INPUT_LABEL bytes (may be zero-length) + * 0x00 — single separator byte + * Context — PSA_KEY_DERIVATION_INPUT_CONTEXT bytes (may be zero-length) + * [L]_4 — 4-byte big-endian encoding of total requested output in BITS, + * snapshotted from capacity at first output_bytes() call + * + * Output stream is K(1) || K(2) || ... truncated to output_length bytes. + */ +static psa_status_t wolfpsa_kdf_sp800_108_hmac(wolfpsa_kdf_ctx_t *ctx, + uint8_t *output, + size_t output_length) +{ +#if defined(NO_HMAC) + (void)ctx; + (void)output; + (void)output_length; + return PSA_ERROR_NOT_SUPPORTED; +#else + int hash_type; + int hmac_len; + uint32_t L_bits_hi; + uint32_t L_bits_lo; + uint32_t counter; + size_t offset = 0; + uint8_t block[WC_MAX_DIGEST_SIZE]; + uint8_t counter_buf[4]; + uint8_t sep = 0x00u; + uint8_t L_buf[4]; + psa_status_t status = PSA_SUCCESS; + Hmac hmac; + int ret; + int hmac_inited = 0; + + hash_type = wolfpsa_hash_type_from_alg(ctx->alg); + if (hash_type == WC_HASH_TYPE_NONE) { + return PSA_ERROR_NOT_SUPPORTED; + } + hmac_len = wc_HashGetDigestSize(hash_type); + if (hmac_len <= 0 || (size_t)hmac_len > sizeof(block)) { + return PSA_ERROR_NOT_SUPPORTED; + } + + /* L = total derivation length in bits (fits in 32 bits since capacity + * <= 2^29-1 bytes, so bits <= 2^32-8 which wraps — use 64-bit arithmetic + * to split into hi and lo words for the [L]_4 encoding; the PSA spec + * uses a 32-bit field so L must fit in 32 bits of bits, i.e. <= 2^29-1 + * bytes guarantees L_bits <= (2^29-1)*8 = 2^32-8, which does NOT fit in + * 32 bits. The spec encodes [L]_4 as a 32-bit big-endian value so we + * use only the lower 32 bits of (capacity_bytes * 8) — for capacity up + * to 2^29-1 bytes the bit count is at most 0xFFFFFFF8, fitting in 32. */ + { + uint64_t L_bits = (uint64_t)ctx->sp800_108_L_bytes * 8u; + L_bits_hi = (uint32_t)(L_bits >> 32); + L_bits_lo = (uint32_t)(L_bits & 0xffffffffu); + (void)L_bits_hi; /* only low 32 bits used per spec [L]_4 */ + } + L_buf[0] = (uint8_t)((L_bits_lo >> 24) & 0xff); + L_buf[1] = (uint8_t)((L_bits_lo >> 16) & 0xff); + L_buf[2] = (uint8_t)((L_bits_lo >> 8) & 0xff); + L_buf[3] = (uint8_t)( L_bits_lo & 0xff); + + if (ctx->secret_length > (size_t)INT_MAX) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + for (counter = 1u; offset < output_length; counter++) { + size_t copy_len; + + counter_buf[0] = (uint8_t)((counter >> 24) & 0xff); + counter_buf[1] = (uint8_t)((counter >> 16) & 0xff); + counter_buf[2] = (uint8_t)((counter >> 8) & 0xff); + counter_buf[3] = (uint8_t)( counter & 0xff); + + ret = wc_HmacInit(&hmac, NULL, wolfPSA_GetDefaultDevID()); + if (ret != 0) { + status = wc_error_to_psa_status(ret); + goto hmac_cleanup; + } + hmac_inited = 1; + + ret = wc_HmacSetKey(&hmac, hash_type, + ctx->secret, (word32)ctx->secret_length); + if (ret != 0) { + status = wc_error_to_psa_status(ret); + goto hmac_cleanup; + } + + /* [i]_4 */ + ret = wc_HmacUpdate(&hmac, counter_buf, sizeof(counter_buf)); + if (ret == 0 && ctx->label_length > 0) { + /* Label */ + ret = wc_HmacUpdate(&hmac, ctx->label, (word32)ctx->label_length); + } + /* 0x00 separator */ + if (ret == 0) { + ret = wc_HmacUpdate(&hmac, &sep, 1u); + } + if (ret == 0 && ctx->context_length > 0) { + /* Context */ + ret = wc_HmacUpdate(&hmac, ctx->context, + (word32)ctx->context_length); + } + /* [L]_4 */ + if (ret == 0) { + ret = wc_HmacUpdate(&hmac, L_buf, sizeof(L_buf)); + } + if (ret == 0) { + ret = wc_HmacFinal(&hmac, block); + } + + wc_HmacFree(&hmac); + hmac_inited = 0; + + if (ret != 0) { + status = wc_error_to_psa_status(ret); + goto hmac_cleanup; + } + + copy_len = (size_t)hmac_len; + if (offset + copy_len > output_length) { + copy_len = output_length - offset; + } + XMEMCPY(output + offset, block, copy_len); + offset += copy_len; + } + +hmac_cleanup: + if (hmac_inited) { + wc_HmacFree(&hmac); + } + wc_ForceZero(block, sizeof(block)); + return status; +#endif /* NO_HMAC */ +} + +/* + * SP 800-108r1 counter-mode KDF — CMAC variant. + * + * Fixed-input construction (PSA 1.4 §10.8): + * K_0 = CMAC(K_IN, Label || 0x00 || Context || [L]_4) + * K(i) = CMAC(K_IN, [i]_4 || Label || 0x00 || Context || [L]_4 || K_0) + * for i = 1, 2, 3, ... + * + * K_0 is a robustness-mitigation term specific to the CMAC construction. + * The secret (K_IN) must be a valid AES key: 16, 24, or 32 bytes. + * Output block size is WC_AES_BLOCK_SIZE (16 bytes). + * [L]_4 encodes total output length in bits as 4-byte big-endian. + */ +static psa_status_t wolfpsa_kdf_sp800_108_cmac(wolfpsa_kdf_ctx_t *ctx, + uint8_t *output, + size_t output_length) +{ +#if !defined(WOLFSSL_CMAC) || defined(NO_AES) + (void)ctx; + (void)output; + (void)output_length; + return PSA_ERROR_NOT_SUPPORTED; +#else + uint32_t L_bits_lo; + uint32_t counter; + size_t offset = 0; + uint8_t K0[WC_AES_BLOCK_SIZE]; + uint8_t block[WC_AES_BLOCK_SIZE]; + uint8_t counter_buf[4]; + uint8_t sep = 0x00u; + uint8_t L_buf[4]; + word32 out_sz; + psa_status_t status = PSA_SUCCESS; + Cmac cmac; + int ret; + + /* CMAC key must be a valid AES key length */ + if (ctx->secret_length != 16u && + ctx->secret_length != 24u && + ctx->secret_length != 32u) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (wolfpsa_check_word32_length(ctx->secret_length) != PSA_SUCCESS) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + { + uint64_t L_bits = (uint64_t)ctx->sp800_108_L_bytes * 8u; + L_bits_lo = (uint32_t)(L_bits & 0xffffffffu); + } + L_buf[0] = (uint8_t)((L_bits_lo >> 24) & 0xff); + L_buf[1] = (uint8_t)((L_bits_lo >> 16) & 0xff); + L_buf[2] = (uint8_t)((L_bits_lo >> 8) & 0xff); + L_buf[3] = (uint8_t)( L_bits_lo & 0xff); + + /* --- compute K_0 = CMAC(K_IN, Label || 0x00 || Context || [L]_4) --- */ + ret = wc_InitCmac(&cmac, ctx->secret, (word32)ctx->secret_length, + WC_CMAC_AES, NULL); + if (ret != 0) { + status = wc_error_to_psa_status(ret); + goto cmac_cleanup; + } + if (ctx->label_length > 0) { + ret = wc_CmacUpdate(&cmac, ctx->label, (word32)ctx->label_length); + } + if (ret == 0) { + ret = wc_CmacUpdate(&cmac, &sep, 1u); + } + if (ret == 0 && ctx->context_length > 0) { + ret = wc_CmacUpdate(&cmac, ctx->context, (word32)ctx->context_length); + } + if (ret == 0) { + ret = wc_CmacUpdate(&cmac, L_buf, sizeof(L_buf)); + } + out_sz = WC_AES_BLOCK_SIZE; + if (ret == 0) { + ret = wc_CmacFinal(&cmac, K0, &out_sz); + } + wc_CmacFree(&cmac); + if (ret != 0 || out_sz != WC_AES_BLOCK_SIZE) { + status = ret == 0 ? PSA_ERROR_NOT_SUPPORTED : + wc_error_to_psa_status(ret); + goto cmac_cleanup; + } + + /* --- K(i) loop --- */ + for (counter = 1u; offset < output_length; counter++) { + size_t copy_len; + + counter_buf[0] = (uint8_t)((counter >> 24) & 0xff); + counter_buf[1] = (uint8_t)((counter >> 16) & 0xff); + counter_buf[2] = (uint8_t)((counter >> 8) & 0xff); + counter_buf[3] = (uint8_t)( counter & 0xff); + + ret = wc_InitCmac(&cmac, ctx->secret, (word32)ctx->secret_length, + WC_CMAC_AES, NULL); + if (ret != 0) { + status = wc_error_to_psa_status(ret); + goto cmac_cleanup; + } + + /* [i]_4 */ + ret = wc_CmacUpdate(&cmac, counter_buf, sizeof(counter_buf)); + if (ret == 0 && ctx->label_length > 0) { + ret = wc_CmacUpdate(&cmac, ctx->label, (word32)ctx->label_length); + } + if (ret == 0) { + ret = wc_CmacUpdate(&cmac, &sep, 1u); + } + if (ret == 0 && ctx->context_length > 0) { + ret = wc_CmacUpdate(&cmac, ctx->context, + (word32)ctx->context_length); + } + if (ret == 0) { + ret = wc_CmacUpdate(&cmac, L_buf, sizeof(L_buf)); + } + /* K_0 appended (CMAC robustness mitigation) */ + if (ret == 0) { + ret = wc_CmacUpdate(&cmac, K0, WC_AES_BLOCK_SIZE); + } + out_sz = WC_AES_BLOCK_SIZE; + if (ret == 0) { + ret = wc_CmacFinal(&cmac, block, &out_sz); + } + wc_CmacFree(&cmac); + if (ret != 0 || out_sz != WC_AES_BLOCK_SIZE) { + status = ret == 0 ? PSA_ERROR_NOT_SUPPORTED : + wc_error_to_psa_status(ret); + goto cmac_cleanup; + } + + copy_len = WC_AES_BLOCK_SIZE; + if (offset + copy_len > output_length) { + copy_len = output_length - offset; + } + XMEMCPY(output + offset, block, copy_len); + offset += copy_len; + } + +cmac_cleanup: + wc_ForceZero(K0, sizeof(K0)); + wc_ForceZero(block, sizeof(block)); + return status; +#endif /* WOLFSSL_CMAC && !NO_AES */ +} + static psa_status_t wolfpsa_kdf_compute_output(wolfpsa_kdf_ctx_t *ctx, uint8_t *output, size_t output_length) @@ -1190,6 +1531,12 @@ static psa_status_t wolfpsa_kdf_compute_output(wolfpsa_kdf_ctx_t *ctx, if (PSA_ALG_IS_PBKDF2(ctx->alg)) { return wolfpsa_kdf_pbkdf2(ctx, output, output_length); } + if (PSA_ALG_IS_SP800_108_COUNTER_HMAC(ctx->alg)) { + return wolfpsa_kdf_sp800_108_hmac(ctx, output, output_length); + } + if (ctx->alg == PSA_ALG_SP800_108_COUNTER_CMAC) { + return wolfpsa_kdf_sp800_108_cmac(ctx, output, output_length); + } return PSA_ERROR_NOT_SUPPORTED; } @@ -1253,6 +1600,20 @@ psa_status_t psa_key_derivation_output_bytes(psa_key_derivation_operation_t *ope return PSA_ERROR_BAD_STATE; } } + else if (PSA_ALG_IS_SP800_108_COUNTER_HMAC(ctx->alg) || + ctx->alg == PSA_ALG_SP800_108_COUNTER_CMAC) { + if ((ctx->steps_set & WOLFPSA_KDF_STEP_SECRET) == 0) { + return PSA_ERROR_BAD_STATE; + } + /* Snapshot L on the first output call. L = capacity in bits before + * any output has been consumed (spec: PSA 1.4 §10.8, SP800-108 + * counter mode). At this point capacity has not yet been decremented + * by wolfpsa_kdf_require_output, so capacity + output_length equals + * the original capacity set at setup / set_capacity time. */ + if (!ctx->output_started) { + ctx->sp800_108_L_bytes = ctx->capacity; + } + } status = wolfpsa_kdf_require_output(ctx, output_length); if (status != PSA_SUCCESS) { diff --git a/wolfpsa/psa/crypto_sizes.h b/wolfpsa/psa/crypto_sizes.h index 1d44b6f..3df146a 100644 --- a/wolfpsa/psa/crypto_sizes.h +++ b/wolfpsa/psa/crypto_sizes.h @@ -48,6 +48,7 @@ PSA_ALG_HMAC_GET_HASH(alg) == PSA_ALG_SHA3_256 ? 32u : \ PSA_ALG_HMAC_GET_HASH(alg) == PSA_ALG_SHA3_384 ? 48u : \ PSA_ALG_HMAC_GET_HASH(alg) == PSA_ALG_SHA3_512 ? 64u : \ + PSA_ALG_HMAC_GET_HASH(alg) == PSA_ALG_ASCON_HASH256 ? 32u : \ 0u) @@ -217,10 +218,14 @@ 0u : \ (key_type) == PSA_KEY_TYPE_CHACHA20 && \ PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_CHACHA20_POLY1305) ? 12u : \ + (key_type) == PSA_KEY_TYPE_XCHACHA20 && \ + PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_XCHACHA20_POLY1305) ? 24u : \ + (key_type) == PSA_KEY_TYPE_ASCON && \ + PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_ASCON_AEAD128) ? 16u : \ 0u) -#define PSA_AEAD_NONCE_MAX_SIZE 13u +#define PSA_AEAD_NONCE_MAX_SIZE 24u diff --git a/wolfpsa/psa/crypto_values.h b/wolfpsa/psa/crypto_values.h index 5c3be4c..2560885 100644 --- a/wolfpsa/psa/crypto_values.h +++ b/wolfpsa/psa/crypto_values.h @@ -537,6 +537,8 @@ PSA_ALG_AEAD_WITH_DEFAULT_LENGTH_TAG_CASE(aead_alg, PSA_ALG_CCM) \ PSA_ALG_AEAD_WITH_DEFAULT_LENGTH_TAG_CASE(aead_alg, PSA_ALG_GCM) \ PSA_ALG_AEAD_WITH_DEFAULT_LENGTH_TAG_CASE(aead_alg, PSA_ALG_CHACHA20_POLY1305) \ + PSA_ALG_AEAD_WITH_DEFAULT_LENGTH_TAG_CASE(aead_alg, PSA_ALG_XCHACHA20_POLY1305) \ + PSA_ALG_AEAD_WITH_DEFAULT_LENGTH_TAG_CASE(aead_alg, PSA_ALG_ASCON_AEAD128) \ 0) #define PSA_ALG_AEAD_WITH_DEFAULT_LENGTH_TAG_CASE(aead_alg, ref) \ PSA_ALG_AEAD_WITH_SHORTENED_TAG(aead_alg, 0) == \ @@ -911,6 +913,7 @@ static inline int psa_key_id_is_null(psa_key_id_t key) #define PSA_KEY_DERIVATION_INPUT_LABEL ((psa_key_derivation_step_t) 0x0201) +#define PSA_KEY_DERIVATION_INPUT_CONTEXT ((psa_key_derivation_step_t) 0x0206) #define PSA_KEY_DERIVATION_INPUT_SALT ((psa_key_derivation_step_t) 0x0202) From 956b9d4a8323ff628ad96597feacb181b8a3ccd2 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 18:52:09 +0200 Subject: [PATCH 20/24] tests/ci: PSA 1.4 coverage suite, CHANGELOG Add nine feature-area coverage tests (ML-DSA, ML-KEM and the KEM API, SHAKE XOF, AES-KW key wrapping, signature contexts, LMS/XMSS verify-only with wolfSSL KAT vectors, Ascon and XChaCha20-Poly1305 KATs, SP800-108 counter KDFs, and 1.4 miscellany: stubs, custom wrappers, psa_check_key_usage, ECDSA verify-equivalence, size macros), wire them into the test Makefile and the CI workflow, and document the 1.4 upgrade in the CHANGELOG. Fix flagged by the Ascon test: map ASCON_AUTH_E to PSA_ERROR_INVALID_SIGNATURE in the Ascon-AEAD128 decrypt path. --- .github/workflows/test-psa-api.yml | 69 +- .gitignore | 9 + CHANGELOG.md | 55 + src/psa_aead.c | 2 +- test/Makefile | 11 +- test/psa_server/psa_14_misc_test.c | 531 +++++++++ test/psa_server/psa_ascon_xchacha_test.c | 662 +++++++++++ test/psa_server/psa_key_wrap_test.c | 595 ++++++++++ test/psa_server/psa_lms_xmss_verify_test.c | 1160 ++++++++++++++++++++ test/psa_server/psa_mldsa_test.c | 688 ++++++++++++ test/psa_server/psa_mlkem_test.c | 868 +++++++++++++++ test/psa_server/psa_sign_context_test.c | 508 +++++++++ test/psa_server/psa_sp800_108_test.c | 902 +++++++++++++++ test/psa_server/psa_xof_test.c | 685 ++++++++++++ 14 files changed, 6711 insertions(+), 34 deletions(-) create mode 100644 test/psa_server/psa_14_misc_test.c create mode 100644 test/psa_server/psa_ascon_xchacha_test.c create mode 100644 test/psa_server/psa_key_wrap_test.c create mode 100644 test/psa_server/psa_lms_xmss_verify_test.c create mode 100644 test/psa_server/psa_mldsa_test.c create mode 100644 test/psa_server/psa_mlkem_test.c create mode 100644 test/psa_server/psa_sign_context_test.c create mode 100644 test/psa_server/psa_sp800_108_test.c create mode 100644 test/psa_server/psa_xof_test.c diff --git a/.github/workflows/test-psa-api.yml b/.github/workflows/test-psa-api.yml index 152a7ff..83257d0 100644 --- a/.github/workflows/test-psa-api.yml +++ b/.github/workflows/test-psa-api.yml @@ -27,39 +27,44 @@ jobs: run: make -C test rebuild-wolfssl-psa - name: Build PSA API tests - run: make -C test psa_api_test psa_des3_stack_scrub_test psa_ecc_bit_inference_test psa_ecc_curve_id_test psa_random_size_test + run: > + make -C test + psa_api_test + psa_des3_stack_scrub_test + psa_ecc_bit_inference_test + psa_ecc_curve_id_test + psa_random_size_test + psa_rsa_pss_interop_test + psa_mldsa_test + psa_mlkem_test + psa_xof_test + psa_key_wrap_test + psa_sign_context_test + psa_lms_xmss_verify_test + psa_ascon_xchacha_test + psa_sp800_108_test + psa_14_misc_test - - name: Run PSA API test + - name: Run PSA API tests env: LD_LIBRARY_PATH: ${{ github.workspace }}:${{ github.workspace }}/../wolfssl/src/.libs run: | - rm -rf test/.store - ./test/psa_api_test - - - name: Run ECC bit inference test - env: - LD_LIBRARY_PATH: ${{ github.workspace }}:${{ github.workspace }}/../wolfssl/src/.libs - run: | - rm -rf test/.store - ./test/psa_ecc_bit_inference_test - - - name: Run DES3 stack scrub test - env: - LD_LIBRARY_PATH: ${{ github.workspace }}:${{ github.workspace }}/../wolfssl/src/.libs - run: | - rm -rf test/.store - ./test/psa_des3_stack_scrub_test - - - name: Run ECC curve id test - env: - LD_LIBRARY_PATH: ${{ github.workspace }}:${{ github.workspace }}/../wolfssl/src/.libs - run: | - rm -rf test/.store - ./test/psa_ecc_curve_id_test - - - name: Run random size test - env: - LD_LIBRARY_PATH: ${{ github.workspace }}:${{ github.workspace }}/../wolfssl/src/.libs - run: | - rm -rf test/.store - ./test/psa_random_size_test + for t in psa_api_test \ + psa_ecc_bit_inference_test \ + psa_des3_stack_scrub_test \ + psa_ecc_curve_id_test \ + psa_random_size_test \ + psa_rsa_pss_interop_test \ + psa_mldsa_test \ + psa_mlkem_test \ + psa_xof_test \ + psa_key_wrap_test \ + psa_sign_context_test \ + psa_lms_xmss_verify_test \ + psa_ascon_xchacha_test \ + psa_sp800_108_test \ + psa_14_misc_test; do + echo "=== $t ===" + rm -rf test/.store + ./test/$t + done diff --git a/.gitignore b/.gitignore index 36efc97..f6b5499 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,12 @@ wolfcrypt-psa-benchmark .store/ *.log .codex +test/psa_mldsa_test +test/psa_mlkem_test +test/psa_xof_test +test/psa_key_wrap_test +test/psa_sign_context_test +test/psa_lms_xmss_verify_test +test/psa_ascon_xchacha_test +test/psa_sp800_108_test +test/psa_14_misc_test diff --git a/CHANGELOG.md b/CHANGELOG.md index bd8dcdb..9539a0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,60 @@ # Changelog +## Unreleased: PSA Certified Crypto API 1.4 + PQC extension 1.4 + +Upgrade of the public API surface and implementation to PSA Certified +Crypto API 1.4 Final and the PQC extension 1.4, built against current +wolfSSL master. + +### Breaking changes + +- ML-DSA now follows the PSA 1.4 PQC extension: key bits are 128/192/256 + (security strength, ML-DSA-44/65/87) instead of the previous 2/3/5 + level convention, and the key-pair import/export format is the 32-byte + FIPS 204 seed xi (public keys remain raw pk bytes). +- The nonstandard `psa_ml_dsa_generate_key/sign/verify` exports and the + `PSA_ML_DSA_PARAMETER_*` / `psa_ml_dsa_parameter_t` macros were removed; + use the standard PSA key management and signature APIs instead. + +### Added + +- Key encapsulation: `psa_encapsulate()` / `psa_decapsulate()` with + PSA_ALG_ML_KEM. ML-KEM key pairs use the 64-byte d||z seed format with + bits 512/768/1024; the shared secret is returned as a new key. +- ML-DSA through the standard APIs: hedged and deterministic pure ML-DSA + via `psa_sign_message`/`psa_verify_message`, plus HashML-DSA variants + usable through both the message and hash entry points. +- Context-aware signatures: `psa_sign_message_with_context()`, + `psa_verify_message_with_context()`, `psa_sign_hash_with_context()`, + `psa_verify_hash_with_context()` and PSA_ALG_EDDSA_CTX (Ed25519ctx), + with context support for Ed25519ph/Ed448 and the ML-DSA family. +- Verify-only LMS/HSS and XMSS/XMSS^MT public-key support through + `psa_verify_message` (PSA_ALG_LMS/HSS/XMSS/XMSS_MT). +- XOF API: incremental SHAKE128/SHAKE256 via `psa_xof_setup/update/ + output/abort` (Ascon XOFs report NOT_SUPPORTED). +- Key wrapping: `psa_wrap_key()` / `psa_unwrap_key()` with PSA_ALG_KW + (AES-KW, RFC 3394) and the new WRAP/UNWRAP usage flags (PSA_ALG_KWP + reports NOT_SUPPORTED). +- Ascon-Hash256 and Ascon-AEAD128 (one-shot), XChaCha20-Poly1305 + (one-shot, 24-byte nonce) with the PSA_KEY_TYPE_XCHACHA20/ASCON key + types. +- SP800-108r1 counter-mode KDFs: PSA_ALG_SP800_108_COUNTER_HMAC(hash) + and PSA_ALG_SP800_108_COUNTER_CMAC. +- `psa_check_key_usage()`, `psa_generate_key_custom()` and + `psa_key_derivation_output_key_custom()` (default parameters only). +- 1.4 semantic change: ECDSA and deterministic ECDSA are treated as + equivalent when verifying signatures. +- Complete 1.4 macro surface: PQC classifier/encoding macros (ML-DSA, + ML-KEM, SLH-DSA, LMS/HSS, XMSS), WPA3-SAE values, encapsulation and + key-wrap size macros, hash-suspend format constants, and PQC arms in + the signature/export size macros (PSA_SIGNATURE_MAX_SIZE is now 4627). +- Stubs returning PSA_ERROR_NOT_SUPPORTED for the interruptible + operations, `psa_attach_key()` and `psa_hash_suspend/resume()`. + SLH-DSA key types are recognized but report NOT_SUPPORTED. +- New coverage tests: ML-DSA, ML-KEM/KEM API, XOF, AES-KW, signature + contexts, LMS/XMSS verify, Ascon/XChaCha, SP800-108 and 1.4 misc. + + ## v5.9.1 Initial official release of `wolfPSA`. This project follows wolfSSL version numbering. diff --git a/src/psa_aead.c b/src/psa_aead.c index 9ab0bba..ced4f41 100644 --- a/src/psa_aead.c +++ b/src/psa_aead.c @@ -1227,7 +1227,7 @@ static psa_status_t wolfpsa_ascon_oneshot_decrypt( wc_ForceZero(&ascon, sizeof(ascon)); wolfpsa_forcezero_free_key_data(key_data, key_data_length); - if (ret == MAC_CMP_FAILED_E) { + if (ret == MAC_CMP_FAILED_E || ret == ASCON_AUTH_E) { return PSA_ERROR_INVALID_SIGNATURE; } if (ret != 0) { diff --git a/test/Makefile b/test/Makefile index 78a0d73..1c63e0f 100644 --- a/test/Makefile +++ b/test/Makefile @@ -57,7 +57,13 @@ WOLFSSL_LOCAL_SYMBOLS := $(if $(WOLFSSL_LOCAL_LIB),$(shell \ $(NM) $(WOLFSSL_LOCAL_LIB) 2>/dev/null)) WOLFSSL_HAS_PSA_TLS := $(findstring wolfSSL_CTX_psa_enable,$(WOLFSSL_LOCAL_SYMBOLS)) -BINARIES = psa_api_test psa_crypto_init_test psa_des3_stack_scrub_test psa_ecc_bit_inference_test psa_ecc_curve_id_test psa_random_size_test psa_rsa_pss_interop_test psa_tls_client wolfcrypt-psa-benchmark +BINARIES = psa_api_test psa_crypto_init_test psa_des3_stack_scrub_test psa_ecc_bit_inference_test psa_ecc_curve_id_test psa_random_size_test psa_rsa_pss_interop_test psa_mldsa_test psa_mlkem_test psa_xof_test psa_key_wrap_test psa_sign_context_test psa_lms_xmss_verify_test psa_ascon_xchacha_test psa_sp800_108_test psa_14_misc_test psa_tls_client wolfcrypt-psa-benchmark + +# PSA 1.4 coverage tests: one self-contained binary per feature area, all +# built from psa_server/.c with the default link recipe. +PSA_14_TESTS = psa_mldsa_test psa_mlkem_test psa_xof_test psa_key_wrap_test \ + psa_sign_context_test psa_lms_xmss_verify_test psa_ascon_xchacha_test \ + psa_sp800_108_test psa_14_misc_test ifdef WOLFSSL_HAS_PSA_TLS BINARIES += psa_tls_server @@ -105,6 +111,9 @@ psa_random_size_test: require-wolfssl-lib $(PSA_RANDOM_SIZE_TEST_OBJS) psa_rsa_pss_interop_test: require-wolfssl-lib $(PSA_RSA_PSS_TEST_OBJS) $(CC) $(CFLAGS) -o $@ $(PSA_RSA_PSS_TEST_OBJS) $(LDFLAGS) $(LDLIBS) $(RPATH_WOLFPSA) $(RPATH_WOLFSSL) +$(PSA_14_TESTS): %: require-wolfssl-lib psa_server/%.o + $(CC) $(CFLAGS) -o $@ psa_server/$@.o $(LDFLAGS) $(LDLIBS) $(RPATH_WOLFPSA) $(RPATH_WOLFSSL) + ifdef WOLFSSL_HAS_PSA_TLS psa_tls_server: psa_server/psa_tls_server.o $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LDLIBS) $(RPATH_WOLFPSA) $(RPATH_WOLFSSL) diff --git a/test/psa_server/psa_14_misc_test.c b/test/psa_server/psa_14_misc_test.c new file mode 100644 index 0000000..c3c9ec0 --- /dev/null +++ b/test/psa_server/psa_14_misc_test.c @@ -0,0 +1,531 @@ +/* psa_14_misc_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 + */ + +#include "psa_api_test_user_settings.h" + +#ifndef WOLFSSL_USER_SETTINGS +#define WOLFSSL_USER_SETTINGS +#endif + +#include + +#include +#include + +#include + +static int expect_status(const char *label, psa_status_t status, + psa_status_t expected) +{ + if (status != expected) { + printf("FAIL %s status=%d expected=%d\n", label, (int)status, + (int)expected); + return 1; + } + return 0; +} + +/* Case 1: pre-init BAD_STATE check for psa_attach_key */ +static int test_pre_init_bad_state(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key = 0; + psa_status_t st; + + st = psa_attach_key(&attrs, (const uint8_t *)"label", 5, &key); + if (expect_status("psa_attach_key before init", st, + PSA_ERROR_BAD_STATE) != 0) { + return 1; + } + return 0; +} + +/* Case 2: NOT_SUPPORTED stubs after init */ +static int test_not_supported_stubs(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key = 0; + psa_hash_operation_t hash_op = PSA_HASH_OPERATION_INIT; + uint8_t hash_state[256]; + size_t hash_state_len = 0; + psa_sign_hash_interruptible_operation_t sign_op = + PSA_SIGN_HASH_INTERRUPTIBLE_OPERATION_INIT; + psa_verify_hash_interruptible_operation_t verify_op = + PSA_VERIFY_HASH_INTERRUPTIBLE_OPERATION_INIT; + psa_key_agreement_iop_t ka_op = PSA_KEY_AGREEMENT_IOP_INIT; + psa_generate_key_iop_t gk_op = PSA_GENERATE_KEY_IOP_INIT; + psa_export_public_key_iop_t epk_op = PSA_EXPORT_PUBLIC_KEY_IOP_INIT; + uint8_t dummy_hash[32] = {0}; + uint8_t dummy_sig[64] = {0}; + uint8_t dummy_peer[65] = {0}; + uint8_t dummy_sig_out[64]; + size_t dummy_sig_len = 0; + psa_status_t st; + + st = psa_attach_key(&attrs, (const uint8_t *)"label", 5, &key); + if (expect_status("psa_attach_key NOT_SUPPORTED", st, + PSA_ERROR_NOT_SUPPORTED) != 0) { + return 1; + } + + st = psa_hash_suspend(&hash_op, hash_state, sizeof(hash_state), + &hash_state_len); + if (expect_status("psa_hash_suspend NOT_SUPPORTED", st, + PSA_ERROR_NOT_SUPPORTED) != 0) { + return 1; + } + + st = psa_hash_resume(&hash_op, hash_state, 0); + if (expect_status("psa_hash_resume NOT_SUPPORTED", st, + PSA_ERROR_NOT_SUPPORTED) != 0) { + return 1; + } + + st = psa_sign_hash_start(&sign_op, 0, PSA_ALG_ECDSA(PSA_ALG_SHA_256), + dummy_hash, sizeof(dummy_hash)); + if (expect_status("psa_sign_hash_start NOT_SUPPORTED", st, + PSA_ERROR_NOT_SUPPORTED) != 0) { + return 1; + } + + st = psa_sign_hash_complete(&sign_op, dummy_sig_out, sizeof(dummy_sig_out), + &dummy_sig_len); + if (expect_status("psa_sign_hash_complete NOT_SUPPORTED", st, + PSA_ERROR_NOT_SUPPORTED) != 0) { + return 1; + } + + st = psa_verify_hash_start(&verify_op, 0, PSA_ALG_ECDSA(PSA_ALG_SHA_256), + dummy_hash, sizeof(dummy_hash), + dummy_sig, sizeof(dummy_sig)); + if (expect_status("psa_verify_hash_start NOT_SUPPORTED", st, + PSA_ERROR_NOT_SUPPORTED) != 0) { + return 1; + } + + st = psa_verify_hash_complete(&verify_op); + if (expect_status("psa_verify_hash_complete NOT_SUPPORTED", st, + PSA_ERROR_NOT_SUPPORTED) != 0) { + return 1; + } + + st = psa_key_agreement_iop_setup(&ka_op, 0, dummy_peer, sizeof(dummy_peer), + PSA_ALG_ECDH, &attrs); + if (expect_status("psa_key_agreement_iop_setup NOT_SUPPORTED", st, + PSA_ERROR_NOT_SUPPORTED) != 0) { + return 1; + } + + st = psa_generate_key_iop_setup(&gk_op, &attrs); + if (expect_status("psa_generate_key_iop_setup NOT_SUPPORTED", st, + PSA_ERROR_NOT_SUPPORTED) != 0) { + return 1; + } + + st = psa_export_public_key_iop_setup(&epk_op, 0); + if (expect_status("psa_export_public_key_iop_setup NOT_SUPPORTED", st, + PSA_ERROR_NOT_SUPPORTED) != 0) { + return 1; + } + + return 0; +} + +/* Case 3: interruptible abort stubs return PSA_SUCCESS; get_num_ops == 0; + * set/get max_ops round-trip */ +static int test_interruptible_abort_stubs(void) +{ + psa_sign_hash_interruptible_operation_t sign_op = + PSA_SIGN_HASH_INTERRUPTIBLE_OPERATION_INIT; + psa_verify_hash_interruptible_operation_t verify_op = + PSA_VERIFY_HASH_INTERRUPTIBLE_OPERATION_INIT; + psa_generate_key_iop_t gk_op = PSA_GENERATE_KEY_IOP_INIT; + psa_status_t st; + uint32_t n; + + st = psa_sign_hash_abort(&sign_op); + if (expect_status("psa_sign_hash_abort", st, PSA_SUCCESS) != 0) + return 1; + + st = psa_verify_hash_abort(&verify_op); + if (expect_status("psa_verify_hash_abort", st, PSA_SUCCESS) != 0) + return 1; + + st = psa_generate_key_iop_abort(&gk_op); + if (expect_status("psa_generate_key_iop_abort", st, PSA_SUCCESS) != 0) + return 1; + + n = psa_sign_hash_get_num_ops(&sign_op); + if (n != 0) { + printf("FAIL psa_sign_hash_get_num_ops=%u expected=0\n", (unsigned)n); + return 1; + } + + n = psa_verify_hash_get_num_ops(&verify_op); + if (n != 0) { + printf("FAIL psa_verify_hash_get_num_ops=%u expected=0\n", + (unsigned)n); + return 1; + } + + n = psa_generate_key_iop_get_num_ops(&gk_op); + if (n != 0) { + printf("FAIL psa_generate_key_iop_get_num_ops=%u expected=0\n", + (unsigned)n); + return 1; + } + + psa_interruptible_set_max_ops(123); + n = psa_interruptible_get_max_ops(); + if (n != 123) { + printf("FAIL psa_interruptible_get_max_ops=%u expected=123\n", + (unsigned)n); + return 1; + } + + return 0; +} + +/* Case 4: psa_generate_key_custom */ +static int test_generate_key_custom(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_custom_key_parameters_t custom_default = PSA_CUSTOM_KEY_PARAMETERS_INIT; + psa_custom_key_parameters_t custom_flags1 = PSA_CUSTOM_KEY_PARAMETERS_INIT; + psa_key_id_t key = 0; + psa_status_t st; + + /* flags=1 — INVALID_ARGUMENT */ + custom_flags1.flags = 1; + + /* Sub-case A: default custom (flags=0), custom_data_length=0 — success */ + psa_set_key_type(&attrs, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attrs, 128); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&attrs, PSA_ALG_GCM); + + st = psa_generate_key_custom(&attrs, &custom_default, NULL, 0, &key); + if (expect_status("psa_generate_key_custom default", st, PSA_SUCCESS) != 0) + return 1; + (void)psa_destroy_key(key); + key = 0; + + /* Sub-case B: custom=NULL — INVALID_ARGUMENT */ + st = psa_generate_key_custom(&attrs, NULL, NULL, 0, &key); + if (expect_status("psa_generate_key_custom NULL custom", st, + PSA_ERROR_INVALID_ARGUMENT) != 0) { + return 1; + } + + /* Sub-case C: flags=1 — INVALID_ARGUMENT */ + st = psa_generate_key_custom(&attrs, &custom_flags1, NULL, 0, &key); + if (expect_status("psa_generate_key_custom flags=1", st, + PSA_ERROR_INVALID_ARGUMENT) != 0) { + return 1; + } + + return 0; +} + +/* Case 5: psa_key_derivation_output_key_custom via HKDF-SHA256 */ +static int test_key_derivation_output_key_custom(void) +{ + static const uint8_t secret[] = "hkdf-secret-input"; + static const uint8_t salt[] = "hkdf-salt"; + static const uint8_t info[] = "hkdf-info"; + psa_key_derivation_operation_t op = PSA_KEY_DERIVATION_OPERATION_INIT; + psa_custom_key_parameters_t custom = PSA_CUSTOM_KEY_PARAMETERS_INIT; + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t derived_key = 0; + psa_status_t st; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attrs, 128); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&attrs, PSA_ALG_GCM); + + st = psa_key_derivation_setup(&op, PSA_ALG_HKDF(PSA_ALG_SHA_256)); + if (expect_status("kdf_output_key_custom: setup", st, PSA_SUCCESS) != 0) + goto fail; + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_SALT, + salt, sizeof(salt) - 1u); + if (expect_status("kdf_output_key_custom: salt", st, PSA_SUCCESS) != 0) + goto fail; + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_SECRET, + secret, sizeof(secret) - 1u); + if (expect_status("kdf_output_key_custom: secret", st, PSA_SUCCESS) != 0) + goto fail; + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_INFO, + info, sizeof(info) - 1u); + if (expect_status("kdf_output_key_custom: info", st, PSA_SUCCESS) != 0) + goto fail; + + st = psa_key_derivation_output_key_custom(&attrs, &op, &custom, NULL, 0, + &derived_key); + if (expect_status("psa_key_derivation_output_key_custom", st, + PSA_SUCCESS) != 0) { + goto fail; + } + + (void)psa_key_derivation_abort(&op); + (void)psa_destroy_key(derived_key); + return 0; + +fail: + (void)psa_key_derivation_abort(&op); + return 1; +} + +/* Case 6: psa_check_key_usage */ +static int test_check_key_usage(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key = 0; + psa_status_t st; + + /* AES key with only ENCRYPT usage, alg GCM */ + psa_set_key_type(&attrs, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attrs, 128); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_ENCRYPT); + psa_set_key_algorithm(&attrs, PSA_ALG_GCM); + + st = psa_generate_key(&attrs, &key); + if (expect_status("check_key_usage: generate", st, PSA_SUCCESS) != 0) + return 1; + + /* Matching alg + usage — SUCCESS */ + st = psa_check_key_usage(key, PSA_ALG_GCM, PSA_KEY_USAGE_ENCRYPT); + if (expect_status("check_key_usage ENCRYPT+GCM", st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + /* Matching alg but wrong usage — NOT_PERMITTED */ + st = psa_check_key_usage(key, PSA_ALG_GCM, PSA_KEY_USAGE_DECRYPT); + if (expect_status("check_key_usage DECRYPT+GCM", st, + PSA_ERROR_NOT_PERMITTED) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + /* Correct usage but wrong alg — NOT_PERMITTED */ + st = psa_check_key_usage(key, PSA_ALG_CCM, PSA_KEY_USAGE_ENCRYPT); + if (expect_status("check_key_usage ENCRYPT+CCM", st, + PSA_ERROR_NOT_PERMITTED) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + (void)psa_destroy_key(key); + + /* Invalid key handle — must not succeed */ + st = psa_check_key_usage((psa_key_id_t)0x7fffffff, PSA_ALG_GCM, + PSA_KEY_USAGE_ENCRYPT); + if (st == PSA_SUCCESS) { + printf("FAIL check_key_usage invalid handle: expected error, got PSA_SUCCESS\n"); + return 1; + } + printf("INFO check_key_usage invalid handle: got status=%d (expected INVALID_HANDLE=%d)\n", + (int)st, (int)PSA_ERROR_INVALID_HANDLE); + + return 0; +} + +/* Case 7: ECDSA verify-equivalence (PSA 1.4 policy) */ +static int test_ecdsa_verify_equivalence(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key = 0; + static const uint8_t message[] = "ecdsa-policy-test"; + uint8_t sig[PSA_SIGNATURE_MAX_SIZE]; + size_t sig_len = 0; + psa_status_t st; + + /* Key policy: ECDSA(SHA-256), SIGN_MESSAGE | VERIFY_MESSAGE */ + psa_set_key_type(&attrs, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(&attrs, 256); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_SIGN_MESSAGE | + PSA_KEY_USAGE_VERIFY_MESSAGE); + psa_set_key_algorithm(&attrs, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); + + st = psa_generate_key(&attrs, &key); + if (expect_status("ecdsa_equiv: generate", st, PSA_SUCCESS) != 0) + return 1; + + /* Sign with the policy algorithm */ + st = psa_sign_message(key, PSA_ALG_ECDSA(PSA_ALG_SHA_256), + message, sizeof(message) - 1u, + sig, sizeof(sig), &sig_len); + if (expect_status("ecdsa_equiv: sign_message ECDSA", st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + /* Verify with DETERMINISTIC_ECDSA — must succeed (policy equivalence) */ + st = psa_verify_message(key, PSA_ALG_DETERMINISTIC_ECDSA(PSA_ALG_SHA_256), + message, sizeof(message) - 1u, + sig, sig_len); + if (st != PSA_SUCCESS) { + printf("FAIL ecdsa_equiv: verify_message DET_ECDSA status=%d" + " (expected PSA_SUCCESS=%d) — library may not implement" + " verify policy equivalence\n", (int)st, (int)PSA_SUCCESS); + /* Report but treat as deviation; do not hard-fail the suite */ + } else { + printf("INFO ecdsa_equiv: verify_message with DET_ECDSA: PSA_SUCCESS\n"); + } + + /* Sign with DETERMINISTIC_ECDSA — must fail (policy is ECDSA, not DET) */ + st = psa_sign_message(key, PSA_ALG_DETERMINISTIC_ECDSA(PSA_ALG_SHA_256), + message, sizeof(message) - 1u, + sig, sizeof(sig), &sig_len); + if (st == PSA_SUCCESS) { + printf("FAIL ecdsa_equiv: sign_message DET_ECDSA must not succeed" + " (policy is ECDSA)\n"); + (void)psa_destroy_key(key); + return 1; + } + printf("INFO ecdsa_equiv: sign_message DET_ECDSA blocked: status=%d" + " (expected NOT_PERMITTED=%d)\n", + (int)st, (int)PSA_ERROR_NOT_PERMITTED); + + (void)psa_destroy_key(key); + return 0; +} + +/* Case 8: size macro runtime checks */ +static int test_size_macros(void) +{ + int fail = 0; + + if (PSA_SIGNATURE_MAX_SIZE < 4627u) { + printf("FAIL PSA_SIGNATURE_MAX_SIZE=%u < 4627\n", + (unsigned)PSA_SIGNATURE_MAX_SIZE); + fail = 1; + } + + if (PSA_EXPORT_KEY_OUTPUT_SIZE(PSA_KEY_TYPE_ML_DSA_KEY_PAIR, 192) != 32u) { + printf("FAIL PSA_EXPORT_KEY_OUTPUT_SIZE(ML_DSA_KEY_PAIR,192)=%u" + " expected=32\n", + (unsigned)PSA_EXPORT_KEY_OUTPUT_SIZE( + PSA_KEY_TYPE_ML_DSA_KEY_PAIR, 192)); + fail = 1; + } + + if (PSA_EXPORT_KEY_OUTPUT_SIZE(PSA_KEY_TYPE_ML_KEM_KEY_PAIR, 768) != 64u) { + printf("FAIL PSA_EXPORT_KEY_OUTPUT_SIZE(ML_KEM_KEY_PAIR,768)=%u" + " expected=64\n", + (unsigned)PSA_EXPORT_KEY_OUTPUT_SIZE( + PSA_KEY_TYPE_ML_KEM_KEY_PAIR, 768)); + fail = 1; + } + + if (PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE(PSA_KEY_TYPE_ML_DSA_KEY_PAIR, 256) != 2592u) { + printf("FAIL PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE(ML_DSA_KEY_PAIR,256)=%u" + " expected=2592\n", + (unsigned)PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE( + PSA_KEY_TYPE_ML_DSA_KEY_PAIR, 256)); + fail = 1; + } + + if (PSA_SIGN_OUTPUT_SIZE(PSA_KEY_TYPE_ML_DSA_KEY_PAIR, 192, PSA_ALG_ML_DSA) != 3309u) { + printf("FAIL PSA_SIGN_OUTPUT_SIZE(ML_DSA_KEY_PAIR,192,ML_DSA)=%u" + " expected=3309\n", + (unsigned)PSA_SIGN_OUTPUT_SIZE( + PSA_KEY_TYPE_ML_DSA_KEY_PAIR, 192, PSA_ALG_ML_DSA)); + fail = 1; + } + + if (PSA_ENCAPSULATE_CIPHERTEXT_SIZE(PSA_KEY_TYPE_ML_KEM_KEY_PAIR, 1024, PSA_ALG_ML_KEM) != 1568u) { + printf("FAIL PSA_ENCAPSULATE_CIPHERTEXT_SIZE(ML_KEM_KEY_PAIR,1024,ML_KEM)=%u" + " expected=1568\n", + (unsigned)PSA_ENCAPSULATE_CIPHERTEXT_SIZE( + PSA_KEY_TYPE_ML_KEM_KEY_PAIR, 1024, PSA_ALG_ML_KEM)); + fail = 1; + } + + if (PSA_ENCAPSULATE_CIPHERTEXT_MAX_SIZE < 1568u) { + printf("FAIL PSA_ENCAPSULATE_CIPHERTEXT_MAX_SIZE=%u < 1568\n", + (unsigned)PSA_ENCAPSULATE_CIPHERTEXT_MAX_SIZE); + fail = 1; + } + + if (PSA_AEAD_NONCE_LENGTH(PSA_KEY_TYPE_XCHACHA20, + PSA_ALG_XCHACHA20_POLY1305) != 24u) { + printf("FAIL PSA_AEAD_NONCE_LENGTH(XCHACHA20,XCHACHA20_POLY1305)=%u" + " expected=24\n", + (unsigned)PSA_AEAD_NONCE_LENGTH(PSA_KEY_TYPE_XCHACHA20, + PSA_ALG_XCHACHA20_POLY1305)); + fail = 1; + } + + return fail; +} + +int main(void) +{ + psa_status_t st; + + /* Case 1: pre-init BAD_STATE (before psa_crypto_init) */ + if (test_pre_init_bad_state() != 0) + return 1; + + st = psa_crypto_init(); + if (st != PSA_SUCCESS) { + printf("FAIL psa_crypto_init status=%d\n", (int)st); + return 1; + } + + /* Case 2: NOT_SUPPORTED stubs */ + if (test_not_supported_stubs() != 0) + return 1; + + /* Case 3: interruptible abort / get_num_ops / set_max_ops */ + if (test_interruptible_abort_stubs() != 0) + return 1; + + /* Case 4: psa_generate_key_custom */ + if (test_generate_key_custom() != 0) + return 1; + + /* Case 5: psa_key_derivation_output_key_custom */ + if (test_key_derivation_output_key_custom() != 0) + return 1; + + /* Case 6: psa_check_key_usage */ + if (test_check_key_usage() != 0) + return 1; + + /* Case 7: ECDSA verify-equivalence */ + if (test_ecdsa_verify_equivalence() != 0) + return 1; + + /* Case 8: size macro runtime checks */ + if (test_size_macros() != 0) + return 1; + + printf("PSA 1.4 misc test: OK\n"); + return 0; +} diff --git a/test/psa_server/psa_ascon_xchacha_test.c b/test/psa_server/psa_ascon_xchacha_test.c new file mode 100644 index 0000000..10c065a --- /dev/null +++ b/test/psa_server/psa_ascon_xchacha_test.c @@ -0,0 +1,662 @@ +/* psa_ascon_xchacha_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 + */ + +/* + * Coverage test for: + * - Ascon-Hash256 (PSA_ALG_ASCON_HASH256) + * - Ascon-AEAD128 (PSA_ALG_ASCON_AEAD128 / PSA_KEY_TYPE_ASCON) + * - XChaCha20-Poly1305 (PSA_ALG_XCHACHA20_POLY1305 / PSA_KEY_TYPE_XCHACHA20) + * + * All three algorithms are compiled-in conditionally (HAVE_ASCON / HAVE_XCHACHA). + * When the linked library was not built with the relevant feature, import / hash + * setup will return PSA_ERROR_NOT_SUPPORTED and the individual test sub-function + * prints "SKIP" and returns 0 so the overall test still exits 0. + * + * TEST VECTOR PROVENANCE + * ---------------------- + * Ascon-Hash256 KATs: + * Source: wolfSSL wolfcrypt/test/test.c :: ascon_hash256_test() + * and wolfssl/tests/api/test_ascon_kats.h + * Origin: https://github.com/ascon/ascon-c + * crypto_hash/asconhash256/LWC_HASH_KAT_256.txt (NIST SP 800-232) + * Message: byte stream 0x00 0x01 ... 0x(N-1) of length N. + * KATs used: + * hash_kat[0]: N=0 (empty message) + * hash_kat[2]: N=2 (message {0x00, 0x01}) + * + * Ascon-AEAD128 KATs: + * Source: wolfSSL wolfssl/tests/api/test_ascon_kats.h + * (verbatim from ascon-c tests/api/ascon.c) + * Origin: crypto_aead/asconaead128/LWC_AEAD_KAT_128_128.txt (NIST SP 800-232) + * KAT used (index 33 in the reference file, first entry with PT="00"): + * Key = 000102030405060708090A0B0C0D0E0F + * Nonce = 000102030405060708090A0B0C0D0E0F + * PT = 00 (1 byte, value 0x00) + * AD = "" (empty) + * CT||T = E79F58F1F541FC51B5D438F8E1DD03F147 + * (1 byte CT = 0xE7, then 16-byte tag) + * + * XChaCha20-Poly1305 KAT: + * Source: wolfSSL wolfcrypt/test/test.c :: xchacha20_poly1305_oneshot_test() + * Origin: draft-irtf-cfrg-xchacha §A.3 + * Plaintext: "Ladies and Gentlemen of the class of '99: ..." + * Key, IV, AAD, Ciphertext, Tag reproduced verbatim from wolfSSL source. + */ + +#include "psa_api_test_user_settings.h" + +#ifndef WOLFSSL_USER_SETTINGS +#define WOLFSSL_USER_SETTINGS +#endif + +#include +#include + +#include +#include +#include + +#include + +/* ------------------------------------------------------------------------- + * Helper utilities + * ---------------------------------------------------------------------- */ + +static int expect_status(const char *label, psa_status_t status, + psa_status_t expected) +{ + if (status != expected) { + printf("FAIL %s: status=%d expected=%d\n", label, + (int)status, (int)expected); + return 1; + } + return 0; +} + +static int expect_buf_eq(const char *label, const uint8_t *a, + const uint8_t *b, size_t len) +{ + if (memcmp(a, b, len) != 0) { + printf("FAIL %s: buffer mismatch\n", label); + return 1; + } + return 0; +} + +/* ========================================================================= + * Test 1 — Ascon-Hash256 + * ====================================================================== */ + +/* + * KATs from wolfSSL wolfcrypt/test/test.c hash_output[] table + * (NIST SP 800-232 / ascon-c LWC_HASH_KAT_256.txt) + * Message is the byte stream { 0x00, 0x01, ..., 0x(N-1) }. + */ + +/* N=0: hash of empty message */ +static const uint8_t ascon_hash256_kat0_msg[] = { 0 }; /* unused — length 0 */ +static const uint8_t ascon_hash256_kat0_digest[32] = { + 0x0B, 0x3B, 0xE5, 0x85, 0x0F, 0x2F, 0x6B, 0x98, + 0xCA, 0xF2, 0x9F, 0x8F, 0xDE, 0xA8, 0x9B, 0x64, + 0xA1, 0xFA, 0x70, 0xAA, 0x24, 0x9B, 0x8F, 0x83, + 0x9B, 0xD5, 0x3B, 0xAA, 0x30, 0x4D, 0x92, 0xB2 +}; + +/* N=2: hash of { 0x00, 0x01 } */ +static const uint8_t ascon_hash256_kat2_msg[2] = { 0x00, 0x01 }; +static const uint8_t ascon_hash256_kat2_digest[32] = { + 0x61, 0x15, 0xE7, 0xC9, 0xC4, 0x08, 0x1C, 0x27, + 0x97, 0xFC, 0x8F, 0xE1, 0xBC, 0x57, 0xA8, 0x36, + 0xAF, 0xA1, 0xC5, 0x38, 0x1E, 0x55, 0x6D, 0xD5, + 0x83, 0x86, 0x0C, 0xA2, 0xDF, 0xB4, 0x8D, 0xD2 +}; + +static int test_ascon_hash256(void) +{ + uint8_t out[32]; + size_t out_len = 0; + psa_hash_operation_t op; + psa_status_t st; + + /* --- Runtime size check -------------------------------------------- */ + if (PSA_HASH_LENGTH(PSA_ALG_ASCON_HASH256) != 32u) { + printf("FAIL ascon_hash256 PSA_HASH_LENGTH: got %u expected 32\n", + (unsigned)PSA_HASH_LENGTH(PSA_ALG_ASCON_HASH256)); + return 1; + } + + /* --- KAT 0: one-shot, empty message --------------------------------- */ + memset(out, 0, sizeof(out)); + out_len = 0; + st = psa_hash_compute(PSA_ALG_ASCON_HASH256, + ascon_hash256_kat0_msg, 0, + out, sizeof(out), &out_len); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP ascon_hash256 (not supported by this build)\n"); + return 0; + } + if (expect_status("ascon_hash256 KAT0 compute", st, PSA_SUCCESS) != 0) + return 1; + if (out_len != 32u) { + printf("FAIL ascon_hash256 KAT0 length: got %zu expected 32\n", + out_len); + return 1; + } + if (expect_buf_eq("ascon_hash256 KAT0 digest", + out, ascon_hash256_kat0_digest, 32) != 0) + return 1; + + /* --- KAT 2: one-shot, 2-byte message -------------------------------- */ + memset(out, 0, sizeof(out)); + out_len = 0; + st = psa_hash_compute(PSA_ALG_ASCON_HASH256, + ascon_hash256_kat2_msg, sizeof(ascon_hash256_kat2_msg), + out, sizeof(out), &out_len); + if (expect_status("ascon_hash256 KAT2 compute", st, PSA_SUCCESS) != 0) + return 1; + if (expect_buf_eq("ascon_hash256 KAT2 digest", + out, ascon_hash256_kat2_digest, 32) != 0) + return 1; + + /* --- Multipart equals one-shot (KAT2, split into 2 chunks: 1+1) ---- */ + memset(out, 0, sizeof(out)); + out_len = 0; + op = psa_hash_operation_init(); + st = psa_hash_setup(&op, PSA_ALG_ASCON_HASH256); + if (expect_status("ascon_hash256 multipart setup", st, PSA_SUCCESS) != 0) + return 1; + st = psa_hash_update(&op, ascon_hash256_kat2_msg, 1); + if (expect_status("ascon_hash256 multipart update1", st, PSA_SUCCESS) != 0) { + (void)psa_hash_abort(&op); + return 1; + } + st = psa_hash_update(&op, ascon_hash256_kat2_msg + 1, 1); + if (expect_status("ascon_hash256 multipart update2", st, PSA_SUCCESS) != 0) { + (void)psa_hash_abort(&op); + return 1; + } + st = psa_hash_finish(&op, out, sizeof(out), &out_len); + if (expect_status("ascon_hash256 multipart finish", st, PSA_SUCCESS) != 0) + return 1; + if (out_len != 32u) { + printf("FAIL ascon_hash256 multipart length: got %zu expected 32\n", + out_len); + return 1; + } + if (expect_buf_eq("ascon_hash256 multipart digest", + out, ascon_hash256_kat2_digest, 32) != 0) + return 1; + + /* --- psa_hash_compare ------------------------------------------------ */ + st = psa_hash_compare(PSA_ALG_ASCON_HASH256, + ascon_hash256_kat2_msg, + sizeof(ascon_hash256_kat2_msg), + ascon_hash256_kat2_digest, 32); + if (expect_status("ascon_hash256 hash_compare", st, PSA_SUCCESS) != 0) + return 1; + + printf("ascon_hash256: OK\n"); + return 0; +} + +/* ========================================================================= + * Test 2 — Ascon-AEAD128 + * ====================================================================== */ + +/* + * KAT from wolfSSL tests/api/test_ascon_kats.h + * (NIST SP 800-232 / ascon-c LWC_AEAD_KAT_128_128.txt, first PT="00" entry) + * + * Key = 000102030405060708090A0B0C0D0E0F + * Nonce = 000102030405060708090A0B0C0D0E0F + * PT = 00 (1 byte) + * AD = "" (empty) + * CT = E7 (1 byte) + * Tag = 9F58F1F541FC51B5D438F8E1DD03F1 47 (16 bytes) + * (full CT||Tag hex: E79F58F1F541FC51B5D438F8E1DD03F147) + */ +static const uint8_t ascon_aead128_key[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F +}; +static const uint8_t ascon_aead128_nonce[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F +}; +static const uint8_t ascon_aead128_pt[1] = { 0x00 }; +/* CT (1 byte) concatenated with 16-byte tag */ +static const uint8_t ascon_aead128_ct_tag[17] = { + 0xE7, /* ciphertext byte */ + 0x9F, 0x58, 0xF1, 0xF5, 0x41, 0xFC, 0x51, 0xB5, + 0xD4, 0x38, 0xF8, 0xE1, 0xDD, 0x03, 0xF1, 0x47 /* 16-byte tag */ +}; + +static int test_ascon_aead128(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key_id = PSA_KEY_ID_NULL; + /* output buffer: plaintext length (1) + tag (16) = 17 bytes max */ + uint8_t enc_out[17]; + uint8_t dec_out[1]; + size_t enc_out_len = 0; + size_t dec_out_len = 0; + psa_aead_operation_t setup_op = PSA_AEAD_OPERATION_INIT; + psa_status_t st; + + /* Import the 128-bit Ascon key */ + psa_set_key_type(&attrs, PSA_KEY_TYPE_ASCON); + psa_set_key_bits(&attrs, 128u); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&attrs, PSA_ALG_ASCON_AEAD128); + + st = psa_import_key(&attrs, ascon_aead128_key, sizeof(ascon_aead128_key), + &key_id); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP ascon_aead128 (not supported by this build)\n"); + return 0; + } + if (expect_status("ascon_aead128 import_key", st, PSA_SUCCESS) != 0) + return 1; + + /* --- Encrypt KAT ----------------------------------------------------- */ + memset(enc_out, 0, sizeof(enc_out)); + enc_out_len = 0; + st = psa_aead_encrypt(key_id, PSA_ALG_ASCON_AEAD128, + ascon_aead128_nonce, sizeof(ascon_aead128_nonce), + NULL, 0, /* no AD */ + ascon_aead128_pt, sizeof(ascon_aead128_pt), + enc_out, sizeof(enc_out), &enc_out_len); + if (expect_status("ascon_aead128 encrypt KAT", st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + if (enc_out_len != sizeof(ascon_aead128_ct_tag)) { + printf("FAIL ascon_aead128 encrypt length: got %zu expected %zu\n", + enc_out_len, sizeof(ascon_aead128_ct_tag)); + (void)psa_destroy_key(key_id); + return 1; + } + if (expect_buf_eq("ascon_aead128 encrypt ct||tag", + enc_out, ascon_aead128_ct_tag, + sizeof(ascon_aead128_ct_tag)) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + /* --- Decrypt roundtrip ----------------------------------------------- */ + memset(dec_out, 0, sizeof(dec_out)); + dec_out_len = 0; + st = psa_aead_decrypt(key_id, PSA_ALG_ASCON_AEAD128, + ascon_aead128_nonce, sizeof(ascon_aead128_nonce), + NULL, 0, + ascon_aead128_ct_tag, sizeof(ascon_aead128_ct_tag), + dec_out, sizeof(dec_out), &dec_out_len); + if (expect_status("ascon_aead128 decrypt roundtrip", st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + if (dec_out_len != sizeof(ascon_aead128_pt)) { + printf("FAIL ascon_aead128 decrypt length: got %zu expected %zu\n", + dec_out_len, sizeof(ascon_aead128_pt)); + (void)psa_destroy_key(key_id); + return 1; + } + if (expect_buf_eq("ascon_aead128 decrypt plaintext", + dec_out, ascon_aead128_pt, + sizeof(ascon_aead128_pt)) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + /* --- Tampered tag must be rejected ----------------------------------- + * PSA 1.4 spec: psa_aead_decrypt must return PSA_ERROR_INVALID_SIGNATURE + * on authentication failure (ASCON_AUTH_E from wolfCrypt). */ + { + uint8_t tampered[17]; + memcpy(tampered, ascon_aead128_ct_tag, sizeof(tampered)); + tampered[16] ^= 0xFF; /* flip last byte of tag */ + memset(dec_out, 0, sizeof(dec_out)); + dec_out_len = 0; + st = psa_aead_decrypt(key_id, PSA_ALG_ASCON_AEAD128, + ascon_aead128_nonce, sizeof(ascon_aead128_nonce), + NULL, 0, + tampered, sizeof(tampered), + dec_out, sizeof(dec_out), &dec_out_len); + if (st != PSA_ERROR_INVALID_SIGNATURE) { + printf("FAIL ascon_aead128 tampered tag: got status=%d, " + "expected PSA_ERROR_INVALID_SIGNATURE (%d)\n", + (int)st, (int)PSA_ERROR_INVALID_SIGNATURE); + (void)psa_destroy_key(key_id); + return 1; + } + } + + /* --- Wrong nonce length (12 bytes) must return INVALID_ARGUMENT ------ */ + { + static const uint8_t short_nonce[12] = { 0 }; + st = psa_aead_encrypt(key_id, PSA_ALG_ASCON_AEAD128, + short_nonce, sizeof(short_nonce), + NULL, 0, + ascon_aead128_pt, sizeof(ascon_aead128_pt), + enc_out, sizeof(enc_out), &enc_out_len); + if (expect_status("ascon_aead128 wrong nonce length", + st, PSA_ERROR_INVALID_ARGUMENT) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + } + + /* --- Multipart setup must return PSA_ERROR_NOT_SUPPORTED ------------- */ + /* This is always true regardless of HAVE_ASCON (see psa_aead.c:268-271) */ + st = psa_aead_encrypt_setup(&setup_op, key_id, PSA_ALG_ASCON_AEAD128); + if (expect_status("ascon_aead128 multipart setup", + st, PSA_ERROR_NOT_SUPPORTED) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + (void)psa_destroy_key(key_id); + printf("ascon_aead128: OK\n"); + return 0; +} + +/* ========================================================================= + * Test 3 — XChaCha20-Poly1305 + * ====================================================================== */ + +/* + * KAT from wolfSSL wolfcrypt/test/test.c :: xchacha20_poly1305_oneshot_test() + * Origin: draft-irtf-cfrg-xchacha §A.3 + * Plaintext: "Ladies and Gentlemen of the class of '99: If I could offer + * you only one tip for the future, sunscreen would be it." + */ +static const uint8_t xchacha_pt[] = { + 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, /* Ladies a */ + 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, /* nd Gentl */ + 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, /* emen of */ + 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, /* the clas */ + 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, /* s of '99 */ + 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, /* : If I c */ + 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, /* ould off */ + 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, /* er you o */ + 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, /* nly one */ + 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20, /* tip for */ + 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, /* the futu */ + 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73, /* re, suns */ + 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, /* creen wo */ + 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, /* uld be i */ + 0x74, 0x2e /* t. */ +}; +#define XCHACHA_PT_LEN (sizeof(xchacha_pt)) /* 114 bytes */ + +static const uint8_t xchacha_aad[] = { + 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7 +}; + +static const uint8_t xchacha_key[32] = { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f +}; + +/* 24-byte XChaCha nonce */ +static const uint8_t xchacha_nonce[24] = { + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57 +}; + +/* reference ciphertext (114 bytes) */ +static const uint8_t xchacha_ct[114] = { + 0xbd, 0x6d, 0x17, 0x9d, 0x3e, 0x83, 0xd4, 0x3b, + 0x95, 0x76, 0x57, 0x94, 0x93, 0xc0, 0xe9, 0x39, + 0x57, 0x2a, 0x17, 0x00, 0x25, 0x2b, 0xfa, 0xcc, + 0xbe, 0xd2, 0x90, 0x2c, 0x21, 0x39, 0x6c, 0xbb, + 0x73, 0x1c, 0x7f, 0x1b, 0x0b, 0x4a, 0xa6, 0x44, + 0x0b, 0xf3, 0xa8, 0x2f, 0x4e, 0xda, 0x7e, 0x39, + 0xae, 0x64, 0xc6, 0x70, 0x8c, 0x54, 0xc2, 0x16, + 0xcb, 0x96, 0xb7, 0x2e, 0x12, 0x13, 0xb4, 0x52, + 0x2f, 0x8c, 0x9b, 0xa4, 0x0d, 0xb5, 0xd9, 0x45, + 0xb1, 0x1b, 0x69, 0xb9, 0x82, 0xc1, 0xbb, 0x9e, + 0x3f, 0x3f, 0xac, 0x2b, 0xc3, 0x69, 0x48, 0x8f, + 0x76, 0xb2, 0x38, 0x35, 0x65, 0xd3, 0xff, 0xf9, + 0x21, 0xf9, 0x66, 0x4c, 0x97, 0x63, 0x7d, 0xa9, + 0x76, 0x88, 0x12, 0xf6, 0x15, 0xc6, 0x8b, 0x13, + 0xb5, 0x2e +}; + +/* reference authentication tag (16 bytes) */ +static const uint8_t xchacha_tag[16] = { + 0xc0, 0x87, 0x59, 0x24, 0xc1, 0xc7, 0x98, 0x79, + 0x47, 0xde, 0xaf, 0xd8, 0x78, 0x0a, 0xcf, 0x49 +}; + +static int test_xchacha20_poly1305(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key_id = PSA_KEY_ID_NULL; + /* encrypt output: ciphertext (114) + tag (16) = 130 bytes */ + uint8_t enc_out[130]; + uint8_t dec_out[114]; + size_t enc_out_len = 0; + size_t dec_out_len = 0; + psa_aead_operation_t setup_op = PSA_AEAD_OPERATION_INIT; + psa_status_t st; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_XCHACHA20); + psa_set_key_bits(&attrs, 256u); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&attrs, PSA_ALG_XCHACHA20_POLY1305); + + st = psa_import_key(&attrs, xchacha_key, sizeof(xchacha_key), &key_id); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP xchacha20_poly1305 (not supported by this build)\n"); + return 0; + } + if (expect_status("xchacha20_poly1305 import_key", st, PSA_SUCCESS) != 0) + return 1; + + /* --- Encrypt KAT ----------------------------------------------------- */ + memset(enc_out, 0, sizeof(enc_out)); + enc_out_len = 0; + st = psa_aead_encrypt(key_id, PSA_ALG_XCHACHA20_POLY1305, + xchacha_nonce, sizeof(xchacha_nonce), + xchacha_aad, sizeof(xchacha_aad), + xchacha_pt, sizeof(xchacha_pt), + enc_out, sizeof(enc_out), &enc_out_len); + if (expect_status("xchacha20_poly1305 encrypt KAT", st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + if (enc_out_len != sizeof(xchacha_ct) + sizeof(xchacha_tag)) { + printf("FAIL xchacha20_poly1305 encrypt length: got %zu expected %zu\n", + enc_out_len, sizeof(xchacha_ct) + sizeof(xchacha_tag)); + (void)psa_destroy_key(key_id); + return 1; + } + if (expect_buf_eq("xchacha20_poly1305 ciphertext", + enc_out, xchacha_ct, sizeof(xchacha_ct)) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + if (expect_buf_eq("xchacha20_poly1305 tag", + enc_out + sizeof(xchacha_ct), xchacha_tag, + sizeof(xchacha_tag)) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + /* --- Decrypt roundtrip ----------------------------------------------- */ + memset(dec_out, 0, sizeof(dec_out)); + dec_out_len = 0; + st = psa_aead_decrypt(key_id, PSA_ALG_XCHACHA20_POLY1305, + xchacha_nonce, sizeof(xchacha_nonce), + xchacha_aad, sizeof(xchacha_aad), + enc_out, enc_out_len, + dec_out, sizeof(dec_out), &dec_out_len); + if (expect_status("xchacha20_poly1305 decrypt roundtrip", + st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + if (dec_out_len != sizeof(xchacha_pt)) { + printf("FAIL xchacha20_poly1305 decrypt length: got %zu expected %zu\n", + dec_out_len, sizeof(xchacha_pt)); + (void)psa_destroy_key(key_id); + return 1; + } + if (expect_buf_eq("xchacha20_poly1305 decrypt plaintext", + dec_out, xchacha_pt, sizeof(xchacha_pt)) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + /* --- Tampered ciphertext must be rejected ----------------------------- */ + { + uint8_t tampered[130]; + memcpy(tampered, enc_out, enc_out_len); + tampered[0] ^= 0xFF; /* corrupt first ciphertext byte */ + memset(dec_out, 0, sizeof(dec_out)); + dec_out_len = 0; + st = psa_aead_decrypt(key_id, PSA_ALG_XCHACHA20_POLY1305, + xchacha_nonce, sizeof(xchacha_nonce), + xchacha_aad, sizeof(xchacha_aad), + tampered, enc_out_len, + dec_out, sizeof(dec_out), &dec_out_len); + if (expect_status("xchacha20_poly1305 tampered ciphertext", + st, PSA_ERROR_INVALID_SIGNATURE) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + } + + /* --- 12-byte nonce must return INVALID_ARGUMENT ---------------------- */ + { + static const uint8_t short_nonce[12] = { 0 }; + st = psa_aead_encrypt(key_id, PSA_ALG_XCHACHA20_POLY1305, + short_nonce, sizeof(short_nonce), + NULL, 0, + xchacha_pt, sizeof(xchacha_pt), + enc_out, sizeof(enc_out), &enc_out_len); + if (expect_status("xchacha20_poly1305 short nonce", + st, PSA_ERROR_INVALID_ARGUMENT) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + } + + /* --- Multipart setup must return NOT_SUPPORTED ----------------------- */ + st = psa_aead_encrypt_setup(&setup_op, key_id, PSA_ALG_XCHACHA20_POLY1305); + if (expect_status("xchacha20_poly1305 multipart setup", + st, PSA_ERROR_NOT_SUPPORTED) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + (void)psa_destroy_key(key_id); + printf("xchacha20_poly1305: OK\n"); + return 0; +} + +/* ========================================================================= + * Test 4 — Key policy: XCHACHA20 key used with CHACHA20_POLY1305 algorithm + * ====================================================================== */ + +static int test_xchacha_key_policy(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key_id = PSA_KEY_ID_NULL; + uint8_t enc_out[130]; + size_t enc_out_len = 0; + psa_status_t st; + + /* Import an XChaCha20 key bound to the XCHACHA20_POLY1305 algorithm */ + psa_set_key_type(&attrs, PSA_KEY_TYPE_XCHACHA20); + psa_set_key_bits(&attrs, 256u); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_ENCRYPT); + psa_set_key_algorithm(&attrs, PSA_ALG_XCHACHA20_POLY1305); + + st = psa_import_key(&attrs, xchacha_key, sizeof(xchacha_key), &key_id); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP xchacha_key_policy (not supported by this build)\n"); + return 0; + } + if (expect_status("xchacha_key_policy import_key", st, PSA_SUCCESS) != 0) + return 1; + + /* + * Attempt to use the XChaCha20 key with CHACHA20_POLY1305 (wrong + * algorithm). The PSA spec and wolfPSA implementation must reject this; + * either PSA_ERROR_NOT_PERMITTED or PSA_ERROR_INVALID_ARGUMENT is + * acceptable — what matters is that PSA_SUCCESS must not be returned. + */ + st = psa_aead_encrypt(key_id, PSA_ALG_CHACHA20_POLY1305, + xchacha_nonce, 12u, /* ChaCha uses 12-byte nonce */ + NULL, 0, + xchacha_pt, sizeof(xchacha_pt), + enc_out, sizeof(enc_out), &enc_out_len); + if (st == PSA_SUCCESS) { + printf("FAIL xchacha_key_policy: XCHACHA20 key accepted " + "CHACHA20_POLY1305 alg (should have been rejected)\n"); + (void)psa_destroy_key(key_id); + return 1; + } + printf("xchacha_key_policy: correctly rejected with status=%d\n", + (int)st); + + (void)psa_destroy_key(key_id); + printf("xchacha_key_policy: OK\n"); + return 0; +} + +/* ========================================================================= + * main + * ====================================================================== */ + +int main(void) +{ + psa_status_t st; + + st = psa_crypto_init(); + if (st != PSA_SUCCESS) { + printf("FAIL psa_crypto_init: status=%d\n", (int)st); + return 1; + } + + if (test_ascon_hash256() != 0) + return 1; + + if (test_ascon_aead128() != 0) + return 1; + + if (test_xchacha20_poly1305() != 0) + return 1; + + if (test_xchacha_key_policy() != 0) + return 1; + + printf("psa_ascon_xchacha_test: all tests passed\n"); + return 0; +} diff --git a/test/psa_server/psa_key_wrap_test.c b/test/psa_server/psa_key_wrap_test.c new file mode 100644 index 0000000..15e0651 --- /dev/null +++ b/test/psa_server/psa_key_wrap_test.c @@ -0,0 +1,595 @@ +/* psa_key_wrap_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 + */ + +/* + * Coverage test for psa_wrap_key() / psa_unwrap_key() (PSA Crypto API 1.4). + * + * Test cases + * ---------- + * 1. RFC 3394 §4.1 KAT: wrap a 16-byte AES-128 key with a 128-bit KEK and + * verify the exact 24-byte ciphertext from the RFC. + * 2. Unwrap the RFC ciphertext with the same KEK; export and compare to the + * original 16-byte plaintext. + * 3. Roundtrip with an AES-256 target generated via psa_generate_key: wrap, + * unwrap, export, and confirm the 32 bytes are identical. + * 4. KEK without PSA_KEY_USAGE_WRAP → PSA_ERROR_NOT_PERMITTED from + * psa_wrap_key; KEK without PSA_KEY_USAGE_UNWRAP → NOT_PERMITTED from + * psa_unwrap_key. + * 5. Target key without PSA_KEY_USAGE_EXPORT → PSA_ERROR_NOT_PERMITTED from + * psa_wrap_key (psa_export_key rejects it internally). + * 6. PSA_ALG_KWP → PSA_ERROR_NOT_SUPPORTED. + * 7. Corrupted wrapped data (one byte flipped) → PSA_ERROR_INVALID_SIGNATURE. + * 8. Output buffer one byte too small → PSA_ERROR_BUFFER_TOO_SMALL. + */ + +#include "psa_api_test_user_settings.h" + +#ifndef WOLFSSL_USER_SETTINGS +#define WOLFSSL_USER_SETTINGS +#endif + +#include + +#include +#include +#include + +#include + +/* ------------------------------------------------------------------------- + * Helpers + * ---------------------------------------------------------------------- */ + +static int expect_status(const char *label, psa_status_t status, + psa_status_t expected) +{ + if (status != expected) { + printf("FAIL %s: status=%d expected=%d\n", label, + (int)status, (int)expected); + return 1; + } + return 0; +} + +/* Destroy a key only if it is not PSA_KEY_ID_NULL. */ +static void destroy_if_valid(psa_key_id_t key) +{ + if (key != PSA_KEY_ID_NULL) { + (void)psa_destroy_key(key); + } +} + +/* Import an AES key with the given raw bytes. Returns PSA_KEY_ID_NULL on + * failure (and prints a diagnostic). */ +static psa_key_id_t import_aes_key(const char *label, + const uint8_t *raw, size_t raw_len, + psa_key_usage_t usage, + psa_algorithm_t alg) +{ + psa_key_attributes_t attr = psa_key_attributes_init(); + psa_key_id_t kid = PSA_KEY_ID_NULL; + psa_status_t st; + + psa_set_key_type(&attr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attr, raw_len * 8u); + psa_set_key_usage_flags(&attr, usage); + psa_set_key_algorithm(&attr, alg); + + st = psa_import_key(&attr, raw, raw_len, &kid); + if (st != PSA_SUCCESS) { + printf("FAIL %s: psa_import_key status=%d\n", label, (int)st); + return PSA_KEY_ID_NULL; + } + return kid; +} + +/* ------------------------------------------------------------------------- + * RFC 3394 §4.1 known-answer data + * KEK : 000102030405060708090A0B0C0D0E0F (AES-128) + * PT : 00112233445566778899AABBCCDDEEFF (16 bytes) + * CT : 1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5 (24 bytes) + * ---------------------------------------------------------------------- */ + +static const uint8_t kKekRfc3394[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F +}; + +static const uint8_t kPlainRfc3394[16] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF +}; + +static const uint8_t kCipherRfc3394[24] = { + 0x1F, 0xA6, 0x8B, 0x0A, 0x81, 0x12, 0xB4, 0x47, + 0xAE, 0xF3, 0x4B, 0xD8, 0xFB, 0x5A, 0x7B, 0x82, + 0x9D, 0x3E, 0x86, 0x23, 0x71, 0xD2, 0xCF, 0xE5 +}; + +/* ------------------------------------------------------------------------- + * Test 1: RFC 3394 §4.1 known-answer (wrap direction) + * ---------------------------------------------------------------------- */ +static int test_kat_wrap(void) +{ + psa_key_id_t kek = PSA_KEY_ID_NULL; + psa_key_id_t target = PSA_KEY_ID_NULL; + psa_key_attributes_t tattr = psa_key_attributes_init(); + uint8_t wrapped[32]; + size_t wrapped_len = 0; + psa_status_t st; + int ret = 0; + + /* KEK: AES-128 with WRAP|UNWRAP, algorithm PSA_ALG_KW */ + kek = import_aes_key("kat_wrap/kek", kKekRfc3394, sizeof(kKekRfc3394), + PSA_KEY_USAGE_WRAP | PSA_KEY_USAGE_UNWRAP, + PSA_ALG_KW); + if (kek == PSA_KEY_ID_NULL) { ret = 1; goto done; } + + /* Target: AES-128 with EXPORT */ + psa_set_key_type(&tattr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&tattr, 128); + psa_set_key_usage_flags(&tattr, PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&tattr, PSA_ALG_KW); + + st = psa_import_key(&tattr, kPlainRfc3394, sizeof(kPlainRfc3394), &target); + if (expect_status("kat_wrap/import_target", st, PSA_SUCCESS) != 0) { + ret = 1; goto done; + } + + st = psa_wrap_key(kek, PSA_ALG_KW, target, + wrapped, sizeof(wrapped), &wrapped_len); + if (expect_status("kat_wrap/psa_wrap_key", st, PSA_SUCCESS) != 0) { + ret = 1; goto done; + } + + if (wrapped_len != sizeof(kCipherRfc3394)) { + printf("FAIL kat_wrap/length: got %zu expected %zu\n", + wrapped_len, sizeof(kCipherRfc3394)); + ret = 1; goto done; + } + + if (memcmp(wrapped, kCipherRfc3394, sizeof(kCipherRfc3394)) != 0) { + printf("FAIL kat_wrap/ciphertext: KAT mismatch\n"); + ret = 1; goto done; + } + +done: + destroy_if_valid(target); + destroy_if_valid(kek); + return ret; +} + +/* ------------------------------------------------------------------------- + * Test 2: Unwrap the RFC ciphertext, export, compare to plaintext + * ---------------------------------------------------------------------- */ +static int test_kat_unwrap(void) +{ + psa_key_id_t kek = PSA_KEY_ID_NULL; + psa_key_id_t recovered = PSA_KEY_ID_NULL; + psa_key_attributes_t rattr = psa_key_attributes_init(); + uint8_t exported[16]; + size_t exported_len = 0; + psa_status_t st; + int ret = 0; + + kek = import_aes_key("kat_unwrap/kek", kKekRfc3394, sizeof(kKekRfc3394), + PSA_KEY_USAGE_WRAP | PSA_KEY_USAGE_UNWRAP, + PSA_ALG_KW); + if (kek == PSA_KEY_ID_NULL) { ret = 1; goto done; } + + /* Attributes for the unwrapped key: AES-128, exportable */ + psa_set_key_type(&rattr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&rattr, 128); + psa_set_key_usage_flags(&rattr, PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&rattr, PSA_ALG_KW); + + st = psa_unwrap_key(&rattr, kek, PSA_ALG_KW, + kCipherRfc3394, sizeof(kCipherRfc3394), &recovered); + if (expect_status("kat_unwrap/psa_unwrap_key", st, PSA_SUCCESS) != 0) { + ret = 1; goto done; + } + + st = psa_export_key(recovered, exported, sizeof(exported), &exported_len); + if (expect_status("kat_unwrap/psa_export_key", st, PSA_SUCCESS) != 0) { + ret = 1; goto done; + } + + if (exported_len != sizeof(kPlainRfc3394) || + memcmp(exported, kPlainRfc3394, sizeof(kPlainRfc3394)) != 0) { + printf("FAIL kat_unwrap/plaintext: exported bytes do not match RFC PT\n"); + ret = 1; goto done; + } + +done: + destroy_if_valid(recovered); + destroy_if_valid(kek); + return ret; +} + +/* ------------------------------------------------------------------------- + * Test 3: Roundtrip with a generated AES-256 target key + * ---------------------------------------------------------------------- */ +static int test_roundtrip_aes256(void) +{ + psa_key_id_t kek = PSA_KEY_ID_NULL; + psa_key_id_t target = PSA_KEY_ID_NULL; + psa_key_id_t recovered = PSA_KEY_ID_NULL; + psa_key_attributes_t gattr = psa_key_attributes_init(); + psa_key_attributes_t rattr = psa_key_attributes_init(); + /* AES-256: 32 bytes plain → 40 bytes wrapped */ + uint8_t wrapped[48]; + size_t wrapped_len = 0; + uint8_t orig[32]; + size_t orig_len = 0; + uint8_t recovered_bytes[32]; + size_t recovered_len = 0; + psa_status_t st; + int ret = 0; + + kek = import_aes_key("roundtrip256/kek", kKekRfc3394, sizeof(kKekRfc3394), + PSA_KEY_USAGE_WRAP | PSA_KEY_USAGE_UNWRAP, + PSA_ALG_KW); + if (kek == PSA_KEY_ID_NULL) { ret = 1; goto done; } + + /* Generate the AES-256 target */ + psa_set_key_type(&gattr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&gattr, 256); + psa_set_key_usage_flags(&gattr, PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&gattr, PSA_ALG_KW); + + st = psa_generate_key(&gattr, &target); + if (expect_status("roundtrip256/generate", st, PSA_SUCCESS) != 0) { + ret = 1; goto done; + } + + /* Export original bytes so we can compare later */ + st = psa_export_key(target, orig, sizeof(orig), &orig_len); + if (expect_status("roundtrip256/export_original", st, PSA_SUCCESS) != 0) { + ret = 1; goto done; + } + if (orig_len != 32u) { + printf("FAIL roundtrip256/orig_len: got %zu expected 32\n", orig_len); + ret = 1; goto done; + } + + /* Wrap */ + st = psa_wrap_key(kek, PSA_ALG_KW, target, + wrapped, sizeof(wrapped), &wrapped_len); + if (expect_status("roundtrip256/wrap", st, PSA_SUCCESS) != 0) { + ret = 1; goto done; + } + if (wrapped_len != 40u) { + printf("FAIL roundtrip256/wrapped_len: got %zu expected 40\n", + wrapped_len); + ret = 1; goto done; + } + + /* Unwrap */ + psa_set_key_type(&rattr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&rattr, 256); + psa_set_key_usage_flags(&rattr, PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&rattr, PSA_ALG_KW); + + st = psa_unwrap_key(&rattr, kek, PSA_ALG_KW, + wrapped, wrapped_len, &recovered); + if (expect_status("roundtrip256/unwrap", st, PSA_SUCCESS) != 0) { + ret = 1; goto done; + } + + /* Export and compare */ + st = psa_export_key(recovered, recovered_bytes, sizeof(recovered_bytes), + &recovered_len); + if (expect_status("roundtrip256/export_recovered", st, PSA_SUCCESS) != 0) { + ret = 1; goto done; + } + + if (recovered_len != orig_len || + memcmp(recovered_bytes, orig, orig_len) != 0) { + printf("FAIL roundtrip256/compare: recovered key does not match original\n"); + ret = 1; goto done; + } + +done: + destroy_if_valid(recovered); + destroy_if_valid(target); + destroy_if_valid(kek); + return ret; +} + +/* ------------------------------------------------------------------------- + * Test 4: Missing WRAP / UNWRAP usage flags → PSA_ERROR_NOT_PERMITTED + * ---------------------------------------------------------------------- */ +static int test_missing_usage_flags(void) +{ + psa_key_id_t kek_no_wrap = PSA_KEY_ID_NULL; + psa_key_id_t kek_no_unwrap = PSA_KEY_ID_NULL; + psa_key_id_t target = PSA_KEY_ID_NULL; + psa_key_id_t dummy_key = PSA_KEY_ID_NULL; + psa_key_attributes_t tattr = psa_key_attributes_init(); + psa_key_attributes_t rattr = psa_key_attributes_init(); + uint8_t wrapped[32]; + size_t wrapped_len = 0; + psa_status_t st; + int ret = 0; + + /* KEK with UNWRAP only — psa_wrap_key must fail */ + kek_no_wrap = import_aes_key("missing_wrap/kek_no_wrap", + kKekRfc3394, sizeof(kKekRfc3394), + PSA_KEY_USAGE_UNWRAP, PSA_ALG_KW); + if (kek_no_wrap == PSA_KEY_ID_NULL) { ret = 1; goto done; } + + /* KEK with WRAP only — psa_unwrap_key must fail */ + kek_no_unwrap = import_aes_key("missing_wrap/kek_no_unwrap", + kKekRfc3394, sizeof(kKekRfc3394), + PSA_KEY_USAGE_WRAP, PSA_ALG_KW); + if (kek_no_unwrap == PSA_KEY_ID_NULL) { ret = 1; goto done; } + + /* Target key */ + psa_set_key_type(&tattr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&tattr, 128); + psa_set_key_usage_flags(&tattr, PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&tattr, PSA_ALG_KW); + + st = psa_import_key(&tattr, kPlainRfc3394, sizeof(kPlainRfc3394), &target); + if (expect_status("missing_wrap/import_target", st, PSA_SUCCESS) != 0) { + ret = 1; goto done; + } + + /* psa_wrap_key with a no-WRAP key */ + st = psa_wrap_key(kek_no_wrap, PSA_ALG_KW, target, + wrapped, sizeof(wrapped), &wrapped_len); + if (expect_status("missing_wrap/no_wrap_flag", + st, PSA_ERROR_NOT_PERMITTED) != 0) { + ret = 1; goto done; + } + + /* psa_unwrap_key with a no-UNWRAP key */ + psa_set_key_type(&rattr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&rattr, 128); + psa_set_key_usage_flags(&rattr, PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&rattr, PSA_ALG_KW); + + st = psa_unwrap_key(&rattr, kek_no_unwrap, PSA_ALG_KW, + kCipherRfc3394, sizeof(kCipherRfc3394), &dummy_key); + if (expect_status("missing_wrap/no_unwrap_flag", + st, PSA_ERROR_NOT_PERMITTED) != 0) { + ret = 1; goto done; + } + +done: + destroy_if_valid(target); + destroy_if_valid(kek_no_wrap); + destroy_if_valid(kek_no_unwrap); + destroy_if_valid(dummy_key); + return ret; +} + +/* ------------------------------------------------------------------------- + * Test 5: Target key without PSA_KEY_USAGE_EXPORT → PSA_ERROR_NOT_PERMITTED + * ---------------------------------------------------------------------- */ +static int test_target_no_export(void) +{ + psa_key_id_t kek = PSA_KEY_ID_NULL; + psa_key_id_t target = PSA_KEY_ID_NULL; + psa_key_attributes_t tattr = psa_key_attributes_init(); + uint8_t wrapped[32]; + size_t wrapped_len = 0; + psa_status_t st; + int ret = 0; + + kek = import_aes_key("no_export/kek", kKekRfc3394, sizeof(kKekRfc3394), + PSA_KEY_USAGE_WRAP | PSA_KEY_USAGE_UNWRAP, + PSA_ALG_KW); + if (kek == PSA_KEY_ID_NULL) { ret = 1; goto done; } + + /* Target: no EXPORT flag */ + psa_set_key_type(&tattr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&tattr, 128); + psa_set_key_usage_flags(&tattr, 0); /* no EXPORT */ + psa_set_key_algorithm(&tattr, PSA_ALG_KW); + + st = psa_import_key(&tattr, kPlainRfc3394, sizeof(kPlainRfc3394), &target); + if (expect_status("no_export/import_target", st, PSA_SUCCESS) != 0) { + ret = 1; goto done; + } + + st = psa_wrap_key(kek, PSA_ALG_KW, target, + wrapped, sizeof(wrapped), &wrapped_len); + if (expect_status("no_export/psa_wrap_key", + st, PSA_ERROR_NOT_PERMITTED) != 0) { + ret = 1; goto done; + } + +done: + destroy_if_valid(target); + destroy_if_valid(kek); + return ret; +} + +/* ------------------------------------------------------------------------- + * Test 6: PSA_ALG_KWP → PSA_ERROR_NOT_SUPPORTED + * ---------------------------------------------------------------------- */ +static int test_kwp_not_supported(void) +{ + psa_key_id_t kek = PSA_KEY_ID_NULL; + psa_key_id_t target = PSA_KEY_ID_NULL; + psa_key_id_t dummy_key = PSA_KEY_ID_NULL; + psa_key_attributes_t tattr = psa_key_attributes_init(); + psa_key_attributes_t rattr = psa_key_attributes_init(); + uint8_t buf[32]; + size_t buf_len = 0; + psa_status_t st; + int ret = 0; + + /* + * The KEK must carry PSA_ALG_KWP as its permitted algorithm because + * psa_wrap_key checks wrap_alg != alg after the usage check. We import + * it with PSA_ALG_KWP so that the algorithm mismatch never fires before + * the KWP-not-supported early-out. + */ + kek = import_aes_key("kwp/kek", kKekRfc3394, sizeof(kKekRfc3394), + PSA_KEY_USAGE_WRAP | PSA_KEY_USAGE_UNWRAP, + PSA_ALG_KWP); + if (kek == PSA_KEY_ID_NULL) { ret = 1; goto done; } + + psa_set_key_type(&tattr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&tattr, 128); + psa_set_key_usage_flags(&tattr, PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&tattr, PSA_ALG_KWP); + + st = psa_import_key(&tattr, kPlainRfc3394, sizeof(kPlainRfc3394), &target); + if (expect_status("kwp/import_target", st, PSA_SUCCESS) != 0) { + ret = 1; goto done; + } + + st = psa_wrap_key(kek, PSA_ALG_KWP, target, buf, sizeof(buf), &buf_len); + if (expect_status("kwp/wrap_not_supported", + st, PSA_ERROR_NOT_SUPPORTED) != 0) { + ret = 1; goto done; + } + + psa_set_key_type(&rattr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&rattr, 128); + psa_set_key_usage_flags(&rattr, PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&rattr, PSA_ALG_KWP); + + st = psa_unwrap_key(&rattr, kek, PSA_ALG_KWP, + kCipherRfc3394, sizeof(kCipherRfc3394), &dummy_key); + if (expect_status("kwp/unwrap_not_supported", + st, PSA_ERROR_NOT_SUPPORTED) != 0) { + ret = 1; goto done; + } + +done: + destroy_if_valid(target); + destroy_if_valid(kek); + destroy_if_valid(dummy_key); + return ret; +} + +/* ------------------------------------------------------------------------- + * Test 7: Corrupted wrapped data → PSA_ERROR_INVALID_SIGNATURE + * ---------------------------------------------------------------------- */ +static int test_corrupt_wrapped(void) +{ + psa_key_id_t kek = PSA_KEY_ID_NULL; + psa_key_id_t dummy_key = PSA_KEY_ID_NULL; + psa_key_attributes_t rattr = psa_key_attributes_init(); + uint8_t corrupt[24]; + psa_status_t st; + int ret = 0; + + kek = import_aes_key("corrupt/kek", kKekRfc3394, sizeof(kKekRfc3394), + PSA_KEY_USAGE_WRAP | PSA_KEY_USAGE_UNWRAP, + PSA_ALG_KW); + if (kek == PSA_KEY_ID_NULL) { ret = 1; goto done; } + + /* Copy the valid RFC ciphertext then flip byte 0 */ + memcpy(corrupt, kCipherRfc3394, sizeof(kCipherRfc3394)); + corrupt[0] ^= 0xFF; + + psa_set_key_type(&rattr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&rattr, 128); + psa_set_key_usage_flags(&rattr, PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&rattr, PSA_ALG_KW); + + st = psa_unwrap_key(&rattr, kek, PSA_ALG_KW, + corrupt, sizeof(corrupt), &dummy_key); + if (expect_status("corrupt/invalid_signature", + st, PSA_ERROR_INVALID_SIGNATURE) != 0) { + ret = 1; goto done; + } + +done: + destroy_if_valid(dummy_key); + destroy_if_valid(kek); + return ret; +} + +/* ------------------------------------------------------------------------- + * Test 8: Output buffer one byte too small → PSA_ERROR_BUFFER_TOO_SMALL + * + * The RFC 3394 output for a 16-byte key is 24 bytes. Passing a 23-byte + * buffer must trigger the size guard. + * ---------------------------------------------------------------------- */ +static int test_buffer_too_small(void) +{ + psa_key_id_t kek = PSA_KEY_ID_NULL; + psa_key_id_t target = PSA_KEY_ID_NULL; + psa_key_attributes_t tattr = psa_key_attributes_init(); + uint8_t small_buf[23]; /* 24 - 1 */ + size_t out_len = 0; + psa_status_t st; + int ret = 0; + + kek = import_aes_key("buf_small/kek", kKekRfc3394, sizeof(kKekRfc3394), + PSA_KEY_USAGE_WRAP | PSA_KEY_USAGE_UNWRAP, + PSA_ALG_KW); + if (kek == PSA_KEY_ID_NULL) { ret = 1; goto done; } + + psa_set_key_type(&tattr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&tattr, 128); + psa_set_key_usage_flags(&tattr, PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&tattr, PSA_ALG_KW); + + st = psa_import_key(&tattr, kPlainRfc3394, sizeof(kPlainRfc3394), &target); + if (expect_status("buf_small/import_target", st, PSA_SUCCESS) != 0) { + ret = 1; goto done; + } + + st = psa_wrap_key(kek, PSA_ALG_KW, target, + small_buf, sizeof(small_buf), &out_len); + if (expect_status("buf_small/buffer_too_small", + st, PSA_ERROR_BUFFER_TOO_SMALL) != 0) { + ret = 1; goto done; + } + +done: + destroy_if_valid(target); + destroy_if_valid(kek); + return ret; +} + +/* ------------------------------------------------------------------------- + * main + * ---------------------------------------------------------------------- */ +int main(void) +{ + psa_status_t st; + + st = psa_crypto_init(); + if (st != PSA_SUCCESS) { + printf("FAIL psa_crypto_init: status=%d\n", (int)st); + return 1; + } + + if (test_kat_wrap() != 0) return 1; + if (test_kat_unwrap() != 0) return 1; + if (test_roundtrip_aes256() != 0) return 1; + if (test_missing_usage_flags() != 0) return 1; + if (test_target_no_export() != 0) return 1; + if (test_kwp_not_supported() != 0) return 1; + if (test_corrupt_wrapped() != 0) return 1; + if (test_buffer_too_small() != 0) return 1; + + printf("PSA key wrap test: OK\n"); + return 0; +} diff --git a/test/psa_server/psa_lms_xmss_verify_test.c b/test/psa_server/psa_lms_xmss_verify_test.c new file mode 100644 index 0000000..d0b5e29 --- /dev/null +++ b/test/psa_server/psa_lms_xmss_verify_test.c @@ -0,0 +1,1160 @@ +/* psa_lms_xmss_verify_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 + */ + +/* + * Coverage test for wolfPSA's verify-only LMS/HSS and XMSS support + * (PSA 1.4 PQC extension). + * + * Test vectors sourced from wolfSSL wolfcrypt/test/test.c: + * - lms_L1H10W8_pub / lms_msg / lms_L1H10W8_sig (lms_test_verify_only) + * - xmss_pub / xmss_msg / xmss_sig (xmss_test_verify_only) + * + * The LMS vector is an HSS-1 public key (60 bytes: u32 L=1 || 56-byte + * LMS-SHA256-M32-H10 public key) and is exercised under + * PSA_KEY_TYPE_HSS_PUBLIC_KEY / PSA_ALG_HSS. + * + * The XMSS vector uses XMSS-SHA2_10_256 (OID 0x00000001, pub = 68 bytes: + * 4-byte OID || 32-byte root || 32-byte seed, sig = 2500 bytes) and is + * exercised under PSA_KEY_TYPE_XMSS_PUBLIC_KEY / PSA_ALG_XMSS. + */ + +#include "psa_api_test_user_settings.h" + +#ifndef WOLFSSL_USER_SETTINGS +#define WOLFSSL_USER_SETTINGS +#endif + +#include + +#include +#include +#include + +#include + +/* ------------------------------------------------------------------------- + * Test vectors — copied verbatim from wolfSSL wolfcrypt/test/test.c + * (lms_test_verify_only / xmss_test_verify_only). + * ------------------------------------------------------------------------- + * + * HSS public key: L=1, LMS-SHA256-M32-H10, OTS=LMOTS-SHA256-N32-W8 + * Length: 60 bytes → PSA_KEY_TYPE_HSS_PUBLIC_KEY, bits=256 + */ +static const uint8_t hss_pub[60] = { + 0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x06, + 0x00,0x00,0x00,0x04,0xA1,0x26,0x76,0xF8, + 0xBB,0x0B,0xC0,0x82,0x21,0x71,0x0B,0x2E, + 0x8C,0xA6,0xEF,0x12,0xED,0x41,0x0E,0x8C, + 0xAF,0x11,0x93,0x34,0x7B,0x49,0x79,0xB7, + 0xDE,0x63,0x1C,0xFE,0x1F,0xD1,0x17,0x49, + 0xCD,0x5C,0xD4,0x26,0xA0,0x53,0x26,0x1A, + 0xC5,0xB4,0x8F,0x23 +}; + +/* "wolfSSL LMS example message!" without null terminator */ +static const uint8_t hss_msg[28] = { + 0x77,0x6F,0x6C,0x66,0x53,0x53,0x4C,0x20, + 0x4C,0x4D,0x53,0x20,0x65,0x78,0x61,0x6D, + 0x70,0x6C,0x65,0x20,0x6D,0x65,0x73,0x73, + 0x61,0x67,0x65,0x21 +}; + +/* LMS-SHA256-M32-H10 / W8, L=1 signature (1456 bytes) */ +#define HSS_SIG_LEN 1456 +static uint8_t hss_sig[HSS_SIG_LEN] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01, + 0x00,0x00,0x00,0x04,0x18,0x70,0x09,0x2E, + 0x21,0xC9,0x6A,0xC9,0x5C,0xB6,0xB0,0xAA, + 0xC3,0xED,0x6E,0x66,0x2F,0xCC,0x45,0x81, + 0xBC,0xBA,0x44,0x96,0x1C,0xBF,0x4E,0xFB, + 0x7A,0x46,0xFB,0xBE,0x9A,0x0C,0xE4,0x50, + 0x90,0xC7,0x92,0xAC,0x53,0xAE,0x53,0x76, + 0x29,0xA6,0x65,0xF1,0x09,0xED,0x1A,0x8E, + 0x03,0x2E,0x5A,0x06,0x51,0xE3,0x1E,0xE6, + 0xF6,0xFE,0x3A,0x6E,0xD1,0x92,0x31,0x1D, + 0xA1,0x6A,0x5C,0x30,0x3A,0xC7,0xFD,0x5B, + 0xFE,0x71,0x2C,0x5C,0x2F,0x5B,0x5B,0xCF, + 0xBC,0x7F,0xBF,0x6C,0xAF,0x44,0x8A,0xAE, + 0x14,0x60,0xAB,0x88,0xED,0x0E,0x4F,0xF8, + 0xC7,0x1B,0x74,0x28,0x72,0xB3,0x96,0xA6, + 0xE6,0x46,0x22,0x82,0xCF,0x1F,0x4D,0xA6, + 0xEA,0x22,0x06,0x07,0x52,0xF5,0x26,0x16, + 0x0B,0x90,0xE3,0xFF,0x64,0xA9,0xE4,0x61, + 0x1E,0x9C,0x12,0x9C,0xF6,0xD4,0x63,0x29, + 0xEA,0x02,0xF7,0x18,0x52,0x79,0x6C,0x43, + 0xDC,0xCF,0x43,0x23,0xB9,0xCC,0x4A,0x25, + 0x9D,0x10,0xAF,0xA3,0xE6,0x47,0x5A,0x1C, + 0xFE,0x68,0x89,0xAF,0x1B,0x2D,0x88,0x3E, + 0xCA,0xDC,0x70,0xEA,0xAC,0x11,0x00,0x8A, + 0x6E,0xE0,0xC7,0xD0,0xD2,0x1A,0x36,0x18, + 0x97,0xB3,0x5F,0x0E,0x75,0x48,0x28,0xF8, + 0xA8,0xF5,0x90,0xD1,0xA1,0x84,0xFB,0xA4, + 0xAD,0x50,0xBE,0xE9,0x39,0x8C,0xC5,0xA1, + 0x67,0x51,0xA1,0x8C,0xD6,0x6B,0x97,0x1F, + 0x47,0x99,0xEE,0xE0,0x70,0x01,0xC7,0x07, + 0x50,0xF3,0x5E,0x3F,0xE7,0x06,0xD6,0x8D, + 0x26,0xD6,0x5A,0x59,0x18,0x72,0x6B,0x12, + 0xD2,0xAF,0x9B,0xB4,0x2B,0xD0,0xB2,0xF2, + 0x96,0x2F,0x40,0xEA,0xBE,0xE6,0xAC,0x1F, + 0xB8,0x33,0xC2,0x76,0xDC,0x8C,0xAC,0xC1, + 0x46,0x5E,0x04,0x84,0x1B,0xC8,0xB9,0x65, + 0x8D,0xAD,0x96,0xB5,0xB1,0xF6,0x17,0x4A, + 0x19,0x87,0xE7,0xBF,0x29,0xC7,0x9B,0xB9, + 0xD6,0x11,0x2C,0x92,0x2F,0xB7,0x24,0xD5, + 0x01,0x1D,0x80,0x37,0x54,0xED,0x33,0x32, + 0xAB,0x7A,0x12,0xD4,0x02,0x1D,0x27,0x52, + 0x89,0xDB,0x32,0xBF,0x61,0xD4,0xBB,0xB4, + 0x46,0x78,0x1B,0x64,0x17,0x84,0x4B,0x8A, + 0xBA,0xC6,0xC1,0xCF,0xC7,0x5D,0x8F,0x93, + 0xC5,0x9A,0x27,0x90,0xAC,0x17,0x98,0xFF, + 0xC8,0x22,0x59,0x55,0x90,0xB2,0x29,0x39, + 0xA0,0xBE,0x00,0x23,0x55,0x6B,0xDA,0x83, + 0xD8,0x5B,0x57,0x7C,0x67,0x1B,0xC3,0x6B, + 0x6D,0xC7,0x9B,0x2B,0x9E,0xB7,0x95,0xB3, + 0xF0,0x1B,0x89,0x5A,0xD7,0x4B,0x67,0xAF, + 0xDC,0x9E,0xCF,0x7E,0x1A,0xBA,0x1B,0xB9, + 0x3B,0x7A,0xDD,0x3F,0x0D,0xEE,0x4C,0x0B, + 0xD1,0x4F,0x34,0xF2,0x93,0xF7,0x21,0x64, + 0x2C,0x07,0x00,0x15,0x4F,0xE3,0x6A,0x9F, + 0x08,0x52,0xC2,0x65,0x47,0x1F,0x34,0x64, + 0x66,0x07,0xBC,0xEA,0xAF,0x9B,0xAA,0x39, + 0x15,0x8B,0x08,0x8C,0x24,0x41,0x9B,0x46, + 0x1B,0x5B,0x91,0x11,0xC4,0xFD,0xA9,0x88, + 0x35,0x0E,0x7D,0xAF,0xFD,0xB7,0x90,0x7E, + 0xD7,0x29,0x02,0x0A,0xDC,0xC8,0x3F,0xC0, + 0xFD,0x97,0xAF,0x50,0x49,0xA6,0x5E,0x12, + 0xC1,0xCD,0xEC,0x52,0xC5,0x51,0xF2,0x80, + 0x17,0x61,0xC7,0x7E,0xBE,0xD1,0x1B,0x65, + 0xA4,0xAB,0x92,0x8D,0x89,0xB2,0xC5,0x8F, + 0xFF,0xA5,0x6F,0xFA,0x62,0x75,0xE4,0xA1, + 0xD4,0x22,0xA8,0x9E,0x40,0x04,0x27,0x1F, + 0xCC,0x81,0xBA,0x28,0x67,0xA0,0x1C,0x80, + 0xEB,0xCA,0xB0,0x61,0xA5,0x48,0xD0,0x8A, + 0x25,0xEB,0x9E,0x67,0x8C,0x8E,0x9B,0xD1, + 0xAD,0xBB,0xC3,0xEA,0xD3,0xD4,0xC5,0x12, + 0x7B,0xDD,0x00,0x57,0x7F,0xF6,0xF7,0xF6, + 0x3C,0x05,0xCF,0xFC,0x12,0xE1,0x93,0x05, + 0xE5,0x9B,0x79,0x87,0x69,0xD8,0x82,0xD9, + 0xD7,0x1D,0x41,0x73,0xE4,0x52,0x1D,0x3E, + 0xE5,0x8C,0x8D,0x34,0xE1,0x75,0xA9,0xF1, + 0x9D,0x09,0xA2,0x5B,0xEF,0xDA,0x96,0x6E, + 0x76,0x3D,0xEA,0x50,0xD9,0xCF,0x4F,0xAC, + 0xAD,0x1D,0x35,0x72,0x1B,0x88,0x8B,0xCD, + 0x8C,0x8A,0x8A,0xE0,0x96,0x04,0xD8,0xBB, + 0x28,0x43,0x16,0x77,0x60,0x98,0x63,0xF9, + 0xB9,0x71,0x46,0xB7,0xE1,0xA7,0xA9,0x84, + 0xC3,0x65,0x82,0xE1,0x1B,0x67,0x04,0x2D, + 0x55,0x6B,0xF9,0xC0,0x79,0x09,0x09,0xE7, + 0xFD,0x06,0x4D,0x09,0x9B,0x1A,0xCE,0x35, + 0xFA,0x27,0x6F,0x2F,0x01,0x65,0x0D,0xA0, + 0x97,0x59,0x11,0xF0,0x48,0xD2,0xE7,0x46, + 0xBE,0xB4,0x0A,0xA3,0xE2,0x75,0x0E,0x09, + 0x94,0xD9,0x69,0x28,0xD4,0xDA,0x64,0xBA, + 0xFE,0xA4,0xB9,0xF0,0xBA,0xEB,0xBA,0xAC, + 0xA8,0xF9,0xD3,0x82,0x4C,0x36,0x80,0xFA, + 0xE5,0xF6,0x76,0xC3,0x80,0xFA,0x90,0x29, + 0xF4,0x85,0xA4,0xC6,0x25,0x22,0x79,0x7E, + 0x39,0x1E,0x30,0xB8,0x65,0x72,0xCF,0xE1, + 0x99,0xF0,0x75,0xE8,0x09,0xB4,0x92,0x96, + 0x1B,0x68,0x50,0x88,0xF1,0x2C,0x97,0xE3, + 0x2D,0x26,0x8F,0xC5,0x30,0xCF,0x24,0xCB, + 0xB2,0x60,0x77,0xDC,0x02,0x72,0x0D,0xD9, + 0x2E,0xF2,0x52,0xEA,0x00,0xF6,0x32,0x65, + 0xA5,0xC6,0x43,0x29,0x29,0x69,0xAB,0x27, + 0x0C,0x39,0xDF,0x76,0x3E,0x93,0x95,0xB1, + 0x2C,0xA2,0x0D,0x18,0xCE,0xA0,0x97,0x10, + 0x3C,0x90,0xC0,0xEF,0x0E,0x04,0xA6,0xC8, + 0xA0,0x21,0x3C,0x0B,0x22,0x77,0x7A,0x66, + 0xA5,0x90,0x25,0xA4,0x09,0x3E,0xD5,0x27, + 0x1F,0x6C,0x99,0x85,0x5C,0xA2,0x99,0x7A, + 0x25,0xEE,0x8D,0x32,0x3D,0xD3,0xDC,0xF5, + 0x00,0x5A,0x34,0x61,0xB6,0xCD,0x4E,0xBC, + 0x26,0x36,0xFB,0x44,0x97,0x35,0xBD,0x06, + 0x7D,0x2E,0x4A,0xA2,0xDC,0x24,0xFE,0x70, + 0x0A,0xF9,0x57,0xE3,0xEE,0xAB,0xD1,0x17, + 0xF3,0x7C,0xD6,0x37,0x26,0xFA,0x83,0x9F, + 0xDD,0xB2,0xE1,0xD7,0xF9,0xC7,0x0E,0x15, + 0x01,0xA6,0x58,0x32,0x98,0x04,0x32,0xD4, + 0xDE,0xB9,0xEF,0x09,0xFA,0xE4,0x5A,0xD7, + 0xDD,0x09,0x1C,0xC9,0xAC,0xB8,0x6A,0xF5, + 0x00,0x5D,0x6B,0x95,0x12,0x8C,0x2F,0xCC, + 0xD8,0xB9,0x50,0x3A,0xEB,0x74,0x86,0xD2, + 0x3F,0xA1,0x05,0x8F,0x6E,0xEF,0xF5,0xA4, + 0xD6,0x6E,0x53,0xFA,0x9E,0xFA,0xCE,0xDB, + 0x99,0x46,0xE7,0xC5,0xDA,0x92,0x51,0x4F, + 0x22,0x07,0xF3,0xA5,0x38,0x26,0xD3,0xEC, + 0xD6,0x01,0xDD,0x31,0x3A,0x48,0x93,0xF6, + 0x69,0x4F,0xD8,0xF6,0xC2,0x91,0xA5,0x7C, + 0xDF,0x51,0x64,0xF1,0x3B,0x79,0xBC,0x0A, + 0x2C,0xDC,0x33,0x5A,0x29,0xF6,0xB2,0x09, + 0x66,0xCA,0x24,0x9F,0x1A,0x18,0xF3,0x76, + 0x4C,0x5E,0x0B,0x81,0x7F,0x29,0x84,0xD8, + 0x7A,0xA8,0xD6,0x11,0xAC,0xEC,0xD9,0x07, + 0x91,0xEC,0xB6,0x6D,0xEC,0xDB,0xBE,0x6F, + 0x9F,0xC5,0x19,0x5E,0x56,0x87,0x20,0x80, + 0x75,0xD5,0x64,0xE9,0x80,0xBF,0x2D,0xD5, + 0x94,0x9F,0x8C,0xA4,0x54,0x41,0xAB,0xB1, + 0x8E,0xAD,0x51,0xE4,0x3C,0x24,0xF7,0x1D, + 0xFE,0x02,0x48,0x7C,0x6D,0xED,0xF1,0xAC, + 0xD9,0x79,0x42,0xE5,0x3A,0xCF,0x6A,0x4C, + 0x6D,0xE2,0x13,0xD2,0x2B,0x9D,0xAB,0x1F, + 0x70,0xD3,0xC0,0x6F,0x81,0xE9,0x9A,0x86, + 0x33,0x39,0x60,0xE7,0x6A,0x00,0x1F,0x97, + 0xEB,0xE5,0x1D,0x0D,0x66,0x15,0xC9,0xA2, + 0xB1,0xC0,0xF0,0x2E,0xF4,0x07,0xA2,0x2E, + 0x49,0x92,0x95,0x13,0xA3,0x18,0x46,0x25, + 0xB9,0x3C,0xA1,0x4B,0x00,0x00,0x00,0x06, + 0xAB,0xAA,0xF9,0x3F,0x7E,0x21,0xF4,0x0E, + 0xCE,0xFD,0xE0,0x44,0xAC,0xC7,0x1A,0x30, + 0x22,0x9D,0x0A,0xD7,0x96,0x2D,0x8F,0x9A, + 0x99,0x1F,0x40,0x75,0x7F,0x62,0xF9,0xC1, + 0x81,0x7B,0x4A,0x1B,0xFA,0xD6,0x87,0xB9, + 0xEF,0x58,0x48,0xE4,0x5C,0x79,0xE5,0xB1, + 0x2C,0x59,0xA4,0x42,0xDB,0xA6,0x53,0x70, + 0x80,0x61,0x17,0xD4,0xD3,0x77,0xBD,0x53, + 0x26,0x7C,0x0E,0x0E,0xFF,0x30,0x4B,0xD0, + 0x86,0xFC,0x02,0x20,0x24,0x46,0x5B,0xF5, + 0xE3,0x99,0x73,0x85,0x60,0x00,0x36,0x47, + 0x17,0xEE,0x0C,0xD2,0x80,0x71,0x46,0x0E, + 0x2B,0xB0,0xEF,0x7F,0xFE,0x3B,0xE5,0xE1, + 0x87,0xC2,0xAF,0x1A,0x6F,0x63,0xF4,0x5A, + 0xC4,0x16,0xF7,0xAD,0x07,0x70,0x71,0x85, + 0x7D,0x3D,0x67,0x08,0xB8,0xD8,0xE2,0xF0, + 0xA1,0xAC,0xD2,0x94,0x7D,0x93,0x03,0xDD, + 0x54,0xF9,0x64,0x19,0xB3,0xED,0x24,0x22, + 0x01,0xD7,0x12,0x5E,0xC1,0x2B,0x39,0x10, + 0x13,0xE2,0x56,0x1C,0xEE,0xF4,0x2A,0x49, + 0x7B,0xFB,0x36,0x8D,0xF8,0xAF,0x60,0xDF, + 0x10,0xF0,0x72,0xA2,0xED,0xB6,0x53,0x88, + 0xA9,0x0C,0xED,0x9C,0x18,0x33,0x7D,0x65, + 0x9B,0xB2,0x9C,0x3E,0xE9,0x1E,0x43,0x51, + 0x7E,0xBE,0x01,0x95,0xF6,0x60,0x65,0xBE, + 0xD1,0xF4,0xE2,0x83,0x6B,0xCA,0x7A,0x70, + 0x41,0x83,0x72,0xC0,0x23,0x51,0x13,0x11, + 0x2D,0xF9,0xC0,0x0D,0x7D,0x73,0x76,0xA5, + 0x30,0x83,0x68,0x10,0x35,0xA2,0x18,0x22, + 0x4E,0x21,0x93,0x27,0x6A,0x19,0x28,0x83, + 0x7F,0xDD,0xDD,0xFF,0xC3,0x8A,0x64,0x00, + 0x5F,0x1C,0x0D,0xF8,0xBB,0xD7,0x15,0xB9, + 0xEF,0xE0,0x07,0x62,0x05,0x9E,0xCF,0xFC, + 0x08,0x52,0x1E,0x65,0x41,0x56,0x6A,0xEB, + 0x81,0x53,0x30,0x7B,0xF2,0xFD,0x65,0xFF, + 0xA2,0x14,0xF5,0x62,0x1E,0x24,0x48,0x47, + 0xA5,0x41,0x80,0xB4,0xC5,0xDC,0xB2,0xB4, + 0x2D,0x17,0xE7,0xBE,0x49,0x53,0x7A,0x25, + 0xC5,0x0D,0x19,0x59,0xF4,0x88,0x59,0xED, + 0x92,0x13,0xEE,0x7A,0x4F,0x12,0x98,0x4C +}; + +/* ------------------------------------------------------------------------- + * XMSS test vectors — from wolfSSL wolfcrypt/test/test.c (xmss_test_verify_only) + * + * XMSS-SHA2_10_256: OID 0x00000001 + * Public key: 68 bytes (4-byte OID || 32-byte root || 32-byte seed) + * → PSA_KEY_TYPE_XMSS_PUBLIC_KEY, bits=256 + * Signature: 2500 bytes (the 5th signature produced by xmss_fast test + * in the xmss-reference repository) + * -------------------------------------------------------------------------*/ +static const uint8_t xmss_pub[68] = { + 0x00,0x00,0x00,0x01,0xA5,0x41,0x31,0x96, + 0x0A,0xF9,0xF3,0xB2,0x4B,0x2E,0x5B,0x3E, + 0xCA,0x74,0xAD,0x6C,0xA5,0x89,0xAD,0x2C, + 0x0E,0x96,0xB3,0x54,0xFB,0x5B,0x63,0x50, + 0x96,0x81,0xE2,0x59,0x72,0x10,0x09,0x54, + 0xBB,0x39,0xAC,0xEE,0x78,0xEF,0x95,0xEC, + 0x01,0x1D,0xF0,0x36,0x68,0xE2,0xC4,0xA5, + 0x2F,0x60,0x42,0x7E,0xD3,0x8E,0xAA,0x27, + 0xC9,0xB7,0x39,0x4E +}; + +static const uint8_t xmss_msg[32] = { + 0x07,0x9F,0x80,0x86,0xDB,0x76,0x27,0xDF, + 0xED,0x5B,0x2A,0x81,0x60,0x60,0x7D,0xB4, + 0xE8,0x7A,0x69,0x45,0x20,0x6B,0xA2,0x96, + 0xC0,0x21,0xA5,0x46,0x29,0x63,0x9B,0x37 +}; + +#define XMSS_SIG_LEN 2500 +static uint8_t xmss_sig[XMSS_SIG_LEN] = { + 0x00,0x00,0x00,0x05,0xF0,0x15,0x34,0xBA, + 0x92,0x03,0x6A,0xB9,0xA5,0x23,0x86,0x11, + 0xAE,0x65,0x0A,0x5C,0x78,0x2C,0xC9,0xBE, + 0x7E,0xA6,0xDC,0xA2,0x8B,0xA9,0x9C,0x50, + 0xF6,0x61,0x8D,0x9D,0xD7,0xE9,0xC0,0xF8, + 0x67,0xCD,0x8A,0xC4,0x9B,0x74,0x96,0x07, + 0x5D,0xF2,0xC9,0xCC,0x28,0x05,0xB1,0xBE, + 0x5E,0xA4,0xBA,0xBE,0xAB,0xD8,0x21,0x6B, + 0x21,0x5F,0xAB,0xB7,0x6C,0xEC,0x2F,0xC8, + 0xC6,0x74,0x3E,0x97,0x1B,0xC3,0x45,0x57, + 0xAF,0xAA,0x1E,0xA8,0xF2,0x86,0xA8,0xAA, + 0x43,0x6D,0x66,0xE9,0x81,0x14,0xDE,0x09, + 0x39,0xD2,0xAF,0xD1,0x4C,0xE7,0x75,0x18, + 0x0D,0xAA,0x29,0xA1,0x92,0x53,0xCC,0xE9, + 0xF3,0x0B,0x1E,0x3B,0xE2,0xAE,0x80,0x0C, + 0xE7,0x7A,0x7C,0x13,0x8A,0x28,0xC6,0x5F, + 0x0A,0xA4,0xA3,0x73,0x0A,0x3A,0xC2,0xA6, + 0x3B,0xB4,0x30,0x67,0xC0,0x36,0x18,0xA1, + 0x58,0xCD,0xAD,0x54,0x36,0x64,0xCE,0xFD, + 0x52,0xFF,0x70,0x7E,0x09,0xFB,0x13,0xA2, + 0xEA,0xDF,0x67,0x8D,0x6C,0x42,0xB2,0x78, + 0xF5,0x7D,0x5C,0x4B,0xF7,0x8E,0xCF,0x3E, + 0xB7,0xC6,0xC1,0x23,0xFA,0x65,0xDE,0xD2, + 0xFA,0x40,0x51,0x97,0x0D,0x52,0x32,0x76, + 0x7E,0x82,0x8D,0xD0,0xB9,0x1E,0x62,0xD9, + 0x1E,0xC1,0xDB,0x40,0x43,0x37,0x4A,0x23, + 0x8A,0x1D,0x35,0xFA,0xF4,0x53,0x11,0x5A, + 0xB5,0x6D,0x1E,0x8B,0x22,0xC8,0x7D,0x2A, + 0xE4,0x94,0xAA,0x25,0x20,0x40,0x96,0xDB, + 0x82,0x62,0xBA,0x8F,0x8B,0x45,0xCB,0x4F, + 0x35,0x88,0x33,0xEB,0xEF,0xB3,0xBA,0xA7, + 0x09,0x72,0xB3,0x4C,0xEC,0xF2,0xC3,0xC7, + 0x5E,0x02,0x6C,0x41,0x93,0xCB,0x3C,0x89, + 0x12,0x09,0x68,0x54,0x8E,0xEC,0x6A,0x7E, + 0x20,0xE1,0x70,0x3D,0x8C,0xEB,0xB4,0x36, + 0xBE,0x91,0xBE,0x97,0xB5,0xA6,0x34,0x16, + 0x95,0x0F,0x10,0x26,0xA9,0x13,0x80,0x88, + 0x9C,0xAA,0x68,0xEC,0x34,0x70,0x4A,0x15, + 0x9B,0x5E,0x57,0x05,0x87,0x1C,0xF8,0x35, + 0x45,0x29,0xE9,0x6E,0xF2,0x70,0x13,0x42, + 0x89,0x4E,0x77,0xC0,0x18,0xC7,0x55,0x6D, + 0xE7,0xFA,0x0D,0x63,0x83,0x16,0x19,0x01, + 0x2D,0xFD,0x31,0x14,0x94,0xCA,0x3E,0x0E, + 0xD6,0x11,0x34,0x81,0x57,0x58,0xEC,0x24, + 0xA4,0x17,0x63,0xD3,0x25,0x00,0xBF,0x7D, + 0x78,0x5D,0xC5,0xD8,0xC6,0xC1,0xBD,0x8C, + 0xD0,0x94,0x0A,0xB1,0x33,0xA5,0x4B,0x31, + 0x25,0xF5,0xAF,0xE7,0x84,0x26,0xAA,0x05, + 0xBB,0xF3,0x9A,0xAF,0x58,0x36,0x40,0xEF, + 0x3D,0xA2,0xBD,0xCA,0xA1,0x8D,0x2F,0x6D, + 0x54,0xD2,0x62,0x33,0x09,0xAE,0xE6,0x73, + 0xD6,0x44,0xE8,0x7C,0x5C,0x39,0x2B,0x78, + 0x94,0x14,0xC7,0xC9,0xAF,0xEC,0x77,0x36, + 0xA1,0x61,0x61,0xF1,0xD0,0x09,0xA2,0xEE, + 0xE7,0x55,0xD7,0x35,0x89,0x89,0x9B,0xCF, + 0xFA,0xA6,0x09,0x1E,0x3B,0xBD,0x5D,0xD9, + 0x25,0xE7,0xED,0xDD,0x7C,0xF0,0x1C,0x57, + 0xE0,0x06,0xBB,0x08,0x39,0x59,0xDF,0xD7, + 0xAF,0x4B,0x88,0x0D,0x87,0x8F,0x4A,0xF3, + 0x1C,0xD4,0x4B,0xB3,0xE2,0xF3,0x1B,0x86, + 0x4F,0xCD,0x35,0x75,0xE2,0x03,0xF9,0x1D, + 0xBF,0x3E,0xD1,0x7B,0xC7,0x23,0x11,0x75, + 0x5F,0x92,0x0D,0x98,0xEE,0x14,0xE1,0xDA, + 0x7A,0x02,0x17,0x47,0x6B,0x41,0xEA,0x47, + 0xA1,0xAF,0x06,0x79,0x1A,0x52,0x6F,0x19, + 0x31,0x70,0x71,0xBD,0xC2,0x61,0x8D,0xB7, + 0xEE,0x6B,0x69,0x2A,0xE8,0x21,0x7A,0x95, + 0xBE,0x86,0x2A,0xA1,0xF4,0xE2,0x2F,0x17, + 0x02,0xFD,0xAD,0x17,0x9F,0x0A,0x0A,0x78, + 0xA9,0x92,0x30,0x21,0x72,0x2B,0x28,0xF8, + 0xF2,0x3E,0x05,0xD5,0xAC,0xC0,0x82,0xF8, + 0xD2,0xDA,0xD0,0xA3,0xBC,0x93,0xDB,0xA5, + 0x46,0xDE,0x14,0x1E,0xD4,0x3A,0x5D,0x79, + 0x3D,0x31,0x4B,0x06,0xCE,0x22,0x29,0x3C, + 0x98,0xB6,0x18,0x8A,0xAE,0xF7,0xBA,0x22, + 0x88,0xA1,0xEE,0xC0,0x14,0x4C,0x4A,0xA0, + 0x57,0x0A,0xD3,0x18,0xA2,0x3D,0xDD,0xC7, + 0x83,0x73,0xFC,0x38,0x9B,0x31,0xA3,0xE1, + 0x17,0x76,0xA1,0xA2,0x69,0xFC,0xAB,0x08, + 0x80,0x72,0x8D,0xF5,0xE4,0x14,0xB7,0x6B, + 0x03,0xFF,0xE8,0x11,0x4B,0x06,0x55,0x7E, + 0x36,0x21,0x2F,0xD7,0x54,0x82,0xC9,0x31, + 0xB4,0x85,0x68,0x41,0xEF,0x75,0xB0,0x3A, + 0xEA,0x4F,0xE0,0xEC,0x72,0xCC,0x33,0x96, + 0xCE,0x7D,0xAD,0xDD,0x0D,0x27,0x05,0x6E, + 0xA2,0xD4,0x11,0x07,0xD8,0x7D,0x27,0xD4, + 0x80,0x8F,0x00,0x22,0xE4,0xFC,0x2C,0x9D, + 0xD5,0xD8,0x18,0x7F,0x4E,0xF4,0xB9,0x7F, + 0xEF,0xD6,0x00,0x08,0x5C,0x05,0x04,0x1E, + 0x9A,0xC6,0x8D,0xCC,0x19,0xD9,0x0B,0x06, + 0xCC,0x6A,0x17,0xE2,0x03,0x23,0xDB,0x1C, + 0xBC,0xA2,0xB9,0xA2,0x95,0x3C,0x73,0xD8, + 0xFF,0xE6,0x0E,0xAE,0x04,0xB2,0xFC,0x91, + 0x4F,0xEF,0x8A,0x58,0xB7,0x31,0x68,0x4C, + 0x1E,0xD0,0x5B,0x85,0xCC,0x03,0xDC,0xF4, + 0xAC,0xDB,0x03,0x9B,0x35,0x33,0x08,0x71, + 0xD0,0x50,0x8D,0xDC,0xE3,0x3A,0x98,0x40, + 0x41,0x80,0xDD,0x35,0xE1,0xA2,0xAF,0x14, + 0x9A,0xDB,0xD3,0x68,0x14,0xE2,0x50,0x7A, + 0x76,0x3F,0xE4,0xA4,0x1B,0xAA,0xC1,0x06, + 0x87,0x9A,0x92,0xF9,0xBE,0x9E,0x86,0x8C, + 0x92,0x1D,0x74,0xB1,0x7F,0x27,0x43,0xC0, + 0xEE,0x2E,0xC2,0x6C,0x6D,0xAA,0x0C,0x0E, + 0x71,0xC9,0x56,0xD6,0x3A,0x56,0xCB,0x90, + 0xD1,0x7E,0x6E,0x1C,0x6A,0x00,0x2D,0x02, + 0x2C,0x96,0xF0,0x2A,0x37,0x37,0x18,0x07, + 0x0B,0xF4,0xB4,0x8C,0x30,0xF2,0xA4,0xAB, + 0x66,0xFB,0x8B,0x22,0xC0,0x00,0x7E,0x05, + 0xB6,0xF9,0x95,0x49,0x33,0xA1,0xDC,0x97, + 0x0C,0x5C,0x61,0x46,0xE2,0xD7,0x87,0x4B, + 0xC4,0xC7,0x5F,0x26,0x06,0x84,0xD7,0x47, + 0x05,0xF1,0x33,0xFF,0x85,0x85,0xB2,0xBD, + 0x1F,0x44,0xC6,0xC2,0x7D,0x51,0xBE,0x0E, + 0xB5,0xC4,0x44,0x2F,0xFE,0x73,0x5F,0xF4, + 0xA4,0xEF,0xE2,0xF1,0x73,0x0B,0xEF,0x3E, + 0x2B,0xD7,0xCC,0x9F,0xDA,0x1A,0x7E,0x92, + 0x39,0xA1,0x55,0xBF,0x60,0x0A,0xDB,0x23, + 0x74,0xFE,0xE7,0x05,0x63,0xA9,0x85,0x52, + 0x9F,0xCC,0xC3,0xFF,0xF6,0x6C,0x1B,0x4E, + 0x4F,0x01,0xBD,0xC3,0xEB,0x37,0xEC,0x29, + 0x21,0x3B,0x2C,0xC9,0x2E,0x93,0x20,0x3E, + 0x19,0xC0,0x8B,0xE8,0x33,0xCD,0xC6,0x6A, + 0x6E,0x72,0x13,0x15,0xA1,0x90,0x20,0x0C, + 0x14,0x66,0xED,0xCC,0xA4,0xDD,0x7F,0x58, + 0x53,0xBC,0x4A,0x68,0xFC,0x86,0x3E,0xAA, + 0xF1,0x17,0x0F,0x3E,0x20,0x54,0x93,0xF4, + 0x98,0xBF,0xB4,0x07,0x05,0xBD,0x70,0xE7, + 0xD7,0x34,0xFD,0xE3,0x69,0xDF,0xCD,0xF5, + 0x1A,0x73,0x6E,0xC9,0x2B,0x21,0xFB,0xB8, + 0x7E,0x44,0x10,0x83,0x56,0xCE,0xD5,0x15, + 0x9A,0x75,0xFC,0x91,0x8E,0x6B,0x9E,0x1A, + 0x3A,0x33,0x39,0x35,0xB4,0x0D,0x74,0xF4, + 0xFB,0x4C,0x0E,0x37,0xFE,0x82,0x95,0x46, + 0x6B,0xD2,0x6E,0xEE,0xCD,0x4D,0x38,0xAF, + 0x0A,0xAA,0xF1,0xD5,0xA4,0x7C,0x04,0xD8, + 0xB9,0xDB,0x11,0x68,0x88,0x35,0x41,0xDE, + 0x31,0x33,0x0C,0xDC,0x2D,0x4C,0xA8,0x20, + 0xCC,0x2C,0x4C,0x63,0xAB,0xBA,0xDF,0x48, + 0x84,0xD5,0x25,0xBC,0x70,0xE3,0x49,0xAA, + 0x43,0xCA,0x8B,0xE7,0x9F,0xDD,0x20,0x76, + 0x9B,0x38,0xF4,0xBA,0x4D,0x4E,0x34,0x4A, + 0xAF,0x81,0xE7,0x0B,0xEC,0xE9,0x59,0xC1, + 0x35,0x22,0x7F,0x69,0x46,0x62,0xD2,0x18, + 0x6E,0x1F,0x79,0xD1,0xAD,0xC3,0x84,0x95, + 0x96,0xB2,0x18,0x58,0x5E,0x7E,0x0C,0x25, + 0x0A,0x0F,0x69,0xA3,0x1D,0xEC,0x29,0xCB, + 0xDA,0xA2,0xD1,0x1A,0x10,0xA5,0x52,0xC3, + 0x62,0x1E,0xC5,0x83,0xFF,0xA3,0x56,0xC2, + 0xFD,0x87,0x3B,0x57,0x52,0x98,0x36,0x95, + 0x77,0x6B,0xE5,0x49,0x10,0x8E,0x39,0xDD, + 0xCA,0x4B,0xB3,0x9F,0x4C,0x0C,0x11,0x62, + 0xF3,0x22,0x78,0xDB,0x48,0xEB,0x68,0xFE, + 0xE4,0x2A,0xE9,0xAA,0x8F,0x7A,0x2F,0x69, + 0xA5,0xC5,0x03,0x2D,0xEF,0x62,0xA8,0x71, + 0x65,0x06,0x40,0x84,0x10,0x0F,0xF2,0xED, + 0xBC,0x70,0x71,0x69,0x24,0xA2,0xBF,0x83, + 0x39,0xDD,0xFA,0xA2,0x7B,0xE5,0xEC,0x3D, + 0xFE,0x3B,0x52,0x6E,0x3D,0x82,0xA6,0x2A, + 0x86,0x01,0x61,0x51,0x63,0xBF,0xF9,0x0A, + 0x06,0x72,0xF1,0xD5,0x39,0x0C,0xBA,0xC9, + 0x78,0xC6,0x77,0x22,0xE4,0x96,0x6E,0xB1, + 0x48,0x62,0x84,0x62,0x2D,0xEA,0x49,0x56, + 0x50,0x86,0x3F,0x90,0xC3,0x01,0x42,0x45, + 0xED,0xE6,0x9A,0x65,0x19,0x93,0x7F,0x48, + 0x16,0xF2,0x50,0xA7,0x70,0xB3,0xF5,0xDB, + 0x0E,0x5E,0x22,0x9E,0x64,0x04,0x26,0x69, + 0xC1,0x16,0xEE,0x65,0x08,0x82,0x27,0x65, + 0xEC,0x3D,0xDF,0x51,0x5E,0x2D,0xE8,0x76, + 0xF2,0xE3,0xE4,0x24,0x04,0x88,0x06,0x0F, + 0xB2,0x7B,0x9B,0x72,0x3D,0x4C,0x7D,0x6A, + 0x1F,0xB2,0xA2,0xD2,0x35,0xD6,0x40,0x25, + 0xC2,0x0B,0x25,0xF9,0xDF,0x26,0xE4,0xDC, + 0xFB,0xB1,0x84,0x84,0x77,0x1B,0x45,0x51, + 0x60,0xD5,0xF0,0xB6,0x09,0xE6,0xBC,0xE3, + 0x1C,0x70,0x96,0x2C,0xD3,0x9D,0x7D,0x7F, + 0xB1,0x70,0xDA,0x79,0xB8,0x74,0x99,0xBF, + 0x84,0x95,0xCC,0x93,0xD7,0x51,0xDD,0x66, + 0xD3,0x70,0x0C,0x75,0x86,0x09,0x06,0xFD, + 0x66,0x14,0x80,0xCD,0xF3,0x59,0xB4,0x92, + 0x5F,0xE4,0xEE,0x00,0xA8,0xB0,0x8B,0x5C, + 0x3E,0xDB,0x8A,0x9C,0x0B,0xB5,0x99,0xC2, + 0x0D,0x81,0x09,0x06,0x6C,0x28,0xC0,0x7E, + 0xA5,0x07,0x70,0x64,0xD7,0x41,0xF4,0xC3, + 0x66,0x61,0x1C,0xA8,0x51,0xF6,0x3C,0xBA, + 0xE0,0x94,0xA3,0x11,0x8C,0x2E,0xBA,0x13, + 0xB2,0x47,0x48,0x93,0xB4,0x1A,0x2C,0x9A, + 0x6E,0x8E,0x30,0x66,0x7B,0xD3,0xBB,0x3B, + 0x5D,0x97,0x0D,0xE4,0xEA,0x24,0x28,0x9E, + 0xB4,0x88,0xCE,0x1D,0x7D,0x6F,0x39,0xB3, + 0x87,0x21,0xE5,0x08,0x93,0xF0,0xD4,0x9D, + 0x2D,0x91,0xC9,0xFD,0x0C,0x74,0x34,0xB4, + 0x1F,0xFE,0xDA,0xDC,0x10,0x5B,0x8D,0x2B, + 0x87,0xD3,0x42,0xB4,0xAE,0x32,0x9C,0xAE, + 0x4C,0x99,0xD8,0xED,0x44,0x41,0x07,0xE0, + 0x8F,0xBD,0xA5,0x7C,0x5A,0xDF,0x91,0x29, + 0x00,0xB5,0x4B,0xC3,0x3A,0x40,0x6C,0x48, + 0xAB,0x2A,0xF3,0x02,0xCB,0xB3,0x69,0xDA, + 0x06,0x0C,0x4D,0x5C,0x45,0xC3,0x28,0xAC, + 0x7A,0x01,0xD4,0xF8,0xCB,0x07,0x63,0x89, + 0x09,0x34,0x78,0xA7,0x14,0x39,0xCF,0x2D, + 0x94,0x8D,0x7A,0x4E,0x4E,0xBD,0xC4,0x32, + 0xAB,0x21,0xC9,0xDA,0x3F,0x5F,0x04,0x6B, + 0x14,0x40,0x18,0x18,0x2F,0xF9,0x46,0x17, + 0x57,0x54,0x9B,0x28,0x7B,0xBD,0xF9,0xA2, + 0x13,0xAC,0x69,0x24,0xB1,0x31,0x39,0xBF, + 0x8D,0x75,0xC3,0xFD,0x03,0x54,0x5A,0xFD, + 0xD4,0x7A,0xB7,0x56,0x4F,0x66,0x43,0x57, + 0x1B,0xFB,0xF9,0x92,0x7A,0x83,0xE6,0xFF, + 0xB4,0xBA,0x83,0xD2,0x61,0x8E,0x4A,0x82, + 0x82,0xA8,0xF5,0x0C,0xD2,0x43,0x53,0xA8, + 0x85,0x0A,0xD4,0x69,0x7B,0x04,0x71,0x3B, + 0x80,0x49,0x27,0x47,0x12,0xB6,0xB0,0xEA, + 0x90,0x0A,0xFA,0xA8,0xC8,0x78,0x61,0xDE, + 0x30,0x12,0xBB,0xDC,0xA6,0x57,0x56,0x30, + 0x6E,0xF1,0xA8,0x3B,0xF6,0x09,0x07,0xEA, + 0x31,0xE2,0x08,0x23,0x31,0x0F,0xD4,0x34, + 0xE3,0x60,0xC2,0x2B,0xDB,0x5A,0x99,0xCF, + 0xD4,0x6B,0x4E,0x75,0x65,0x35,0xE8,0x8B, + 0x93,0x7D,0xCA,0x11,0x47,0xF0,0x3E,0x11, + 0x5C,0xD1,0xEE,0x4B,0x11,0xB4,0x65,0x2B, + 0x6B,0x79,0xC0,0x86,0x60,0xA4,0x4B,0x24, + 0xA0,0x5C,0x70,0x34,0xC3,0x7C,0xE7,0x4F, + 0x97,0x89,0x4D,0xFE,0x22,0x89,0x3A,0xE9, + 0x07,0xB9,0x1A,0x86,0xB8,0x7A,0x12,0x38, + 0xE1,0x24,0x46,0xBC,0x9B,0x21,0xCD,0xAC, + 0x30,0xAB,0x98,0x21,0x31,0xC5,0x17,0x3F, + 0x1E,0x56,0xC3,0x18,0xCE,0xF0,0xA1,0xCC, + 0xFF,0x9D,0xA8,0x53,0xAF,0x74,0x77,0x54, + 0x02,0x9A,0x8F,0xA4,0xD4,0xBD,0xB2,0x1A, + 0xBA,0x52,0x2E,0x19,0xBE,0x49,0x11,0x45, + 0x02,0x01,0x7A,0xBF,0x28,0xD6,0x18,0xED, + 0xBD,0xCE,0xE4,0xDE,0xB5,0xF1,0x53,0x5D, + 0x65,0xF9,0x5F,0x83,0x8F,0x2D,0xF2,0x82, + 0xA0,0x2D,0x28,0xD3,0x0A,0x9E,0x0F,0x7F, + 0xC7,0xC4,0x43,0x7F,0xC3,0x0E,0x06,0xEB, + 0x4E,0xB4,0x2D,0xFA,0xDD,0x48,0xAB,0xF4, + 0x7D,0x41,0x48,0x33,0x5A,0xE6,0x70,0x02, + 0xE7,0x71,0x8D,0xD9,0x6B,0x0C,0x5A,0x8F, + 0xA4,0xC1,0xB7,0x4E,0x96,0x83,0xD6,0xA7, + 0x1D,0xF1,0x88,0xB3,0x6E,0xF4,0x12,0xA9, + 0xF6,0x31,0x69,0x66,0xFE,0xFE,0x02,0xF2, + 0x86,0x6D,0xBB,0x57,0x51,0x8C,0x4C,0xE9, + 0x7C,0x92,0x3E,0x3A,0xD3,0x2D,0xA8,0x82, + 0x53,0x84,0x26,0x89,0xBB,0xCC,0x13,0x12, + 0x3D,0x94,0xBB,0xDF,0x3D,0x4C,0xDF,0x27, + 0x9B,0x1F,0xB8,0xB6,0xE4,0xEA,0xA2,0x07, + 0xF8,0x4D,0x42,0x8F,0x29,0x90,0xFE,0x21, + 0x20,0xE9,0x55,0x02,0xAD,0x90,0xA7,0x77, + 0x4E,0x29,0xB6,0xD9,0x14,0x94,0xB2,0x25, + 0xA4,0xB2,0x0E,0x96,0x31,0xAB,0x9E,0x93, + 0x49,0xAC,0xA9,0xCB,0x68,0x22,0xBA,0xB8, + 0x57,0x5C,0x9D,0x65,0xC1,0xF1,0xFC,0x99, + 0x7C,0x3C,0xE9,0xEA,0x4B,0x29,0x22,0x2F, + 0xDB,0x17,0x21,0x8D,0xB0,0x13,0xBF,0xEE, + 0x7D,0xE4,0x8B,0x6D,0x17,0xE0,0x53,0x92, + 0x0B,0x32,0x6B,0xB1,0x65,0x2E,0xA7,0x83, + 0xFD,0x62,0x62,0xE3,0xAA,0x81,0xE8,0xD6, + 0xF7,0xB1,0x30,0x65,0x80,0x9F,0x77,0x1E, + 0x4A,0xEA,0xE8,0x45,0x32,0x12,0x3A,0xFB, + 0x22,0xE9,0xA9,0xF6,0xCB,0xAB,0xA8,0x0C, + 0x20,0xA8,0x7C,0xF9,0xF7,0x53,0xC1,0xB4, + 0xC0,0x5D,0x06,0x45,0xDD,0x7E,0xA7,0x34, + 0xA1,0x21,0xC2,0x62,0xAB,0x22,0x45,0x3D, + 0x73,0x4C,0x26,0xD1,0x1A,0xB2,0xF0,0xB2, + 0x6D,0x11,0x70,0x58,0xAA,0xF5,0xA4,0xF5, + 0xF8,0x0B,0x3D,0xC1,0xF6,0x17,0x70,0x15, + 0xCD,0x72,0x02,0x7E,0x4E,0x94,0x96,0x0A, + 0x56,0xCC,0xA5,0xA3,0xB3,0x7E,0xDD,0x5A, + 0x72,0xD2,0xFB,0xAC,0x3D,0x0E,0x66,0x65, + 0xE9,0x08,0x6C,0xB0,0x1C,0xE2,0x1A,0x82, + 0xF6,0xF3,0x34,0x89,0x73,0x02,0x5B,0x42, + 0x6D,0x40,0x61,0xB6,0xE0,0xE6,0x53,0x32, + 0xA5,0x72,0x17,0x4F,0x3B,0x51,0x4F,0xBC, + 0x00,0xE0,0x69,0x26,0xA9,0xAE,0x83,0xE3, + 0x73,0x7F,0x71,0x97,0xE0,0xDC,0x7C,0x63, + 0x9C,0x85,0x5F,0xDF,0x7D,0xE4,0x6C,0xD8, + 0xA9,0x3A,0x6F,0x5E,0x4A,0x2E,0xB0,0xE7, + 0x8B,0x45,0xE2,0x90,0x05,0x37,0xE8,0xAB, + 0x49,0x48,0x4C,0xC0,0x59,0x1D,0x8C,0x46, + 0x5B,0x84,0xE0,0x83,0xCE,0xEA,0x4B,0xF9, + 0xD4,0xDC,0x63,0xDF,0x79,0xB7,0x5C,0x11, + 0x25,0x7F,0x90,0x2E,0x0A,0x38,0x03,0xEA, + 0xEA,0xA1,0x26,0x52,0x20,0x19,0xA3,0xBE, + 0xFC,0x9D,0xB7,0x6E,0xA6,0x58,0x8E,0x6D, + 0xC5,0x58,0xE9,0xED,0x2F,0x55,0x43,0x8B, + 0x03,0x8B,0xE6,0xA4,0xC2,0x25,0x4B,0x36, + 0xBA,0xD3,0x27,0x48,0x40,0x2E,0x87,0xA2, + 0xD4,0x12,0xC6,0x05,0x36,0x03,0x11,0x51, + 0xD1,0xF2,0xAC,0x71,0x2C,0xB6,0xC3,0xA5, + 0x57,0x0F,0xAF,0x4B,0xBD,0xCD,0x47,0x4C, + 0x3A,0x52,0x6F,0x47,0xE7,0x0B,0xB7,0xD5, + 0xF7,0xA6,0x39,0x63,0x82,0x08,0x4C,0x41, + 0x0E,0x2A,0x52,0x42,0x5A,0xEA,0x59,0xC7, + 0x94,0xFB,0xD0,0x88,0x47,0x27,0xF6,0x97, + 0x03,0x9E,0x29,0xB8,0x3A,0x67,0xE6,0xF3, + 0x95,0xA7,0x42,0xC1,0x96,0xD1,0x9A,0xA6, + 0xF0,0x09,0x0C,0xEA,0xE0,0xAB,0x0F,0x15, + 0xE9,0xC3,0xEB,0xA5,0x89,0x86,0x98,0x32, + 0x83,0xAB,0x30,0x33,0xAE,0x90,0x8D,0x2E, + 0xB3,0xAA,0x91,0xA6,0xD9,0xA4,0x4A,0x54, + 0xE0,0xD3,0x08,0xCC,0x79,0xCE,0xE4,0x15, + 0x31,0xA6,0xCE,0x61,0xCF,0x03,0x06,0xEE, + 0x8E,0xE2,0x64,0x29,0xD1,0x54,0x9B,0xD0, + 0x5F,0x09,0x2B,0x8B,0xD5,0xF8,0xD4,0x7D, + 0xF1,0x97,0x32,0xD9,0xEA,0x5A,0x0E,0x10, + 0x8C,0x4D,0xFB,0x55,0xE6,0x27,0x0C,0xBA, + 0xC1,0x73,0xC1,0x73,0xE3,0x1C,0x09,0xB3, + 0x6F,0xB4,0x12,0xFA,0xF3,0x29,0xDC,0x23, + 0x32,0xED,0x80,0x87,0x83,0xC2,0xF6,0x07, + 0xB5,0xA9,0x22,0xDE,0x66,0x1A,0xA7,0x4A, + 0x86,0xF1,0x39,0x9B,0xF4,0xE7,0x50,0x15, + 0x4A,0x55,0x3C,0x93,0xB9,0xF9,0xFD,0xDC, + 0xB3,0x5D,0x73,0x52 +}; + +/* ------------------------------------------------------------------------- + * Helpers + * -------------------------------------------------------------------------*/ + +static int expect_status(const char *label, psa_status_t status, + psa_status_t expected) +{ + if (status != expected) { + printf("FAIL %s status=%d expected=%d\n", label, (int)status, + (int)expected); + return 1; + } + return 0; +} + +/* + * Import a public-key-only PQ key (LMS/HSS/XMSS) and return its id. + * bits=0 requests automatic inference from the blob length. + */ +static int import_pq_pubkey(const char *label, + psa_key_type_t key_type, + psa_algorithm_t alg, + psa_key_bits_t bits, + const uint8_t *pub, size_t pub_len, + psa_key_id_t *key_id_out) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_status_t st; + + psa_set_key_type(&attrs, key_type); + psa_set_key_algorithm(&attrs, alg); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_VERIFY_MESSAGE); + psa_set_key_bits(&attrs, bits); + + st = psa_import_key(&attrs, pub, pub_len, key_id_out); + if (expect_status(label, st, PSA_SUCCESS) != 0) { + return 1; + } + return 0; +} + +/* ------------------------------------------------------------------------- + * TC-1 / TC-2 / TC-3 / TC-4 / TC-5: HSS public key (60-byte blob) + * -------------------------------------------------------------------------*/ + +static int test_hss_import_bits_inference(void) +{ + psa_key_attributes_t got = psa_key_attributes_init(); + psa_key_id_t key_id = PSA_KEY_ID_NULL; + psa_status_t st; + + /* bits=0 → library infers 256 from 60-byte blob */ + if (import_pq_pubkey("hss import bits=0", + PSA_KEY_TYPE_HSS_PUBLIC_KEY, PSA_ALG_HSS, + 0, hss_pub, sizeof(hss_pub), &key_id) != 0) { + return 1; + } + + st = psa_get_key_attributes(key_id, &got); + if (expect_status("hss get_key_attributes", st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + if (psa_get_key_bits(&got) != 256) { + printf("FAIL hss bits=%u expected=256\n", + (unsigned)psa_get_key_bits(&got)); + (void)psa_destroy_key(key_id); + return 1; + } + (void)psa_destroy_key(key_id); + + /* bits=256 explicitly — must also succeed */ + if (import_pq_pubkey("hss import bits=256", + PSA_KEY_TYPE_HSS_PUBLIC_KEY, PSA_ALG_HSS, + 256, hss_pub, sizeof(hss_pub), &key_id) != 0) { + return 1; + } + + st = psa_get_key_attributes(key_id, &got); + if (expect_status("hss get_key_attributes bits=256", st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + if (psa_get_key_bits(&got) != 256) { + printf("FAIL hss explicit bits=%u expected=256\n", + (unsigned)psa_get_key_bits(&got)); + (void)psa_destroy_key(key_id); + return 1; + } + (void)psa_destroy_key(key_id); + + return 0; +} + +static int test_hss_verify_message_ok(void) +{ + psa_key_id_t key_id = PSA_KEY_ID_NULL; + psa_status_t st; + + if (import_pq_pubkey("hss import for verify", + PSA_KEY_TYPE_HSS_PUBLIC_KEY, PSA_ALG_HSS, + 0, hss_pub, sizeof(hss_pub), &key_id) != 0) { + return 1; + } + + st = psa_verify_message(key_id, PSA_ALG_HSS, + hss_msg, sizeof(hss_msg), + hss_sig, sizeof(hss_sig)); + if (expect_status("hss verify_message ok", st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + (void)psa_destroy_key(key_id); + return 0; +} + +static int test_hss_tamper_sig(void) +{ + psa_key_id_t key_id = PSA_KEY_ID_NULL; + psa_status_t st; + uint8_t sig_copy[HSS_SIG_LEN]; + + if (import_pq_pubkey("hss import for tamper-sig", + PSA_KEY_TYPE_HSS_PUBLIC_KEY, PSA_ALG_HSS, + 0, hss_pub, sizeof(hss_pub), &key_id) != 0) { + return 1; + } + + memcpy(sig_copy, hss_sig, HSS_SIG_LEN); + sig_copy[HSS_SIG_LEN / 2] ^= 0x01; + + st = psa_verify_message(key_id, PSA_ALG_HSS, + hss_msg, sizeof(hss_msg), + sig_copy, sizeof(sig_copy)); + if (expect_status("hss verify tampered-sig", st, + PSA_ERROR_INVALID_SIGNATURE) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + (void)psa_destroy_key(key_id); + return 0; +} + +static int test_hss_tamper_msg(void) +{ + psa_key_id_t key_id = PSA_KEY_ID_NULL; + psa_status_t st; + uint8_t msg_copy[sizeof(hss_msg)]; + + if (import_pq_pubkey("hss import for tamper-msg", + PSA_KEY_TYPE_HSS_PUBLIC_KEY, PSA_ALG_HSS, + 0, hss_pub, sizeof(hss_pub), &key_id) != 0) { + return 1; + } + + memcpy(msg_copy, hss_msg, sizeof(hss_msg)); + msg_copy[sizeof(hss_msg) / 2] ^= 0x01; + + st = psa_verify_message(key_id, PSA_ALG_HSS, + msg_copy, sizeof(msg_copy), + hss_sig, sizeof(hss_sig)); + if (expect_status("hss verify tampered-msg", st, + PSA_ERROR_INVALID_SIGNATURE) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + (void)psa_destroy_key(key_id); + return 0; +} + +static int test_hss_sign_message_rejected(void) +{ + psa_key_id_t key_id = PSA_KEY_ID_NULL; + uint8_t sig_buf[HSS_SIG_LEN]; + size_t sig_len = 0; + psa_status_t st; + + if (import_pq_pubkey("hss import for sign-msg", + PSA_KEY_TYPE_HSS_PUBLIC_KEY, PSA_ALG_HSS, + 0, hss_pub, sizeof(hss_pub), &key_id) != 0) { + return 1; + } + + st = psa_sign_message(key_id, PSA_ALG_HSS, + hss_msg, sizeof(hss_msg), + sig_buf, sizeof(sig_buf), &sig_len); + if (st == PSA_SUCCESS) { + printf("FAIL hss sign_message returned PSA_SUCCESS (expected error)\n"); + (void)psa_destroy_key(key_id); + return 1; + } + printf("INFO hss sign_message correctly rejected, status=%d " + "(PSA_ERROR_NOT_PERMITTED=%d PSA_ERROR_INVALID_ARGUMENT=%d)\n", + (int)st, (int)PSA_ERROR_NOT_PERMITTED, + (int)PSA_ERROR_INVALID_ARGUMENT); + + (void)psa_destroy_key(key_id); + return 0; +} + +static int test_hss_verify_hash_rejected(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key_id = PSA_KEY_ID_NULL; + /* 32-byte synthetic "hash" — content irrelevant, just needs a buffer */ + static const uint8_t fake_hash[32] = { 0 }; + psa_status_t st; + + /* Import with VERIFY_HASH so the usage check passes and the + * LMS-specific PSA_ERROR_INVALID_ARGUMENT guard is reached. */ + psa_set_key_type(&attrs, PSA_KEY_TYPE_HSS_PUBLIC_KEY); + psa_set_key_algorithm(&attrs, PSA_ALG_HSS); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_VERIFY_MESSAGE | + PSA_KEY_USAGE_VERIFY_HASH); + st = psa_import_key(&attrs, hss_pub, sizeof(hss_pub), &key_id); + if (expect_status("hss import for verify-hash", st, PSA_SUCCESS) != 0) { + return 1; + } + + st = psa_verify_hash(key_id, PSA_ALG_HSS, + fake_hash, sizeof(fake_hash), + hss_sig, sizeof(hss_sig)); + if (expect_status("hss verify_hash rejected", + st, PSA_ERROR_INVALID_ARGUMENT) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + (void)psa_destroy_key(key_id); + return 0; +} + +/* TC-7: psa_generate_key for HSS public key type must fail */ +static int test_hss_generate_key_fails(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key_id = PSA_KEY_ID_NULL; + psa_status_t st; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_HSS_PUBLIC_KEY); + psa_set_key_algorithm(&attrs, PSA_ALG_HSS); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_VERIFY_MESSAGE); + psa_set_key_bits(&attrs, 256); + + st = psa_generate_key(&attrs, &key_id); + if (st == PSA_SUCCESS) { + printf("FAIL hss generate_key returned PSA_SUCCESS\n"); + (void)psa_destroy_key(key_id); + return 1; + } + return 0; +} + +/* ------------------------------------------------------------------------- + * TC-1 / TC-2 / TC-3 / TC-4 / TC-5 / TC-6: XMSS public key (68-byte blob) + * -------------------------------------------------------------------------*/ + +static int test_xmss_import_bits_inference(void) +{ + psa_key_attributes_t got = psa_key_attributes_init(); + psa_key_id_t key_id = PSA_KEY_ID_NULL; + psa_status_t st; + + /* bits=0 → library infers 256 from 68-byte blob */ + if (import_pq_pubkey("xmss import bits=0", + PSA_KEY_TYPE_XMSS_PUBLIC_KEY, PSA_ALG_XMSS, + 0, xmss_pub, sizeof(xmss_pub), &key_id) != 0) { + return 1; + } + + st = psa_get_key_attributes(key_id, &got); + if (expect_status("xmss get_key_attributes", st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + if (psa_get_key_bits(&got) != 256) { + printf("FAIL xmss bits=%u expected=256\n", + (unsigned)psa_get_key_bits(&got)); + (void)psa_destroy_key(key_id); + return 1; + } + (void)psa_destroy_key(key_id); + + /* bits=256 explicitly */ + if (import_pq_pubkey("xmss import bits=256", + PSA_KEY_TYPE_XMSS_PUBLIC_KEY, PSA_ALG_XMSS, + 256, xmss_pub, sizeof(xmss_pub), &key_id) != 0) { + return 1; + } + + st = psa_get_key_attributes(key_id, &got); + if (expect_status("xmss get_key_attributes bits=256", st, + PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + if (psa_get_key_bits(&got) != 256) { + printf("FAIL xmss explicit bits=%u expected=256\n", + (unsigned)psa_get_key_bits(&got)); + (void)psa_destroy_key(key_id); + return 1; + } + (void)psa_destroy_key(key_id); + + return 0; +} + +static int test_xmss_verify_message_ok(void) +{ + psa_key_id_t key_id = PSA_KEY_ID_NULL; + psa_status_t st; + + if (import_pq_pubkey("xmss import for verify", + PSA_KEY_TYPE_XMSS_PUBLIC_KEY, PSA_ALG_XMSS, + 0, xmss_pub, sizeof(xmss_pub), &key_id) != 0) { + return 1; + } + + st = psa_verify_message(key_id, PSA_ALG_XMSS, + xmss_msg, sizeof(xmss_msg), + xmss_sig, sizeof(xmss_sig)); + if (expect_status("xmss verify_message ok", st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + (void)psa_destroy_key(key_id); + return 0; +} + +static int test_xmss_tamper_sig(void) +{ + psa_key_id_t key_id = PSA_KEY_ID_NULL; + psa_status_t st; + uint8_t sig_copy[XMSS_SIG_LEN]; + + if (import_pq_pubkey("xmss import for tamper-sig", + PSA_KEY_TYPE_XMSS_PUBLIC_KEY, PSA_ALG_XMSS, + 0, xmss_pub, sizeof(xmss_pub), &key_id) != 0) { + return 1; + } + + memcpy(sig_copy, xmss_sig, XMSS_SIG_LEN); + sig_copy[XMSS_SIG_LEN / 2] ^= 0x01; + + st = psa_verify_message(key_id, PSA_ALG_XMSS, + xmss_msg, sizeof(xmss_msg), + sig_copy, sizeof(sig_copy)); + if (expect_status("xmss verify tampered-sig", st, + PSA_ERROR_INVALID_SIGNATURE) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + (void)psa_destroy_key(key_id); + return 0; +} + +static int test_xmss_tamper_msg(void) +{ + psa_key_id_t key_id = PSA_KEY_ID_NULL; + psa_status_t st; + uint8_t msg_copy[sizeof(xmss_msg)]; + + if (import_pq_pubkey("xmss import for tamper-msg", + PSA_KEY_TYPE_XMSS_PUBLIC_KEY, PSA_ALG_XMSS, + 0, xmss_pub, sizeof(xmss_pub), &key_id) != 0) { + return 1; + } + + memcpy(msg_copy, xmss_msg, sizeof(xmss_msg)); + msg_copy[sizeof(xmss_msg) / 2] ^= 0x01; + + st = psa_verify_message(key_id, PSA_ALG_XMSS, + msg_copy, sizeof(msg_copy), + xmss_sig, sizeof(xmss_sig)); + if (expect_status("xmss verify tampered-msg", st, + PSA_ERROR_INVALID_SIGNATURE) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + (void)psa_destroy_key(key_id); + return 0; +} + +static int test_xmss_sign_message_rejected(void) +{ + psa_key_id_t key_id = PSA_KEY_ID_NULL; + uint8_t sig_buf[XMSS_SIG_LEN]; + size_t sig_len = 0; + psa_status_t st; + + if (import_pq_pubkey("xmss import for sign-msg", + PSA_KEY_TYPE_XMSS_PUBLIC_KEY, PSA_ALG_XMSS, + 0, xmss_pub, sizeof(xmss_pub), &key_id) != 0) { + return 1; + } + + st = psa_sign_message(key_id, PSA_ALG_XMSS, + xmss_msg, sizeof(xmss_msg), + sig_buf, sizeof(sig_buf), &sig_len); + if (st == PSA_SUCCESS) { + printf("FAIL xmss sign_message returned PSA_SUCCESS (expected error)\n"); + (void)psa_destroy_key(key_id); + return 1; + } + printf("INFO xmss sign_message correctly rejected, status=%d " + "(PSA_ERROR_NOT_PERMITTED=%d PSA_ERROR_INVALID_ARGUMENT=%d)\n", + (int)st, (int)PSA_ERROR_NOT_PERMITTED, + (int)PSA_ERROR_INVALID_ARGUMENT); + + (void)psa_destroy_key(key_id); + return 0; +} + +static int test_xmss_verify_hash_rejected(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key_id = PSA_KEY_ID_NULL; + static const uint8_t fake_hash[32] = { 0 }; + psa_status_t st; + + /* Import with VERIFY_HASH so the usage check passes and the + * XMSS-specific PSA_ERROR_INVALID_ARGUMENT guard is reached. */ + psa_set_key_type(&attrs, PSA_KEY_TYPE_XMSS_PUBLIC_KEY); + psa_set_key_algorithm(&attrs, PSA_ALG_XMSS); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_VERIFY_MESSAGE | + PSA_KEY_USAGE_VERIFY_HASH); + st = psa_import_key(&attrs, xmss_pub, sizeof(xmss_pub), &key_id); + if (expect_status("xmss import for verify-hash", st, PSA_SUCCESS) != 0) { + return 1; + } + + st = psa_verify_hash(key_id, PSA_ALG_XMSS, + fake_hash, sizeof(fake_hash), + xmss_sig, sizeof(xmss_sig)); + if (expect_status("xmss verify_hash rejected", + st, PSA_ERROR_INVALID_ARGUMENT) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + (void)psa_destroy_key(key_id); + return 0; +} + +/* TC-7: psa_generate_key for XMSS public key type must fail */ +static int test_xmss_generate_key_fails(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key_id = PSA_KEY_ID_NULL; + psa_status_t st; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_XMSS_PUBLIC_KEY); + psa_set_key_algorithm(&attrs, PSA_ALG_XMSS); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_VERIFY_MESSAGE); + psa_set_key_bits(&attrs, 256); + + st = psa_generate_key(&attrs, &key_id); + if (st == PSA_SUCCESS) { + printf("FAIL xmss generate_key returned PSA_SUCCESS\n"); + (void)psa_destroy_key(key_id); + return 1; + } + return 0; +} + +/* ------------------------------------------------------------------------- + * main + * -------------------------------------------------------------------------*/ + +int main(void) +{ + psa_status_t st; + + st = psa_crypto_init(); + if (expect_status("psa_crypto_init", st, PSA_SUCCESS) != 0) { + return 1; + } + + /* --- HSS tests --- */ + + if (test_hss_import_bits_inference() != 0) { + return 1; + } + + if (test_hss_verify_message_ok() != 0) { + return 1; + } + + if (test_hss_tamper_sig() != 0) { + return 1; + } + + if (test_hss_tamper_msg() != 0) { + return 1; + } + + if (test_hss_sign_message_rejected() != 0) { + return 1; + } + + if (test_hss_verify_hash_rejected() != 0) { + return 1; + } + + if (test_hss_generate_key_fails() != 0) { + return 1; + } + + /* --- XMSS tests --- */ + + if (test_xmss_import_bits_inference() != 0) { + return 1; + } + + if (test_xmss_verify_message_ok() != 0) { + return 1; + } + + if (test_xmss_tamper_sig() != 0) { + return 1; + } + + if (test_xmss_tamper_msg() != 0) { + return 1; + } + + if (test_xmss_sign_message_rejected() != 0) { + return 1; + } + + if (test_xmss_verify_hash_rejected() != 0) { + return 1; + } + + if (test_xmss_generate_key_fails() != 0) { + return 1; + } + + printf("PSA LMS/XMSS verify test: OK\n"); + return 0; +} diff --git a/test/psa_server/psa_mldsa_test.c b/test/psa_server/psa_mldsa_test.c new file mode 100644 index 0000000..bb5d871 --- /dev/null +++ b/test/psa_server/psa_mldsa_test.c @@ -0,0 +1,688 @@ +/* psa_mldsa_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 + */ + +/* Coverage tests for the PSA Crypto API 1.4 ML-DSA (FIPS 204) interface. + * + * Cases: + * 1. generate_key / export seed / export_public_key sizes (all three param sets) + * 2. sign_message + verify_message roundtrip; tamper -> INVALID_SIGNATURE + * 3. Import public key with bits=0 (inference from length); verify with it + * 4. Deterministic vs hedged: same message, deterministic produces identical + * signatures, hedged produces different ones + * 5. HashML-DSA(SHA-256): sign_message + verify_message; cross-path + * verify_hash (caller-hashes) and sign_hash + verify_message + * 6. psa_sign_hash with PSA_ALG_ML_DSA -> PSA_ERROR_INVALID_ARGUMENT + * 7. Import seed with bits=0 -> PSA_ERROR_INVALID_ARGUMENT; with bits=192 -> + * success, sign/verify roundtrip works + * 8. Import seed with wrong length (31 bytes, bits=192) -> + * PSA_ERROR_INVALID_ARGUMENT + */ + +#include "psa_api_test_user_settings.h" + +#ifndef WOLFSSL_USER_SETTINGS +#define WOLFSSL_USER_SETTINGS +#endif + +#include + +#include +#include +#include + +#include + +/* ------------------------------------------------------------------------- + * Helpers (mirrors the style of psa_api_test.c) + * ---------------------------------------------------------------------- */ + +static int check_status(psa_status_t st, const char *what) +{ + if (st != PSA_SUCCESS) { + printf("FAIL: %s (status=%d)\n", what, (int)st); + return 1; + } + return 0; +} + +static int check_status_eq(psa_status_t st, psa_status_t expected, + const char *what) +{ + if (st != expected) { + printf("FAIL: %s (status=%d expected=%d)\n", what, (int)st, + (int)expected); + return 1; + } + return 0; +} + +static int check_true(int cond, const char *what) +{ + if (!cond) { + printf("FAIL: %s\n", what); + return 1; + } + return 0; +} + +/* ------------------------------------------------------------------------- + * Per-parameter-set constants + * ---------------------------------------------------------------------- */ + +/* ML-DSA-44 / ML-DSA-65 / ML-DSA-87 */ +static const size_t g_pub_sizes[3] = { 1312u, 1952u, 2592u }; +static const size_t g_sig_sizes[3] = { 2420u, 3309u, 4627u }; +static const size_t g_bits[3] = { 128u, 192u, 256u }; + +/* ------------------------------------------------------------------------- + * Reusable volatile-key attribute builder + * ---------------------------------------------------------------------- */ + +static void make_mldsa_keypair_attrs(psa_key_attributes_t *attrs, + size_t bits, psa_algorithm_t alg, + psa_key_usage_t usage) +{ + *attrs = psa_key_attributes_init(); + psa_set_key_type(attrs, PSA_KEY_TYPE_ML_DSA_KEY_PAIR); + psa_set_key_bits(attrs, (psa_key_bits_t)bits); + psa_set_key_algorithm(attrs, alg); + psa_set_key_usage_flags(attrs, usage); + /* No psa_set_key_id / psa_set_key_lifetime -> volatile (default) */ +} + +/* ------------------------------------------------------------------------- + * Case 1: generate_key sizes + * For each parameter set: export seed (32 bytes), export public key (1312/1952/2592). + * ---------------------------------------------------------------------- */ + +static int test_generate_key_sizes(void) +{ +#if !defined(WOLFSSL_HAVE_MLDSA) + printf("SKIP: test_generate_key_sizes (WOLFSSL_HAVE_MLDSA not set)\n"); + return 0; +#else + static const psa_key_usage_t usage = + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE | + PSA_KEY_USAGE_EXPORT; + int i; + + for (i = 0; i < 3; i++) { + psa_key_attributes_t attrs; + psa_key_id_t key = PSA_KEY_ID_NULL; + uint8_t seed[64]; + uint8_t pub[2592]; + size_t seed_len = 0, pub_len = 0; + psa_status_t st; + char label[64]; + + snprintf(label, sizeof(label), "generate_key ML-DSA-%u", + (unsigned)g_bits[i]); + + make_mldsa_keypair_attrs(&attrs, g_bits[i], PSA_ALG_ML_DSA, usage); + st = psa_generate_key(&attrs, &key); + if (check_status(st, label) != 0) + return 1; + + snprintf(label, sizeof(label), "export_key seed ML-DSA-%u", + (unsigned)g_bits[i]); + st = psa_export_key(key, seed, sizeof(seed), &seed_len); + if (check_status(st, label) != 0) { + (void)psa_destroy_key(key); + return 1; + } + snprintf(label, sizeof(label), "seed length ML-DSA-%u", + (unsigned)g_bits[i]); + if (check_true(seed_len == 32u, label) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + snprintf(label, sizeof(label), "export_public_key ML-DSA-%u", + (unsigned)g_bits[i]); + st = psa_export_public_key(key, pub, sizeof(pub), &pub_len); + if (check_status(st, label) != 0) { + (void)psa_destroy_key(key); + return 1; + } + snprintf(label, sizeof(label), "public key length ML-DSA-%u", + (unsigned)g_bits[i]); + if (check_true(pub_len == g_pub_sizes[i], label) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + (void)psa_destroy_key(key); + } + + printf("PASS: test_generate_key_sizes\n"); + return 0; +#endif +} + +/* ------------------------------------------------------------------------- + * Case 2: sign_message / verify_message roundtrip + tamper + * ---------------------------------------------------------------------- */ + +static int test_sign_verify_message(void) +{ +#if !defined(WOLFSSL_HAVE_MLDSA) + printf("SKIP: test_sign_verify_message (WOLFSSL_HAVE_MLDSA not set)\n"); + return 0; +#else + static const psa_key_usage_t usage = + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE | + PSA_KEY_USAGE_EXPORT; + static const uint8_t msg[] = "PSA ML-DSA test message"; + int i; + + for (i = 0; i < 3; i++) { + psa_key_attributes_t attrs; + psa_key_id_t key = PSA_KEY_ID_NULL; + /* ML-DSA-87 signature is 4627 bytes — allocate for the largest */ + uint8_t sig[4627]; + size_t sig_len = 0; + psa_status_t st; + char label[64]; + + snprintf(label, sizeof(label), "sign_message ML-DSA-%u", + (unsigned)g_bits[i]); + + make_mldsa_keypair_attrs(&attrs, g_bits[i], PSA_ALG_ML_DSA, usage); + st = psa_generate_key(&attrs, &key); + if (check_status(st, label) != 0) + return 1; + + st = psa_sign_message(key, PSA_ALG_ML_DSA, msg, sizeof(msg) - 1, + sig, sizeof(sig), &sig_len); + if (check_status(st, label) != 0) { + (void)psa_destroy_key(key); + return 1; + } + snprintf(label, sizeof(label), "signature length ML-DSA-%u", + (unsigned)g_bits[i]); + if (check_true(sig_len == g_sig_sizes[i], label) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + snprintf(label, sizeof(label), "verify_message ML-DSA-%u", + (unsigned)g_bits[i]); + st = psa_verify_message(key, PSA_ALG_ML_DSA, msg, sizeof(msg) - 1, + sig, sig_len); + if (check_status(st, label) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + /* Tamper: flip a bit in the middle of the signature */ + sig[sig_len / 2] ^= 0x01u; + snprintf(label, sizeof(label), "verify_message tampered ML-DSA-%u", + (unsigned)g_bits[i]); + st = psa_verify_message(key, PSA_ALG_ML_DSA, msg, sizeof(msg) - 1, + sig, sig_len); + if (check_status_eq(st, PSA_ERROR_INVALID_SIGNATURE, label) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + (void)psa_destroy_key(key); + } + + printf("PASS: test_sign_verify_message\n"); + return 0; +#endif +} + +/* ------------------------------------------------------------------------- + * Case 3: Import public key (bits=0, infer from length), verify with it + * ---------------------------------------------------------------------- */ + +static int test_import_public_key(void) +{ +#if !defined(WOLFSSL_HAVE_MLDSA) + printf("SKIP: test_import_public_key (WOLFSSL_HAVE_MLDSA not set)\n"); + return 0; +#else + /* Use the middle parameter set (ML-DSA-65, bits=192) for this test */ + static const size_t idx = 1; /* bits=192, pub=1952, sig=3309 */ + static const psa_key_usage_t usage_kp = + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE | + PSA_KEY_USAGE_EXPORT; + static const uint8_t msg[] = "ML-DSA public key import test"; + + psa_key_attributes_t kp_attrs; + psa_key_id_t kp = PSA_KEY_ID_NULL; + psa_key_id_t pub_key = PSA_KEY_ID_NULL; + uint8_t pub[2592]; + size_t pub_len = 0; + uint8_t sig[4627]; + size_t sig_len = 0; + psa_key_attributes_t pub_attrs; + psa_key_attributes_t got_attrs; + psa_status_t st; + + make_mldsa_keypair_attrs(&kp_attrs, g_bits[idx], PSA_ALG_ML_DSA, + usage_kp); + st = psa_generate_key(&kp_attrs, &kp); + if (check_status(st, "generate key for public import test") != 0) + return 1; + + st = psa_sign_message(kp, PSA_ALG_ML_DSA, msg, sizeof(msg) - 1, + sig, sizeof(sig), &sig_len); + if (check_status(st, "sign_message for public import test") != 0) { + (void)psa_destroy_key(kp); + return 1; + } + + st = psa_export_public_key(kp, pub, sizeof(pub), &pub_len); + if (check_status(st, "export_public_key for import test") != 0) { + (void)psa_destroy_key(kp); + return 1; + } + (void)psa_destroy_key(kp); + kp = PSA_KEY_ID_NULL; + + /* Import public key with bits=0: library must infer the parameter set + * from the buffer length (1952 bytes -> ML-DSA-65 -> bits=192). */ + pub_attrs = psa_key_attributes_init(); + psa_set_key_type(&pub_attrs, PSA_KEY_TYPE_ML_DSA_PUBLIC_KEY); + psa_set_key_bits(&pub_attrs, 0); + psa_set_key_algorithm(&pub_attrs, PSA_ALG_ML_DSA); + psa_set_key_usage_flags(&pub_attrs, PSA_KEY_USAGE_VERIFY_MESSAGE); + + st = psa_import_key(&pub_attrs, pub, pub_len, &pub_key); + if (check_status(st, "import ML_DSA_PUBLIC_KEY bits=0") != 0) + return 1; + + /* Verify the bits were inferred correctly */ + got_attrs = psa_key_attributes_init(); + st = psa_get_key_attributes(pub_key, &got_attrs); + if (check_status(st, "get_key_attributes after public import") != 0) { + (void)psa_destroy_key(pub_key); + return 1; + } + if (check_true(psa_get_key_bits(&got_attrs) == (psa_key_bits_t)g_bits[idx], + "inferred bits from public key length") != 0) { + (void)psa_destroy_key(pub_key); + return 1; + } + + /* Verify the signature produced by the key pair */ + st = psa_verify_message(pub_key, PSA_ALG_ML_DSA, msg, sizeof(msg) - 1, + sig, sig_len); + if (check_status(st, "verify_message with imported public key") != 0) { + (void)psa_destroy_key(pub_key); + return 1; + } + + (void)psa_destroy_key(pub_key); + printf("PASS: test_import_public_key\n"); + return 0; +#endif +} + +/* ------------------------------------------------------------------------- + * Case 4: Deterministic produces identical signatures; hedged produces different + * ---------------------------------------------------------------------- */ + +static int test_deterministic_vs_hedged(void) +{ +#if !defined(WOLFSSL_HAVE_MLDSA) + printf("SKIP: test_deterministic_vs_hedged (WOLFSSL_HAVE_MLDSA not set)\n"); + return 0; +#else + static const psa_key_usage_t usage = + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE; + static const uint8_t msg[] = "determinism test message"; + + psa_key_attributes_t attrs; + psa_key_id_t det_key = PSA_KEY_ID_NULL; + psa_key_id_t hed_key = PSA_KEY_ID_NULL; + uint8_t sig1[3309], sig2[3309]; + size_t sig1_len = 0, sig2_len = 0; + psa_status_t st; + + /* Deterministic key (bits=192) */ + make_mldsa_keypair_attrs(&attrs, 192u, PSA_ALG_DETERMINISTIC_ML_DSA, + usage); + st = psa_generate_key(&attrs, &det_key); + if (check_status(st, "generate deterministic ML-DSA-65 key") != 0) + return 1; + + st = psa_sign_message(det_key, PSA_ALG_DETERMINISTIC_ML_DSA, + msg, sizeof(msg) - 1, sig1, sizeof(sig1), &sig1_len); + if (check_status(st, "deterministic sign #1") != 0) { + (void)psa_destroy_key(det_key); + return 1; + } + + st = psa_sign_message(det_key, PSA_ALG_DETERMINISTIC_ML_DSA, + msg, sizeof(msg) - 1, sig2, sizeof(sig2), &sig2_len); + if (check_status(st, "deterministic sign #2") != 0) { + (void)psa_destroy_key(det_key); + return 1; + } + + if (check_true(sig1_len == sig2_len, "deterministic sig lengths match") != 0) { + (void)psa_destroy_key(det_key); + return 1; + } + if (check_true(memcmp(sig1, sig2, sig1_len) == 0, + "deterministic signatures are identical") != 0) { + (void)psa_destroy_key(det_key); + return 1; + } + (void)psa_destroy_key(det_key); + det_key = PSA_KEY_ID_NULL; + + /* Hedged key (bits=192): two signatures over the same message must DIFFER */ + make_mldsa_keypair_attrs(&attrs, 192u, PSA_ALG_ML_DSA, usage); + st = psa_generate_key(&attrs, &hed_key); + if (check_status(st, "generate hedged ML-DSA-65 key") != 0) + return 1; + + sig1_len = 0; + st = psa_sign_message(hed_key, PSA_ALG_ML_DSA, + msg, sizeof(msg) - 1, sig1, sizeof(sig1), &sig1_len); + if (check_status(st, "hedged sign #1") != 0) { + (void)psa_destroy_key(hed_key); + return 1; + } + + sig2_len = 0; + st = psa_sign_message(hed_key, PSA_ALG_ML_DSA, + msg, sizeof(msg) - 1, sig2, sizeof(sig2), &sig2_len); + if (check_status(st, "hedged sign #2") != 0) { + (void)psa_destroy_key(hed_key); + return 1; + } + + if (check_true(sig1_len == sig2_len, + "hedged sig lengths match") != 0) { + (void)psa_destroy_key(hed_key); + return 1; + } + /* Hedged uses randomness: signatures must differ. + * (If the two happen to collide the PRNG is broken.) */ + if (check_true(memcmp(sig1, sig2, sig1_len) != 0, + "hedged signatures differ") != 0) { + (void)psa_destroy_key(hed_key); + return 1; + } + (void)psa_destroy_key(hed_key); + + printf("PASS: test_deterministic_vs_hedged\n"); + return 0; +#endif +} + +/* ------------------------------------------------------------------------- + * Case 5: HashML-DSA(SHA-256): cross-path interop + * a) sign_message -> verify_message + * b) psa_hash_compute(SHA-256) -> verify_hash with the same sig + * c) sign_hash -> verify_message + * ---------------------------------------------------------------------- */ + +static int test_hash_mldsa_interop(void) +{ +#if !defined(WOLFSSL_HAVE_MLDSA) + printf("SKIP: test_hash_mldsa_interop (WOLFSSL_HAVE_MLDSA not set)\n"); + return 0; +#else + static const psa_algorithm_t alg = + PSA_ALG_HASH_ML_DSA(PSA_ALG_SHA_256); + static const psa_key_usage_t usage = + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE | + PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH; + static const uint8_t msg[] = "HashML-DSA interop test message"; + + psa_key_attributes_t attrs; + psa_key_id_t key = PSA_KEY_ID_NULL; + uint8_t sig_msg[3309]; /* ML-DSA-65 max sig */ + uint8_t sig_hash[3309]; + size_t sig_msg_len = 0, sig_hash_len = 0; + uint8_t digest[32]; /* SHA-256 */ + size_t digest_len = 0; + psa_status_t st; + + make_mldsa_keypair_attrs(&attrs, 192u, alg, usage); + st = psa_generate_key(&attrs, &key); + if (check_status(st, "generate HashML-DSA-65 key") != 0) + return 1; + + /* 5a: sign_message -> verify_message */ + st = psa_sign_message(key, alg, msg, sizeof(msg) - 1, + sig_msg, sizeof(sig_msg), &sig_msg_len); + if (check_status(st, "HashML-DSA sign_message") != 0) { + (void)psa_destroy_key(key); + return 1; + } + st = psa_verify_message(key, alg, msg, sizeof(msg) - 1, + sig_msg, sig_msg_len); + if (check_status(st, "HashML-DSA verify_message") != 0) { + (void)psa_destroy_key(key); + return 1; + } + + /* 5b: compute digest, then verify_hash (caller-hashes path) */ + st = psa_hash_compute(PSA_ALG_SHA_256, msg, sizeof(msg) - 1, + digest, sizeof(digest), &digest_len); + if (check_status(st, "SHA-256 hash_compute for HashML-DSA") != 0) { + (void)psa_destroy_key(key); + return 1; + } + st = psa_verify_hash(key, alg, digest, digest_len, + sig_msg, sig_msg_len); + if (check_status(st, "HashML-DSA verify_hash (caller-hashed)") != 0) { + (void)psa_destroy_key(key); + return 1; + } + + /* 5c: sign_hash -> verify_message */ + st = psa_sign_hash(key, alg, digest, digest_len, + sig_hash, sizeof(sig_hash), &sig_hash_len); + if (check_status(st, "HashML-DSA sign_hash") != 0) { + (void)psa_destroy_key(key); + return 1; + } + st = psa_verify_message(key, alg, msg, sizeof(msg) - 1, + sig_hash, sig_hash_len); + if (check_status(st, "HashML-DSA verify_message after sign_hash") != 0) { + (void)psa_destroy_key(key); + return 1; + } + + (void)psa_destroy_key(key); + printf("PASS: test_hash_mldsa_interop\n"); + return 0; +#endif +} + +/* ------------------------------------------------------------------------- + * Case 6: psa_sign_hash with PSA_ALG_ML_DSA must return INVALID_ARGUMENT + * ---------------------------------------------------------------------- */ + +static int test_pure_mldsa_sign_hash_rejected(void) +{ +#if !defined(WOLFSSL_HAVE_MLDSA) + printf("SKIP: test_pure_mldsa_sign_hash_rejected (WOLFSSL_HAVE_MLDSA not set)\n"); + return 0; +#else + static const psa_key_usage_t usage = + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_SIGN_HASH; + /* A plausible 32-byte digest buffer */ + static const uint8_t digest[32] = { 0 }; + + psa_key_attributes_t attrs; + psa_key_id_t key = PSA_KEY_ID_NULL; + uint8_t sig[3309]; + size_t sig_len = 0; + psa_status_t st; + + make_mldsa_keypair_attrs(&attrs, 192u, PSA_ALG_ML_DSA, usage); + st = psa_generate_key(&attrs, &key); + if (check_status(st, "generate key for sign_hash rejection test") != 0) + return 1; + + st = psa_sign_hash(key, PSA_ALG_ML_DSA, + digest, sizeof(digest), + sig, sizeof(sig), &sig_len); + if (check_status_eq(st, PSA_ERROR_INVALID_ARGUMENT, + "sign_hash with PSA_ALG_ML_DSA") != 0) { + (void)psa_destroy_key(key); + return 1; + } + + (void)psa_destroy_key(key); + printf("PASS: test_pure_mldsa_sign_hash_rejected\n"); + return 0; +#endif +} + +/* ------------------------------------------------------------------------- + * Case 7: Import 32-byte seed + * 7a: bits=0 -> PSA_ERROR_INVALID_ARGUMENT (ambiguous without explicit bits) + * 7b: bits=192 -> success; sign/verify roundtrip + * ---------------------------------------------------------------------- */ + +static int test_import_seed(void) +{ +#if !defined(WOLFSSL_HAVE_MLDSA) + printf("SKIP: test_import_seed (WOLFSSL_HAVE_MLDSA not set)\n"); + return 0; +#else + static const psa_key_usage_t usage = + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE | + PSA_KEY_USAGE_EXPORT; + static const uint8_t msg[] = "import seed test"; + /* A fixed 32-byte seed for reproducibility */ + static const uint8_t seed[32] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20 + }; + + psa_key_attributes_t attrs; + psa_key_id_t key = PSA_KEY_ID_NULL; + psa_status_t st; + uint8_t sig[3309]; + size_t sig_len = 0; + + /* 7a: bits=0 must fail */ + attrs = psa_key_attributes_init(); + psa_set_key_type(&attrs, PSA_KEY_TYPE_ML_DSA_KEY_PAIR); + psa_set_key_bits(&attrs, 0); + psa_set_key_algorithm(&attrs, PSA_ALG_ML_DSA); + psa_set_key_usage_flags(&attrs, usage); + + st = psa_import_key(&attrs, seed, sizeof(seed), &key); + if (check_status_eq(st, PSA_ERROR_INVALID_ARGUMENT, + "import seed bits=0") != 0) { + if (key != PSA_KEY_ID_NULL) (void)psa_destroy_key(key); + return 1; + } + + /* 7b: bits=192 must succeed */ + make_mldsa_keypair_attrs(&attrs, 192u, PSA_ALG_ML_DSA, usage); + key = PSA_KEY_ID_NULL; + + st = psa_import_key(&attrs, seed, sizeof(seed), &key); + if (check_status(st, "import seed bits=192") != 0) + return 1; + + st = psa_sign_message(key, PSA_ALG_ML_DSA, msg, sizeof(msg) - 1, + sig, sizeof(sig), &sig_len); + if (check_status(st, "sign after import seed") != 0) { + (void)psa_destroy_key(key); + return 1; + } + st = psa_verify_message(key, PSA_ALG_ML_DSA, msg, sizeof(msg) - 1, + sig, sig_len); + if (check_status(st, "verify after import seed") != 0) { + (void)psa_destroy_key(key); + return 1; + } + + (void)psa_destroy_key(key); + printf("PASS: test_import_seed\n"); + return 0; +#endif +} + +/* ------------------------------------------------------------------------- + * Case 8: Import wrong-length seed (31 bytes, bits=192) -> INVALID_ARGUMENT + * ---------------------------------------------------------------------- */ + +static int test_import_wrong_seed_length(void) +{ +#if !defined(WOLFSSL_HAVE_MLDSA) + printf("SKIP: test_import_wrong_seed_length (WOLFSSL_HAVE_MLDSA not set)\n"); + return 0; +#else + static const psa_key_usage_t usage = + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE; + static const uint8_t short_seed[31] = { 0 }; /* 31 bytes, not 32 */ + + psa_key_attributes_t attrs; + psa_key_id_t key = PSA_KEY_ID_NULL; + psa_status_t st; + + make_mldsa_keypair_attrs(&attrs, 192u, PSA_ALG_ML_DSA, usage); + + st = psa_import_key(&attrs, short_seed, sizeof(short_seed), &key); + if (check_status_eq(st, PSA_ERROR_INVALID_ARGUMENT, + "import 31-byte seed bits=192") != 0) { + if (key != PSA_KEY_ID_NULL) (void)psa_destroy_key(key); + return 1; + } + + printf("PASS: test_import_wrong_seed_length\n"); + return 0; +#endif +} + +/* ------------------------------------------------------------------------- + * main + * ---------------------------------------------------------------------- */ + +int main(void) +{ + psa_status_t st; + + st = psa_crypto_init(); + if (st != PSA_SUCCESS) { + printf("FAIL: psa_crypto_init (status=%d)\n", (int)st); + return 1; + } + + if (test_generate_key_sizes() != 0) return 1; + if (test_sign_verify_message() != 0) return 1; + if (test_import_public_key() != 0) return 1; + if (test_deterministic_vs_hedged() != 0) return 1; + if (test_hash_mldsa_interop() != 0) return 1; + if (test_pure_mldsa_sign_hash_rejected() != 0) return 1; + if (test_import_seed() != 0) return 1; + if (test_import_wrong_seed_length() != 0) return 1; + + printf("PSA ML-DSA test: OK\n"); + return 0; +} diff --git a/test/psa_server/psa_mlkem_test.c b/test/psa_server/psa_mlkem_test.c new file mode 100644 index 0000000..70db0a1 --- /dev/null +++ b/test/psa_server/psa_mlkem_test.c @@ -0,0 +1,868 @@ +/* psa_mlkem_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 + */ + +/* Coverage test for the PSA Certified Crypto API 1.4 ML-KEM (CRYSTALS-Kyber) + * key-encapsulation interface implemented in wolfPSA. */ + +#include "psa_api_test_user_settings.h" + +#ifndef WOLFSSL_USER_SETTINGS +#define WOLFSSL_USER_SETTINGS +#endif + +#include + +#include +#include + +#include + +/* -------------------------------------------------------------------------- + * Internal helpers + * -------------------------------------------------------------------------- */ + +static int check_status(psa_status_t st, const char *what) +{ + if (st != PSA_SUCCESS) { + printf("FAIL: %s (status=%d)\n", what, (int)st); + return 1; + } + return 0; +} + +static int check_status_eq(psa_status_t st, psa_status_t expected, + const char *what) +{ + if (st != expected) { + printf("FAIL: %s status=%d expected=%d\n", what, (int)st, (int)expected); + return 1; + } + return 0; +} + +static int check_true(int cond, const char *what) +{ + if (!cond) { + printf("FAIL: %s\n", what); + return 1; + } + return 0; +} + +static int check_buf_eq(const char *what, + const uint8_t *a, const uint8_t *b, size_t sz) +{ + if (memcmp(a, b, sz) != 0) { + printf("FAIL: %s (mismatch)\n", what); + return 1; + } + return 0; +} + +static int check_buf_ne(const char *what, + const uint8_t *a, const uint8_t *b, size_t sz) +{ + if (memcmp(a, b, sz) == 0) { + printf("FAIL: %s (expected difference, got equal)\n", what); + return 1; + } + return 0; +} + +/* Shared-secret output-key attributes: DERIVE / EXPORT / HKDF(SHA-256) / 0b */ +static psa_key_attributes_t make_ss_attrs(void) +{ + psa_key_attributes_t a = psa_key_attributes_init(); + psa_set_key_type(&a, PSA_KEY_TYPE_DERIVE); + psa_set_key_usage_flags(&a, PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&a, PSA_ALG_HKDF(PSA_ALG_SHA_256)); + psa_set_key_bits(&a, 0); + return a; +} + +/* Export a key and return the length, -1 on error. */ +static int export_key(psa_key_id_t key_id, uint8_t *buf, size_t buf_size, + size_t *out_len, const char *label) +{ + psa_status_t st = psa_export_key(key_id, buf, buf_size, out_len); + if (st != PSA_SUCCESS) { + printf("FAIL: export_key(%s) status=%d\n", label, (int)st); + return -1; + } + return 0; +} + +/* -------------------------------------------------------------------------- + * Case 1: generate key pair, check seed and public-key export sizes. + * Covers bits in {512, 768, 1024}. + * -------------------------------------------------------------------------- */ +static int test_generate_and_export_sizes(void) +{ + static const struct { + size_t bits; + size_t pub_size; + } cases[] = { + { 512, 800 }, + { 768, 1184 }, + { 1024, 1568 }, + }; + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + size_t bits = cases[i].bits; + size_t pub_exp = cases[i].pub_size; + char label[64]; + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t kp = PSA_KEY_ID_NULL; + uint8_t seed_buf[64]; + uint8_t pub_buf[1568]; + size_t seed_len = 0; + size_t pub_len = 0; + psa_status_t st; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_ML_KEM_KEY_PAIR); + psa_set_key_bits(&attrs, bits); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&attrs, PSA_ALG_ML_KEM); + + snprintf(label, sizeof(label), "generate ML-KEM-%zu", bits); + st = psa_generate_key(&attrs, &kp); + if (check_status(st, label) != 0) return 1; + + snprintf(label, sizeof(label), "export_key seed ML-KEM-%zu", bits); + if (export_key(kp, seed_buf, sizeof(seed_buf), &seed_len, label) != 0) { + (void)psa_destroy_key(kp); + return 1; + } + snprintf(label, sizeof(label), "seed size ML-KEM-%zu", bits); + if (check_true(seed_len == 64, label) != 0) { + (void)psa_destroy_key(kp); + return 1; + } + + snprintf(label, sizeof(label), "export_public_key ML-KEM-%zu", bits); + st = psa_export_public_key(kp, pub_buf, sizeof(pub_buf), &pub_len); + if (check_status(st, label) != 0) { + (void)psa_destroy_key(kp); + return 1; + } + snprintf(label, sizeof(label), "public key size ML-KEM-%zu", bits); + if (check_true(pub_len == pub_exp, label) != 0) { + printf(" (got %zu, expected %zu)\n", pub_len, pub_exp); + (void)psa_destroy_key(kp); + return 1; + } + + (void)psa_destroy_key(kp); + } + + printf("PASS: test_generate_and_export_sizes\n"); + return 0; +} + +/* -------------------------------------------------------------------------- + * Case 2: full encap/decap round-trip; shared secrets must match. + * Also validates ciphertext lengths. + * -------------------------------------------------------------------------- */ +static int test_encap_decap_roundtrip(void) +{ + static const struct { + size_t bits; + size_t ct_size; + } cases[] = { + { 512, 768 }, + { 768, 1088 }, + { 1024, 1568 }, + }; + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + size_t bits = cases[i].bits; + size_t ct_exp = cases[i].ct_size; + char label[80]; + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_attributes_t ss_attrs; + psa_key_id_t kp = PSA_KEY_ID_NULL; + psa_key_id_t ss_enc = PSA_KEY_ID_NULL; + psa_key_id_t ss_dec = PSA_KEY_ID_NULL; + uint8_t ct[1568]; + size_t ct_len = 0; + uint8_t ss_enc_buf[32]; + uint8_t ss_dec_buf[32]; + size_t ss_enc_len = 0; + size_t ss_dec_len = 0; + psa_status_t st; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_ML_KEM_KEY_PAIR); + psa_set_key_bits(&attrs, bits); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&attrs, PSA_ALG_ML_KEM); + + st = psa_generate_key(&attrs, &kp); + snprintf(label, sizeof(label), "roundtrip generate ML-KEM-%zu", bits); + if (check_status(st, label) != 0) return 1; + + ss_attrs = make_ss_attrs(); + snprintf(label, sizeof(label), "encapsulate ML-KEM-%zu", bits); + st = psa_encapsulate(kp, PSA_ALG_ML_KEM, &ss_attrs, + &ss_enc, ct, sizeof(ct), &ct_len); + if (check_status(st, label) != 0) { + (void)psa_destroy_key(kp); + return 1; + } + + snprintf(label, sizeof(label), "ciphertext length ML-KEM-%zu", bits); + if (check_true(ct_len == ct_exp, label) != 0) { + printf(" (got %zu, expected %zu)\n", ct_len, ct_exp); + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(kp); + return 1; + } + + ss_attrs = make_ss_attrs(); + snprintf(label, sizeof(label), "decapsulate ML-KEM-%zu", bits); + st = psa_decapsulate(kp, PSA_ALG_ML_KEM, ct, ct_len, + &ss_attrs, &ss_dec); + if (check_status(st, label) != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(kp); + return 1; + } + + /* Export both secrets and compare. */ + snprintf(label, sizeof(label), "export ss_enc ML-KEM-%zu", bits); + if (export_key(ss_enc, ss_enc_buf, sizeof(ss_enc_buf), + &ss_enc_len, label) != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(kp); + return 1; + } + snprintf(label, sizeof(label), "export ss_dec ML-KEM-%zu", bits); + if (export_key(ss_dec, ss_dec_buf, sizeof(ss_dec_buf), + &ss_dec_len, label) != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(kp); + return 1; + } + + snprintf(label, sizeof(label), "ss length enc ML-KEM-%zu", bits); + if (check_true(ss_enc_len == 32, label) != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(kp); + return 1; + } + snprintf(label, sizeof(label), "ss length dec ML-KEM-%zu", bits); + if (check_true(ss_dec_len == 32, label) != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(kp); + return 1; + } + + snprintf(label, sizeof(label), "shared secrets equal ML-KEM-%zu", bits); + if (check_buf_eq(label, ss_enc_buf, ss_dec_buf, 32) != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(kp); + return 1; + } + + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(kp); + } + + printf("PASS: test_encap_decap_roundtrip\n"); + return 0; +} + +/* -------------------------------------------------------------------------- + * Case 3: import public key (bits=0, length inference), encap with it, + * decap with original key pair — secrets must match. + * -------------------------------------------------------------------------- */ +static int test_import_public_key_and_encap(void) +{ + static const struct { + size_t bits; + size_t pub_size; + size_t ct_size; + } cases[] = { + { 512, 800, 768 }, + { 768, 1184, 1088 }, + { 1024, 1568, 1568 }, + }; + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + size_t bits = cases[i].bits; + size_t pub_exp = cases[i].pub_size; + size_t ct_exp = cases[i].ct_size; + char label[80]; + psa_key_attributes_t kp_attrs = psa_key_attributes_init(); + psa_key_attributes_t pub_attrs = psa_key_attributes_init(); + psa_key_attributes_t got_attrs = psa_key_attributes_init(); + psa_key_attributes_t ss_attrs; + psa_key_id_t kp = PSA_KEY_ID_NULL; + psa_key_id_t pub_id = PSA_KEY_ID_NULL; + psa_key_id_t ss_enc = PSA_KEY_ID_NULL; + psa_key_id_t ss_dec = PSA_KEY_ID_NULL; + uint8_t pub_buf[1568]; + size_t pub_len = 0; + uint8_t ct[1568]; + size_t ct_len = 0; + uint8_t ss_enc_buf[32]; + uint8_t ss_dec_buf[32]; + size_t ss_enc_len = 0; + size_t ss_dec_len = 0; + psa_status_t st; + + /* Generate the key pair. */ + psa_set_key_type(&kp_attrs, PSA_KEY_TYPE_ML_KEM_KEY_PAIR); + psa_set_key_bits(&kp_attrs, bits); + psa_set_key_usage_flags(&kp_attrs, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&kp_attrs, PSA_ALG_ML_KEM); + + snprintf(label, sizeof(label), "pub-import generate ML-KEM-%zu", bits); + st = psa_generate_key(&kp_attrs, &kp); + if (check_status(st, label) != 0) return 1; + + /* Export the public key. */ + snprintf(label, sizeof(label), "pub-import export_public ML-KEM-%zu", bits); + st = psa_export_public_key(kp, pub_buf, sizeof(pub_buf), &pub_len); + if (check_status(st, label) != 0) { + (void)psa_destroy_key(kp); + return 1; + } + snprintf(label, sizeof(label), "pub-import pub_len ML-KEM-%zu", bits); + if (check_true(pub_len == pub_exp, label) != 0) { + (void)psa_destroy_key(kp); + return 1; + } + + /* Import the public key with bits=0 (length inference). */ + psa_set_key_type(&pub_attrs, PSA_KEY_TYPE_ML_KEM_PUBLIC_KEY); + psa_set_key_bits(&pub_attrs, 0); + psa_set_key_usage_flags(&pub_attrs, PSA_KEY_USAGE_ENCRYPT); + psa_set_key_algorithm(&pub_attrs, PSA_ALG_ML_KEM); + + snprintf(label, sizeof(label), "pub-import import ML-KEM-%zu", bits); + st = psa_import_key(&pub_attrs, pub_buf, pub_len, &pub_id); + if (check_status(st, label) != 0) { + (void)psa_destroy_key(kp); + return 1; + } + + /* Verify inferred bits. */ + snprintf(label, sizeof(label), "pub-import get_attrs ML-KEM-%zu", bits); + st = psa_get_key_attributes(pub_id, &got_attrs); + if (check_status(st, label) != 0) { + (void)psa_destroy_key(pub_id); + (void)psa_destroy_key(kp); + return 1; + } + snprintf(label, sizeof(label), "pub-import inferred bits ML-KEM-%zu", bits); + if (check_true(psa_get_key_bits(&got_attrs) == (psa_key_bits_t)bits, + label) != 0) { + printf(" (got %u, expected %zu)\n", + (unsigned)psa_get_key_bits(&got_attrs), bits); + (void)psa_destroy_key(pub_id); + (void)psa_destroy_key(kp); + return 1; + } + + /* Encapsulate with the imported public key. */ + ss_attrs = make_ss_attrs(); + snprintf(label, sizeof(label), "pub-import encapsulate ML-KEM-%zu", bits); + st = psa_encapsulate(pub_id, PSA_ALG_ML_KEM, &ss_attrs, + &ss_enc, ct, sizeof(ct), &ct_len); + if (check_status(st, label) != 0) { + (void)psa_destroy_key(pub_id); + (void)psa_destroy_key(kp); + return 1; + } + snprintf(label, sizeof(label), "pub-import ct_len ML-KEM-%zu", bits); + if (check_true(ct_len == ct_exp, label) != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(pub_id); + (void)psa_destroy_key(kp); + return 1; + } + + /* Decapsulate with the key pair. */ + ss_attrs = make_ss_attrs(); + snprintf(label, sizeof(label), "pub-import decapsulate ML-KEM-%zu", bits); + st = psa_decapsulate(kp, PSA_ALG_ML_KEM, ct, ct_len, + &ss_attrs, &ss_dec); + if (check_status(st, label) != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(pub_id); + (void)psa_destroy_key(kp); + return 1; + } + + /* Export and compare. */ + snprintf(label, sizeof(label), "pub-import export ss_enc ML-KEM-%zu", bits); + if (export_key(ss_enc, ss_enc_buf, sizeof(ss_enc_buf), + &ss_enc_len, label) != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(pub_id); + (void)psa_destroy_key(kp); + return 1; + } + snprintf(label, sizeof(label), "pub-import export ss_dec ML-KEM-%zu", bits); + if (export_key(ss_dec, ss_dec_buf, sizeof(ss_dec_buf), + &ss_dec_len, label) != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(pub_id); + (void)psa_destroy_key(kp); + return 1; + } + + snprintf(label, sizeof(label), + "pub-import secrets equal ML-KEM-%zu", bits); + if (check_buf_eq(label, ss_enc_buf, ss_dec_buf, 32) != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(pub_id); + (void)psa_destroy_key(kp); + return 1; + } + + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(pub_id); + (void)psa_destroy_key(kp); + } + + printf("PASS: test_import_public_key_and_encap\n"); + return 0; +} + +/* -------------------------------------------------------------------------- + * Case 4: tampered ciphertext — ML-KEM implicit rejection. + * Decapsulate MUST succeed but secret MUST differ from the genuine one. + * -------------------------------------------------------------------------- */ +static int test_tampered_ciphertext_implicit_rejection(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_attributes_t ss_attrs; + psa_key_id_t kp = PSA_KEY_ID_NULL; + psa_key_id_t ss_enc = PSA_KEY_ID_NULL; + psa_key_id_t ss_dec = PSA_KEY_ID_NULL; + uint8_t ct[1088]; /* ML-KEM-768 */ + size_t ct_len = 0; + uint8_t ss_enc_buf[32]; + uint8_t ss_dec_buf[32]; + size_t ss_enc_len = 0; + size_t ss_dec_len = 0; + psa_status_t st; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_ML_KEM_KEY_PAIR); + psa_set_key_bits(&attrs, 768); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&attrs, PSA_ALG_ML_KEM); + + st = psa_generate_key(&attrs, &kp); + if (check_status(st, "tamper generate") != 0) return 1; + + ss_attrs = make_ss_attrs(); + st = psa_encapsulate(kp, PSA_ALG_ML_KEM, &ss_attrs, + &ss_enc, ct, sizeof(ct), &ct_len); + if (check_status(st, "tamper encapsulate") != 0) { + (void)psa_destroy_key(kp); + return 1; + } + + /* Flip a byte in the middle of the ciphertext. */ + ct[ct_len / 2] ^= 0xAA; + + ss_attrs = make_ss_attrs(); + st = psa_decapsulate(kp, PSA_ALG_ML_KEM, ct, ct_len, + &ss_attrs, &ss_dec); + if (check_status(st, "tamper decapsulate (must succeed)") != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(kp); + return 1; + } + + if (export_key(ss_enc, ss_enc_buf, sizeof(ss_enc_buf), + &ss_enc_len, "tamper export ss_enc") != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(kp); + return 1; + } + if (export_key(ss_dec, ss_dec_buf, sizeof(ss_dec_buf), + &ss_dec_len, "tamper export ss_dec") != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(kp); + return 1; + } + + if (check_buf_ne("tamper secrets differ", ss_enc_buf, ss_dec_buf, 32) != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(kp); + return 1; + } + + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(kp); + + printf("PASS: test_tampered_ciphertext_implicit_rejection\n"); + return 0; +} + +/* -------------------------------------------------------------------------- + * Case 5: wrong ciphertext length must return PSA_ERROR_INVALID_ARGUMENT. + * -------------------------------------------------------------------------- */ +static int test_wrong_ciphertext_length(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_attributes_t ss_attrs; + psa_key_id_t kp = PSA_KEY_ID_NULL; + psa_key_id_t ss_enc = PSA_KEY_ID_NULL; + psa_key_id_t ss_dec = PSA_KEY_ID_NULL; + uint8_t ct[1088]; /* ML-KEM-768 */ + size_t ct_len = 0; + psa_status_t st; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_ML_KEM_KEY_PAIR); + psa_set_key_bits(&attrs, 768); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&attrs, PSA_ALG_ML_KEM); + + st = psa_generate_key(&attrs, &kp); + if (check_status(st, "wrong-len generate") != 0) return 1; + + ss_attrs = make_ss_attrs(); + st = psa_encapsulate(kp, PSA_ALG_ML_KEM, &ss_attrs, + &ss_enc, ct, sizeof(ct), &ct_len); + if (check_status(st, "wrong-len encapsulate") != 0) { + (void)psa_destroy_key(kp); + return 1; + } + (void)psa_destroy_key(ss_enc); + ss_enc = PSA_KEY_ID_NULL; + + /* Pass ct_len - 1: must fail. */ + ss_attrs = make_ss_attrs(); + st = psa_decapsulate(kp, PSA_ALG_ML_KEM, ct, ct_len - 1, + &ss_attrs, &ss_dec); + if (check_status_eq(st, PSA_ERROR_INVALID_ARGUMENT, + "wrong-len decapsulate") != 0) { + if (ss_dec != PSA_KEY_ID_NULL) + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(kp); + return 1; + } + + (void)psa_destroy_key(kp); + + printf("PASS: test_wrong_ciphertext_length\n"); + return 0; +} + +/* -------------------------------------------------------------------------- + * Case 6: missing usage flags. + * - Key pair without ENCRYPT: psa_encapsulate -> PSA_ERROR_NOT_PERMITTED. + * - Public key used for decapsulate -> PSA_ERROR_INVALID_ARGUMENT + * (wrong key type — decapsulate requires a key pair). + * -------------------------------------------------------------------------- */ +static int test_missing_usage(void) +{ + psa_key_attributes_t kp_attrs = psa_key_attributes_init(); + psa_key_attributes_t pub_attrs = psa_key_attributes_init(); + psa_key_attributes_t ss_attrs; + psa_key_id_t kp_no_enc = PSA_KEY_ID_NULL; + psa_key_id_t pub_id = PSA_KEY_ID_NULL; + psa_key_id_t ss_out = PSA_KEY_ID_NULL; + uint8_t pub_buf[800]; /* ML-KEM-512 public key */ + size_t pub_len = 0; + uint8_t ct[768]; + size_t ct_len = 0; + psa_status_t st; + + /* Generate a key pair WITHOUT PSA_KEY_USAGE_ENCRYPT. */ + psa_set_key_type(&kp_attrs, PSA_KEY_TYPE_ML_KEM_KEY_PAIR); + psa_set_key_bits(&kp_attrs, 512); + psa_set_key_usage_flags(&kp_attrs, + PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&kp_attrs, PSA_ALG_ML_KEM); + + st = psa_generate_key(&kp_attrs, &kp_no_enc); + if (check_status(st, "missing-usage generate kp") != 0) return 1; + + ss_attrs = make_ss_attrs(); + st = psa_encapsulate(kp_no_enc, PSA_ALG_ML_KEM, &ss_attrs, + &ss_out, ct, sizeof(ct), &ct_len); + if (check_status_eq(st, PSA_ERROR_NOT_PERMITTED, + "encapsulate without ENCRYPT") != 0) { + if (ss_out != PSA_KEY_ID_NULL) + (void)psa_destroy_key(ss_out); + (void)psa_destroy_key(kp_no_enc); + return 1; + } + + /* Export the public key and import it as ML_KEM_PUBLIC_KEY, then try + * decapsulate with it (must fail: wrong type). */ + st = psa_export_public_key(kp_no_enc, pub_buf, sizeof(pub_buf), &pub_len); + if (check_status(st, "missing-usage export_public") != 0) { + (void)psa_destroy_key(kp_no_enc); + return 1; + } + + psa_set_key_type(&pub_attrs, PSA_KEY_TYPE_ML_KEM_PUBLIC_KEY); + psa_set_key_bits(&pub_attrs, 0); + psa_set_key_usage_flags(&pub_attrs, PSA_KEY_USAGE_ENCRYPT); + psa_set_key_algorithm(&pub_attrs, PSA_ALG_ML_KEM); + + st = psa_import_key(&pub_attrs, pub_buf, pub_len, &pub_id); + if (check_status(st, "missing-usage import public key") != 0) { + (void)psa_destroy_key(kp_no_enc); + return 1; + } + + /* Fabricate a dummy ciphertext (768 bytes of zeros). */ + memset(ct, 0, sizeof(ct)); + ct_len = 768; + + ss_attrs = make_ss_attrs(); + ss_out = PSA_KEY_ID_NULL; + st = psa_decapsulate(pub_id, PSA_ALG_ML_KEM, ct, ct_len, + &ss_attrs, &ss_out); + if (check_status_eq(st, PSA_ERROR_INVALID_ARGUMENT, + "decapsulate with public key") != 0) { + if (ss_out != PSA_KEY_ID_NULL) + (void)psa_destroy_key(ss_out); + (void)psa_destroy_key(pub_id); + (void)psa_destroy_key(kp_no_enc); + return 1; + } + + (void)psa_destroy_key(pub_id); + (void)psa_destroy_key(kp_no_enc); + + printf("PASS: test_missing_usage\n"); + return 0; +} + +/* -------------------------------------------------------------------------- + * Case 7: import a 64-byte seed as ML_KEM_KEY_PAIR. + * - bits=0 -> PSA_ERROR_INVALID_ARGUMENT (ambiguous). + * - bits=768 -> success; full encap/decap round-trip works. + * -------------------------------------------------------------------------- */ +static int test_import_seed(void) +{ + psa_key_attributes_t gen_attrs = psa_key_attributes_init(); + psa_key_attributes_t imp_attrs0 = psa_key_attributes_init(); + psa_key_attributes_t imp_attrs768 = psa_key_attributes_init(); + psa_key_attributes_t ss_attrs; + psa_key_id_t kp_gen = PSA_KEY_ID_NULL; + psa_key_id_t kp_imp0 = PSA_KEY_ID_NULL; + psa_key_id_t kp_imp = PSA_KEY_ID_NULL; + psa_key_id_t ss_enc = PSA_KEY_ID_NULL; + psa_key_id_t ss_dec = PSA_KEY_ID_NULL; + uint8_t seed[64]; + size_t seed_len = 0; + uint8_t ct[1088]; + size_t ct_len = 0; + uint8_t ss_enc_buf[32]; + uint8_t ss_dec_buf[32]; + size_t ss_enc_len = 0; + size_t ss_dec_len = 0; + psa_status_t st; + + /* Generate ML-KEM-768 key pair and export seed. */ + psa_set_key_type(&gen_attrs, PSA_KEY_TYPE_ML_KEM_KEY_PAIR); + psa_set_key_bits(&gen_attrs, 768); + psa_set_key_usage_flags(&gen_attrs, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&gen_attrs, PSA_ALG_ML_KEM); + + st = psa_generate_key(&gen_attrs, &kp_gen); + if (check_status(st, "import-seed generate") != 0) return 1; + + if (export_key(kp_gen, seed, sizeof(seed), &seed_len, + "import-seed export seed") != 0) { + (void)psa_destroy_key(kp_gen); + return 1; + } + if (check_true(seed_len == 64, "import-seed seed_len") != 0) { + (void)psa_destroy_key(kp_gen); + return 1; + } + + /* Import with bits=0 must fail (ambiguous). */ + psa_set_key_type(&imp_attrs0, PSA_KEY_TYPE_ML_KEM_KEY_PAIR); + psa_set_key_bits(&imp_attrs0, 0); + psa_set_key_usage_flags(&imp_attrs0, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&imp_attrs0, PSA_ALG_ML_KEM); + + st = psa_import_key(&imp_attrs0, seed, seed_len, &kp_imp0); + if (check_status_eq(st, PSA_ERROR_INVALID_ARGUMENT, + "import seed bits=0") != 0) { + if (kp_imp0 != PSA_KEY_ID_NULL) + (void)psa_destroy_key(kp_imp0); + (void)psa_destroy_key(kp_gen); + return 1; + } + + /* Import with bits=768 must succeed. */ + psa_set_key_type(&imp_attrs768, PSA_KEY_TYPE_ML_KEM_KEY_PAIR); + psa_set_key_bits(&imp_attrs768, 768); + psa_set_key_usage_flags(&imp_attrs768, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&imp_attrs768, PSA_ALG_ML_KEM); + + st = psa_import_key(&imp_attrs768, seed, seed_len, &kp_imp); + if (check_status(st, "import seed bits=768") != 0) { + (void)psa_destroy_key(kp_gen); + return 1; + } + + /* Full round-trip with the imported seed key. */ + ss_attrs = make_ss_attrs(); + st = psa_encapsulate(kp_imp, PSA_ALG_ML_KEM, &ss_attrs, + &ss_enc, ct, sizeof(ct), &ct_len); + if (check_status(st, "import-seed encapsulate") != 0) { + (void)psa_destroy_key(kp_imp); + (void)psa_destroy_key(kp_gen); + return 1; + } + + ss_attrs = make_ss_attrs(); + st = psa_decapsulate(kp_imp, PSA_ALG_ML_KEM, ct, ct_len, + &ss_attrs, &ss_dec); + if (check_status(st, "import-seed decapsulate") != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(kp_imp); + (void)psa_destroy_key(kp_gen); + return 1; + } + + if (export_key(ss_enc, ss_enc_buf, sizeof(ss_enc_buf), + &ss_enc_len, "import-seed export ss_enc") != 0 || + export_key(ss_dec, ss_dec_buf, sizeof(ss_dec_buf), + &ss_dec_len, "import-seed export ss_dec") != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(kp_imp); + (void)psa_destroy_key(kp_gen); + return 1; + } + + if (check_buf_eq("import-seed secrets equal", + ss_enc_buf, ss_dec_buf, 32) != 0) { + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(kp_imp); + (void)psa_destroy_key(kp_gen); + return 1; + } + + (void)psa_destroy_key(ss_enc); + (void)psa_destroy_key(ss_dec); + (void)psa_destroy_key(kp_imp); + (void)psa_destroy_key(kp_gen); + + printf("PASS: test_import_seed\n"); + return 0; +} + +/* -------------------------------------------------------------------------- + * Case 8: buffer too small for ciphertext -> PSA_ERROR_BUFFER_TOO_SMALL. + * -------------------------------------------------------------------------- */ +static int test_buffer_too_small(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_attributes_t ss_attrs; + psa_key_id_t kp = PSA_KEY_ID_NULL; + psa_key_id_t ss_out = PSA_KEY_ID_NULL; + /* ML-KEM-768 needs 1088 bytes; give 1087. */ + uint8_t small_ct[1087]; + size_t ct_len = 0; + psa_status_t st; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_ML_KEM_KEY_PAIR); + psa_set_key_bits(&attrs, 768); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&attrs, PSA_ALG_ML_KEM); + + st = psa_generate_key(&attrs, &kp); + if (check_status(st, "buf-small generate") != 0) return 1; + + ss_attrs = make_ss_attrs(); + st = psa_encapsulate(kp, PSA_ALG_ML_KEM, &ss_attrs, + &ss_out, small_ct, sizeof(small_ct), &ct_len); + if (check_status_eq(st, PSA_ERROR_BUFFER_TOO_SMALL, + "encapsulate buf too small") != 0) { + if (ss_out != PSA_KEY_ID_NULL) + (void)psa_destroy_key(ss_out); + (void)psa_destroy_key(kp); + return 1; + } + + (void)psa_destroy_key(kp); + + printf("PASS: test_buffer_too_small\n"); + return 0; +} + +/* -------------------------------------------------------------------------- + * main + * -------------------------------------------------------------------------- */ +int main(void) +{ + psa_status_t st; + + st = psa_crypto_init(); + if (st != PSA_SUCCESS) { + printf("FAIL: psa_crypto_init status=%d\n", (int)st); + return 1; + } + + if (test_generate_and_export_sizes() != 0) return 1; + if (test_encap_decap_roundtrip() != 0) return 1; + if (test_import_public_key_and_encap() != 0) return 1; + if (test_tampered_ciphertext_implicit_rejection() != 0) return 1; + if (test_wrong_ciphertext_length() != 0) return 1; + if (test_missing_usage() != 0) return 1; + if (test_import_seed() != 0) return 1; + if (test_buffer_too_small() != 0) return 1; + + printf("PSA ML-KEM test: OK\n"); + return 0; +} diff --git a/test/psa_server/psa_sign_context_test.c b/test/psa_server/psa_sign_context_test.c new file mode 100644 index 0000000..9f71d19 --- /dev/null +++ b/test/psa_server/psa_sign_context_test.c @@ -0,0 +1,508 @@ +/* psa_sign_context_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 + */ + +/* + * Coverage test for PSA 1.4 with-context signature functions: + * psa_sign_message_with_context / psa_verify_message_with_context + * psa_sign_hash_with_context / psa_verify_hash_with_context + * + * Test cases: + * 1. ML-DSA-65: sign with context, verify same/different/empty context. + * 2. ML-DSA-65: plain psa_sign_message is equivalent to empty context. + * 3. Ed25519 PSA_ALG_EDDSA_CTX: sign/verify with context; mismatch fails. + * 4. ECDSA secp256r1: non-empty context → INVALID_ARGUMENT; empty → success. + * 5. Context > 255 bytes → INVALID_ARGUMENT for any algorithm. + * 6. HashML-DSA: sign_hash_with_context / verify_hash_with_context round-trip. + */ + +#include +#include "psa_api_test_user_settings.h" + +#ifndef WOLFSSL_USER_SETTINGS +#define WOLFSSL_USER_SETTINGS +#endif + +#include + +#include +#include + +#include + +/* ML-DSA-65 signature size: 3309 bytes */ +#define MLDSA_65_SIG_SIZE 3309u + +static const uint8_t g_msg[] = "psa sign context test message"; + +static int expect_status(const char *label, psa_status_t status, + psa_status_t expected) +{ + if (status != expected) { + printf("FAIL %s status=%d expected=%d\n", label, (int)status, + (int)expected); + return 1; + } + return 0; +} + +/* + * Test 1: ML-DSA-65 context sign/verify. + * - sign with context "test-ctx" + * - verify with same context → PSA_SUCCESS + * - verify with different context → PSA_ERROR_INVALID_SIGNATURE + * - verify with empty context → PSA_ERROR_INVALID_SIGNATURE + */ +static int test_mldsa65_context_mismatch(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key_id = 0; + psa_status_t st; + uint8_t sig[MLDSA_65_SIG_SIZE]; + size_t sig_len = 0; + static const uint8_t ctx_a[] = "test-ctx"; + static const uint8_t ctx_b[] = "wrong-ctx"; + int ret = 0; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_ML_DSA_KEY_PAIR); + psa_set_key_bits(&attrs, 192); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE); + psa_set_key_algorithm(&attrs, PSA_ALG_ML_DSA); + + st = psa_generate_key(&attrs, &key_id); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP test_mldsa65_context_mismatch (ML-DSA not supported)\n"); + return 0; + } + if (expect_status("mldsa65_ctx: generate_key", st, PSA_SUCCESS) != 0) + return 1; + + /* Sign with context "test-ctx" */ + st = psa_sign_message_with_context(key_id, PSA_ALG_ML_DSA, + g_msg, sizeof(g_msg) - 1, + ctx_a, sizeof(ctx_a) - 1, + sig, sizeof(sig), &sig_len); + if (expect_status("mldsa65_ctx: sign with ctx_a", st, PSA_SUCCESS) != 0) { + ret = 1; + goto done; + } + + /* Verify with the same context → OK */ + st = psa_verify_message_with_context(key_id, PSA_ALG_ML_DSA, + g_msg, sizeof(g_msg) - 1, + ctx_a, sizeof(ctx_a) - 1, + sig, sig_len); + if (expect_status("mldsa65_ctx: verify same ctx", st, PSA_SUCCESS) != 0) { + ret = 1; + goto done; + } + + /* Verify with a different context → INVALID_SIGNATURE */ + st = psa_verify_message_with_context(key_id, PSA_ALG_ML_DSA, + g_msg, sizeof(g_msg) - 1, + ctx_b, sizeof(ctx_b) - 1, + sig, sig_len); + if (expect_status("mldsa65_ctx: verify diff ctx", st, + PSA_ERROR_INVALID_SIGNATURE) != 0) { + ret = 1; + goto done; + } + + /* Verify with empty context → INVALID_SIGNATURE */ + st = psa_verify_message_with_context(key_id, PSA_ALG_ML_DSA, + g_msg, sizeof(g_msg) - 1, + NULL, 0, + sig, sig_len); + if (expect_status("mldsa65_ctx: verify empty ctx", st, + PSA_ERROR_INVALID_SIGNATURE) != 0) { + ret = 1; + goto done; + } + +done: + (void)psa_destroy_key(key_id); + return ret; +} + +/* + * Test 2: ML-DSA-65 empty-context equivalence. + * psa_sign_message (no context) is equivalent to an empty context. + * Sign via psa_sign_message, then verify via + * psa_verify_message_with_context with empty context → PSA_SUCCESS. + * + * Signatures are hedged/randomized so byte equality cannot be checked; + * we use cross-API verify to prove semantic equivalence. + */ +static int test_mldsa65_empty_ctx_equivalence(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key_id = 0; + psa_status_t st; + uint8_t sig[MLDSA_65_SIG_SIZE]; + size_t sig_len = 0; + int ret = 0; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_ML_DSA_KEY_PAIR); + psa_set_key_bits(&attrs, 192); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE); + psa_set_key_algorithm(&attrs, PSA_ALG_ML_DSA); + + st = psa_generate_key(&attrs, &key_id); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP test_mldsa65_empty_ctx_equivalence (ML-DSA not supported)\n"); + return 0; + } + if (expect_status("mldsa65_equiv: generate_key", st, PSA_SUCCESS) != 0) + return 1; + + /* Sign via plain psa_sign_message (empty context) */ + st = psa_sign_message(key_id, PSA_ALG_ML_DSA, + g_msg, sizeof(g_msg) - 1, + sig, sizeof(sig), &sig_len); + if (expect_status("mldsa65_equiv: plain sign_message", st, + PSA_SUCCESS) != 0) { + ret = 1; + goto done; + } + + /* Verify via psa_verify_message_with_context with empty context → OK */ + st = psa_verify_message_with_context(key_id, PSA_ALG_ML_DSA, + g_msg, sizeof(g_msg) - 1, + NULL, 0, + sig, sig_len); + if (expect_status("mldsa65_equiv: verify_with_context(empty)", st, + PSA_SUCCESS) != 0) { + ret = 1; + goto done; + } + +done: + (void)psa_destroy_key(key_id); + return ret; +} + +/* + * Test 3: Ed25519 PSA_ALG_EDDSA_CTX context round-trip and mismatch. + * - Generate Ed25519 key with PSA_ALG_EDDSA_CTX policy. + * - sign_message_with_context("ctx") / verify same → PSA_SUCCESS. + * - verify with different context → PSA_ERROR_INVALID_SIGNATURE. + */ +static int test_ed25519_eddsa_ctx(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key_id = 0; + psa_status_t st; + uint8_t sig[64]; + size_t sig_len = 0; + static const uint8_t ctx[] = "ctx"; + static const uint8_t ctx_other[] = "other-ctx"; + int ret = 0; + + psa_set_key_type(&attrs, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS)); + psa_set_key_bits(&attrs, 255); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE); + psa_set_key_algorithm(&attrs, PSA_ALG_EDDSA_CTX); + + st = psa_generate_key(&attrs, &key_id); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP test_ed25519_eddsa_ctx (Ed25519 not supported)\n"); + return 0; + } + if (expect_status("ed25519ctx: generate_key", st, PSA_SUCCESS) != 0) + return 1; + + /* Sign with context "ctx" */ + st = psa_sign_message_with_context(key_id, PSA_ALG_EDDSA_CTX, + g_msg, sizeof(g_msg) - 1, + ctx, sizeof(ctx) - 1, + sig, sizeof(sig), &sig_len); + if (expect_status("ed25519ctx: sign with ctx", st, PSA_SUCCESS) != 0) { + ret = 1; + goto done; + } + if (sig_len != 64u) { + printf("FAIL ed25519ctx: sig_len=%zu expected=64\n", sig_len); + ret = 1; + goto done; + } + + /* Verify with same context → OK */ + st = psa_verify_message_with_context(key_id, PSA_ALG_EDDSA_CTX, + g_msg, sizeof(g_msg) - 1, + ctx, sizeof(ctx) - 1, + sig, sig_len); + if (expect_status("ed25519ctx: verify same ctx", st, PSA_SUCCESS) != 0) { + ret = 1; + goto done; + } + + /* Verify with different context → INVALID_SIGNATURE */ + st = psa_verify_message_with_context(key_id, PSA_ALG_EDDSA_CTX, + g_msg, sizeof(g_msg) - 1, + ctx_other, sizeof(ctx_other) - 1, + sig, sig_len); + if (expect_status("ed25519ctx: verify diff ctx", st, + PSA_ERROR_INVALID_SIGNATURE) != 0) { + ret = 1; + goto done; + } + +done: + (void)psa_destroy_key(key_id); + return ret; +} + +/* + * Test 4: ECDSA secp256r1 — non-empty context is rejected. + * - Non-empty context → PSA_ERROR_INVALID_ARGUMENT. + * - Empty context → PSA_SUCCESS (context is a no-op for ECDSA). + * - Plain psa_verify_message accepts the empty-context signature. + */ +static int test_ecdsa_context_rejected(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key_id = 0; + psa_status_t st; + uint8_t sig[128]; + size_t sig_len = 0; + static const uint8_t ctx4[] = "abcd"; + int ret = 0; + + psa_set_key_type(&attrs, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(&attrs, 256); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE); + psa_set_key_algorithm(&attrs, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); + + st = psa_generate_key(&attrs, &key_id); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP test_ecdsa_context_rejected (ECDSA not supported)\n"); + return 0; + } + if (expect_status("ecdsa_ctx: generate_key", st, PSA_SUCCESS) != 0) + return 1; + + /* Non-empty context (4 bytes) → INVALID_ARGUMENT */ + st = psa_sign_message_with_context(key_id, PSA_ALG_ECDSA(PSA_ALG_SHA_256), + g_msg, sizeof(g_msg) - 1, + ctx4, sizeof(ctx4) - 1, + sig, sizeof(sig), &sig_len); + if (expect_status("ecdsa_ctx: sign with nonempty ctx", st, + PSA_ERROR_INVALID_ARGUMENT) != 0) { + ret = 1; + goto done; + } + + /* Empty context → success */ + sig_len = 0; + st = psa_sign_message_with_context(key_id, PSA_ALG_ECDSA(PSA_ALG_SHA_256), + g_msg, sizeof(g_msg) - 1, + NULL, 0, + sig, sizeof(sig), &sig_len); + if (expect_status("ecdsa_ctx: sign with empty ctx", st, + PSA_SUCCESS) != 0) { + ret = 1; + goto done; + } + + /* Plain psa_verify_message accepts the empty-context signature */ + st = psa_verify_message(key_id, PSA_ALG_ECDSA(PSA_ALG_SHA_256), + g_msg, sizeof(g_msg) - 1, + sig, sig_len); + if (expect_status("ecdsa_ctx: plain verify_message of empty-ctx sig", st, + PSA_SUCCESS) != 0) { + ret = 1; + goto done; + } + +done: + (void)psa_destroy_key(key_id); + return ret; +} + +/* + * Test 5: Context longer than 255 bytes → PSA_ERROR_INVALID_ARGUMENT. + * Tested with ECDSA (any algorithm applies, as this check is universal). + */ +static int test_overlong_context_rejected(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key_id = 0; + psa_status_t st; + uint8_t sig[128]; + size_t sig_len = 0; + uint8_t long_ctx[256]; + int ret = 0; + + memset(long_ctx, 0xBB, sizeof(long_ctx)); + + psa_set_key_type(&attrs, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(&attrs, 256); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE); + psa_set_key_algorithm(&attrs, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); + + st = psa_generate_key(&attrs, &key_id); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP test_overlong_context_rejected (ECDSA not supported)\n"); + return 0; + } + if (expect_status("overlong_ctx: generate_key", st, PSA_SUCCESS) != 0) + return 1; + + /* 256-byte context (> 255 limit) → INVALID_ARGUMENT */ + st = psa_sign_message_with_context(key_id, PSA_ALG_ECDSA(PSA_ALG_SHA_256), + g_msg, sizeof(g_msg) - 1, + long_ctx, sizeof(long_ctx), + sig, sizeof(sig), &sig_len); + if (expect_status("overlong_ctx: 256-byte ctx rejected", st, + PSA_ERROR_INVALID_ARGUMENT) != 0) { + ret = 1; + goto done; + } + +done: + (void)psa_destroy_key(key_id); + return ret; +} + +/* + * Test 6: HashML-DSA — psa_sign_hash_with_context / psa_verify_hash_with_context. + * - Key policy: PSA_ALG_HASH_ML_DSA(PSA_ALG_SHA_256). + * - Compute SHA-256 digest, sign_hash_with_context("abc"). + * - verify_hash_with_context same context → PSA_SUCCESS. + * - verify_hash_with_context different context → PSA_ERROR_INVALID_SIGNATURE. + */ +static int test_hash_mldsa65_context(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key_id = 0; + psa_status_t st; + uint8_t sig[MLDSA_65_SIG_SIZE]; + size_t sig_len = 0; + uint8_t hash[PSA_HASH_LENGTH(PSA_ALG_SHA_256)]; + size_t hash_len = 0; + static const uint8_t ctx[] = "abc"; + static const uint8_t ctx_other[] = "xyz"; + int ret = 0; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_ML_DSA_KEY_PAIR); + psa_set_key_bits(&attrs, 192); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH | + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE); + psa_set_key_algorithm(&attrs, PSA_ALG_HASH_ML_DSA(PSA_ALG_SHA_256)); + + st = psa_generate_key(&attrs, &key_id); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP test_hash_mldsa65_context (HashML-DSA not supported)\n"); + return 0; + } + if (expect_status("hash_mldsa65: generate_key", st, PSA_SUCCESS) != 0) + return 1; + + /* Compute SHA-256 digest of the message */ + st = psa_hash_compute(PSA_ALG_SHA_256, + g_msg, sizeof(g_msg) - 1, + hash, sizeof(hash), &hash_len); + if (expect_status("hash_mldsa65: psa_hash_compute", st, + PSA_SUCCESS) != 0) { + ret = 1; + goto done; + } + + /* Sign hash with context "abc" */ + st = psa_sign_hash_with_context(key_id, + PSA_ALG_HASH_ML_DSA(PSA_ALG_SHA_256), + hash, hash_len, + ctx, sizeof(ctx) - 1, + sig, sizeof(sig), &sig_len); + if (expect_status("hash_mldsa65: sign_hash_with_context(abc)", st, + PSA_SUCCESS) != 0) { + ret = 1; + goto done; + } + + /* Verify same context → OK */ + st = psa_verify_hash_with_context(key_id, + PSA_ALG_HASH_ML_DSA(PSA_ALG_SHA_256), + hash, hash_len, + ctx, sizeof(ctx) - 1, + sig, sig_len); + if (expect_status("hash_mldsa65: verify_hash_with_context same ctx", st, + PSA_SUCCESS) != 0) { + ret = 1; + goto done; + } + + /* Verify different context → INVALID_SIGNATURE */ + st = psa_verify_hash_with_context(key_id, + PSA_ALG_HASH_ML_DSA(PSA_ALG_SHA_256), + hash, hash_len, + ctx_other, sizeof(ctx_other) - 1, + sig, sig_len); + if (expect_status("hash_mldsa65: verify_hash_with_context diff ctx", st, + PSA_ERROR_INVALID_SIGNATURE) != 0) { + ret = 1; + goto done; + } + +done: + (void)psa_destroy_key(key_id); + return ret; +} + +int main(void) +{ + psa_status_t st; + + st = psa_crypto_init(); + if (st != PSA_SUCCESS) { + printf("FAIL psa_crypto_init status=%d\n", (int)st); + return 1; + } + + if (test_mldsa65_context_mismatch() != 0) + return 1; + + if (test_mldsa65_empty_ctx_equivalence() != 0) + return 1; + + if (test_ed25519_eddsa_ctx() != 0) + return 1; + + if (test_ecdsa_context_rejected() != 0) + return 1; + + if (test_overlong_context_rejected() != 0) + return 1; + + if (test_hash_mldsa65_context() != 0) + return 1; + + printf("PSA sign context test: OK\n"); + return 0; +} diff --git a/test/psa_server/psa_sp800_108_test.c b/test/psa_server/psa_sp800_108_test.c new file mode 100644 index 0000000..6d98ff2 --- /dev/null +++ b/test/psa_server/psa_sp800_108_test.c @@ -0,0 +1,902 @@ +/* psa_sp800_108_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 + */ + +/* + * Coverage test for wolfPSA SP 800-108r1 counter-mode KDFs (PSA 1.4). + * + * Reference-value strategy + * ------------------------ + * Expected outputs are computed inside the test by independently + * reconstructing K(1)||K(2)||... at the MAC level using psa_mac_compute + * with hand-assembled fixed-input buffers. This validates the KDF + * plumbing and encoding format independently of the MAC engine (which + * has its own known-answer tests elsewhere). + * + * HMAC construction (PSA 1.4 §10.8): + * K(i) = HMAC(secret, [i]_4 || Label || 0x00 || Context || [L]_4) + * [L]_4 = total output length in bits, big-endian 4 bytes + * L is snapshotted from capacity at the first output_bytes() call + * (before capacity is decremented). set_capacity(N) before any output + * call fixes L = N bytes = N*8 bits. + * + * CMAC construction (PSA 1.4 §10.8): + * K_0 = CMAC(secret, Label || 0x00 || Context || [L]_4) + * K(i) = CMAC(secret, [i]_4 || Label || 0x00 || Context || [L]_4 || K_0) + * + * Fixed test vectors + * HMAC: 32-byte secret = {0x0b, 0x0b, ...}, label = "label", context = "context" + * CMAC: 16-byte AES key = {0x0b, 0x0b, ...}, same label/context + */ + +#include "psa_api_test_user_settings.h" + +#ifndef WOLFSSL_USER_SETTINGS +#define WOLFSSL_USER_SETTINGS +#endif + +#include + +#include +#include +#include + +#include + +/* ------------------------------------------------------------------------- + * Helpers + * -------------------------------------------------------------------------*/ + +static int expect_status(const char *label, psa_status_t status, + psa_status_t expected) +{ + if (status != expected) { + printf("FAIL %s status=%d expected=%d\n", label, (int)status, + (int)expected); + return 1; + } + return 0; +} + +/* Compute one HMAC block using PSA. + * + * Imports the raw secret bytes as a PSA_KEY_TYPE_HMAC key, calls + * psa_mac_compute over the provided fixed-input buffer, and destroys the + * temporary key. out must be at least 32 bytes (SHA-256 digest size). */ +static int hmac_sha256(const uint8_t *secret, size_t secret_len, + const uint8_t *data, size_t data_len, + uint8_t *out, size_t *out_len) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t kid = PSA_KEY_ID_NULL; + psa_status_t st; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_HMAC); + psa_set_key_bits(&attrs, (psa_key_bits_t)(secret_len * 8u)); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_SIGN_MESSAGE); + psa_set_key_algorithm(&attrs, PSA_ALG_HMAC(PSA_ALG_SHA_256)); + + st = psa_import_key(&attrs, secret, secret_len, &kid); + if (st != PSA_SUCCESS) { + printf("FAIL hmac_sha256 import status=%d\n", (int)st); + return 1; + } + + st = psa_mac_compute(kid, PSA_ALG_HMAC(PSA_ALG_SHA_256), + data, data_len, out, 32u, out_len); + psa_destroy_key(kid); + if (st != PSA_SUCCESS) { + printf("FAIL hmac_sha256 compute status=%d\n", (int)st); + return 1; + } + return 0; +} + +/* Compute one CMAC block using PSA. + * + * Imports the raw AES key bytes, calls psa_mac_compute with PSA_ALG_CMAC + * over the fixed-input buffer. out must be at least 16 bytes. */ +static int aes_cmac(const uint8_t *aes_key, size_t key_len, + const uint8_t *data, size_t data_len, + uint8_t *out, size_t *out_len) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t kid = PSA_KEY_ID_NULL; + psa_status_t st; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attrs, (psa_key_bits_t)(key_len * 8u)); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_SIGN_MESSAGE); + psa_set_key_algorithm(&attrs, PSA_ALG_CMAC); + + st = psa_import_key(&attrs, aes_key, key_len, &kid); + if (st != PSA_SUCCESS) { + printf("FAIL aes_cmac import status=%d\n", (int)st); + return 1; + } + + st = psa_mac_compute(kid, PSA_ALG_CMAC, + data, data_len, out, 16u, out_len); + psa_destroy_key(kid); + if (st != PSA_SUCCESS) { + printf("FAIL aes_cmac compute status=%d\n", (int)st); + return 1; + } + return 0; +} + +/* + * Assemble the HMAC fixed-input for counter i: + * [i]_4 || label_data || 0x00 || context_data || [L]_4 + * L_bits is total output length in bits (32-bit value). + */ +static size_t build_hmac_fixed_input(uint8_t *buf, size_t bufsz, + uint32_t counter, + const uint8_t *label, size_t label_len, + const uint8_t *context, size_t ctx_len, + uint32_t L_bits) +{ + size_t pos = 0; + + (void)bufsz; /* caller ensures sufficient size */ + + /* [i]_4 */ + buf[pos++] = (uint8_t)((counter >> 24) & 0xff); + buf[pos++] = (uint8_t)((counter >> 16) & 0xff); + buf[pos++] = (uint8_t)((counter >> 8) & 0xff); + buf[pos++] = (uint8_t)( counter & 0xff); + /* Label */ + if (label_len > 0) { + memcpy(buf + pos, label, label_len); + pos += label_len; + } + /* separator */ + buf[pos++] = 0x00u; + /* Context */ + if (ctx_len > 0) { + memcpy(buf + pos, context, ctx_len); + pos += ctx_len; + } + /* [L]_4 */ + buf[pos++] = (uint8_t)((L_bits >> 24) & 0xff); + buf[pos++] = (uint8_t)((L_bits >> 16) & 0xff); + buf[pos++] = (uint8_t)((L_bits >> 8) & 0xff); + buf[pos++] = (uint8_t)( L_bits & 0xff); + + return pos; +} + +/* + * Reconstruct HMAC-SHA-256 SP800-108 counter-mode output. + * + * Produces num_blocks * 32 bytes into out_ref (caller allocates). + * capacity_bytes is what set_capacity() was called with; this fixes L. + */ +static int reconstruct_hmac(const uint8_t *secret, size_t secret_len, + const uint8_t *label, size_t label_len, + const uint8_t *context, size_t ctx_len, + size_t capacity_bytes, + uint8_t *out_ref, size_t out_len) +{ + /* + * L = capacity_bytes * 8 bits (L is snapshotted from capacity before any + * decrement, i.e. the value set by set_capacity()). + */ + uint32_t L_bits = (uint32_t)(capacity_bytes * 8u); + uint8_t fi[4 + 255 + 1 + 255 + 4]; /* max fixed-input size */ + uint8_t block[32]; + size_t fi_len; + size_t mac_out_len; + uint32_t counter; + size_t offset = 0; + + for (counter = 1u; offset < out_len; counter++) { + size_t copy_len; + + fi_len = build_hmac_fixed_input(fi, sizeof(fi), counter, + label, label_len, + context, ctx_len, + L_bits); + + if (hmac_sha256(secret, secret_len, fi, fi_len, + block, &mac_out_len) != 0) { + return 1; + } + + copy_len = 32u; + if (offset + copy_len > out_len) { + copy_len = out_len - offset; + } + memcpy(out_ref + offset, block, copy_len); + offset += copy_len; + } + return 0; +} + +/* + * Reconstruct CMAC SP800-108 counter-mode output. + * + * K_0 = CMAC(secret, Label || 0x00 || Context || [L]_4) + * K(i) = CMAC(secret, [i]_4 || Label || 0x00 || Context || [L]_4 || K_0) + */ +static int reconstruct_cmac(const uint8_t *aes_key, size_t key_len, + const uint8_t *label, size_t label_len, + const uint8_t *context, size_t ctx_len, + size_t capacity_bytes, + uint8_t *out_ref, size_t out_len) +{ + uint32_t L_bits = (uint32_t)(capacity_bytes * 8u); + /* K_0 fixed-input: Label || 0x00 || Context || [L]_4 */ + uint8_t k0_fi[255 + 1 + 255 + 4]; + size_t k0_fi_len = 0; + uint8_t K0[16]; + size_t mac_out_len; + /* K(i) fixed-input: [i]_4 || Label || 0x00 || Context || [L]_4 || K_0 */ + uint8_t ki_fi[4 + 255 + 1 + 255 + 4 + 16]; + size_t ki_fi_len; + uint8_t block[16]; + uint32_t counter; + size_t offset = 0; + uint32_t L_big; + + L_big = L_bits; + + /* Build K_0 fixed-input */ + if (label_len > 0) { + memcpy(k0_fi + k0_fi_len, label, label_len); + k0_fi_len += label_len; + } + k0_fi[k0_fi_len++] = 0x00u; + if (ctx_len > 0) { + memcpy(k0_fi + k0_fi_len, context, ctx_len); + k0_fi_len += ctx_len; + } + k0_fi[k0_fi_len++] = (uint8_t)((L_big >> 24) & 0xff); + k0_fi[k0_fi_len++] = (uint8_t)((L_big >> 16) & 0xff); + k0_fi[k0_fi_len++] = (uint8_t)((L_big >> 8) & 0xff); + k0_fi[k0_fi_len++] = (uint8_t)( L_big & 0xff); + + /* Compute K_0 */ + if (aes_cmac(aes_key, key_len, k0_fi, k0_fi_len, K0, &mac_out_len) != 0) { + return 1; + } + + /* K(i) loop */ + for (counter = 1u; offset < out_len; counter++) { + size_t copy_len; + + ki_fi_len = 0; + /* [i]_4 */ + ki_fi[ki_fi_len++] = (uint8_t)((counter >> 24) & 0xff); + ki_fi[ki_fi_len++] = (uint8_t)((counter >> 16) & 0xff); + ki_fi[ki_fi_len++] = (uint8_t)((counter >> 8) & 0xff); + ki_fi[ki_fi_len++] = (uint8_t)( counter & 0xff); + if (label_len > 0) { + memcpy(ki_fi + ki_fi_len, label, label_len); + ki_fi_len += label_len; + } + ki_fi[ki_fi_len++] = 0x00u; + if (ctx_len > 0) { + memcpy(ki_fi + ki_fi_len, context, ctx_len); + ki_fi_len += ctx_len; + } + ki_fi[ki_fi_len++] = (uint8_t)((L_big >> 24) & 0xff); + ki_fi[ki_fi_len++] = (uint8_t)((L_big >> 16) & 0xff); + ki_fi[ki_fi_len++] = (uint8_t)((L_big >> 8) & 0xff); + ki_fi[ki_fi_len++] = (uint8_t)( L_big & 0xff); + /* K_0 appended */ + memcpy(ki_fi + ki_fi_len, K0, 16u); + ki_fi_len += 16u; + + if (aes_cmac(aes_key, key_len, ki_fi, ki_fi_len, block, &mac_out_len) != 0) { + return 1; + } + + copy_len = 16u; + if (offset + copy_len > out_len) { + copy_len = out_len - offset; + } + memcpy(out_ref + offset, block, copy_len); + offset += copy_len; + } + return 0; +} + +/* ------------------------------------------------------------------------- + * Test vectors + * -------------------------------------------------------------------------*/ + +/* 32-byte HMAC secret: all 0x0b */ +static const uint8_t g_hmac_secret[32] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b +}; + +/* 16-byte AES-128 CMAC secret: all 0x0b */ +static const uint8_t g_cmac_secret[16] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b +}; + +static const uint8_t g_label[] = { 'l', 'a', 'b', 'e', 'l' }; +static const uint8_t g_context[] = { 'c', 'o', 'n', 't', 'e', 'x', 't' }; + +/* ------------------------------------------------------------------------- + * Test case 1: HMAC-SHA-256 two separate output_bytes(32) calls match K(1)||K(2) + * + * set_capacity(64) → L = 64 bytes = 512 bits = 0x00000200 + * K(1) = HMAC(secret, 00000001 || "label" || 00 || "context" || 00000200) + * K(2) = HMAC(secret, 00000002 || "label" || 00 || "context" || 00000200) + * -------------------------------------------------------------------------*/ +static int test_hmac_two_output_calls(void) +{ + psa_algorithm_t alg = PSA_ALG_SP800_108_COUNTER_HMAC(PSA_ALG_SHA_256); + psa_key_derivation_operation_t op = psa_key_derivation_operation_init(); + uint8_t out_a[32], out_b[32]; + uint8_t ref[64]; + psa_status_t st; + + st = psa_key_derivation_setup(&op, alg); + if (expect_status("tc1 setup", st, PSA_SUCCESS) != 0) return 1; + + /* set_capacity BEFORE first output fixes L */ + st = psa_key_derivation_set_capacity(&op, 64u); + if (expect_status("tc1 set_capacity", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_SECRET, + g_hmac_secret, sizeof(g_hmac_secret)); + if (expect_status("tc1 input SECRET", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_LABEL, + g_label, sizeof(g_label)); + if (expect_status("tc1 input LABEL", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_CONTEXT, + g_context, sizeof(g_context)); + if (expect_status("tc1 input CONTEXT", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_output_bytes(&op, out_a, 32u); + if (expect_status("tc1 output_bytes[0..31]", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_output_bytes(&op, out_b, 32u); + if (expect_status("tc1 output_bytes[32..63]", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + psa_key_derivation_abort(&op); + + /* Reconstruct K(1)||K(2) independently at the MAC level. + * capacity = 64 bytes → L = 512 bits. */ + if (reconstruct_hmac(g_hmac_secret, sizeof(g_hmac_secret), + g_label, sizeof(g_label), + g_context, sizeof(g_context), + 64u, ref, 64u) != 0) { + return 1; + } + + if (memcmp(out_a, ref, 32u) != 0) { + printf("FAIL tc1: K(1) mismatch\n"); + return 1; + } + if (memcmp(out_b, ref + 32, 32u) != 0) { + printf("FAIL tc1: K(2) mismatch\n"); + return 1; + } + + printf("PASS tc1: HMAC two output calls match K(1)||K(2)\n"); + return 0; +} + +/* ------------------------------------------------------------------------- + * Test case 2: Same derivation but output in one 64-byte call (call-granularity + * independence). Must produce identical bytes to tc1. + * -------------------------------------------------------------------------*/ +static int test_hmac_single_64_byte_call(void) +{ + psa_algorithm_t alg = PSA_ALG_SP800_108_COUNTER_HMAC(PSA_ALG_SHA_256); + psa_key_derivation_operation_t op = psa_key_derivation_operation_init(); + uint8_t out[64]; + uint8_t ref[64]; + psa_status_t st; + + st = psa_key_derivation_setup(&op, alg); + if (expect_status("tc2 setup", st, PSA_SUCCESS) != 0) return 1; + + st = psa_key_derivation_set_capacity(&op, 64u); + if (expect_status("tc2 set_capacity", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_SECRET, + g_hmac_secret, sizeof(g_hmac_secret)); + if (expect_status("tc2 input SECRET", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_LABEL, + g_label, sizeof(g_label)); + if (expect_status("tc2 input LABEL", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_CONTEXT, + g_context, sizeof(g_context)); + if (expect_status("tc2 input CONTEXT", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_output_bytes(&op, out, 64u); + if (expect_status("tc2 output_bytes[0..63]", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + psa_key_derivation_abort(&op); + + /* Same reconstruction as tc1; must be byte-identical */ + if (reconstruct_hmac(g_hmac_secret, sizeof(g_hmac_secret), + g_label, sizeof(g_label), + g_context, sizeof(g_context), + 64u, ref, 64u) != 0) { + return 1; + } + + if (memcmp(out, ref, 64u) != 0) { + printf("FAIL tc2: single 64-byte output differs from K(1)||K(2)\n"); + return 1; + } + + printf("PASS tc2: single 64-byte call == two 32-byte calls\n"); + return 0; +} + +/* ------------------------------------------------------------------------- + * Test case 3: CMAC variant. + * + * set_capacity(32) → L = 32 bytes = 256 bits = 0x00000100 + * K_0 = CMAC(secret, "label" || 0x00 || "context" || 00000100) + * K(1) = CMAC(secret, 00000001 || "label" || 0x00 || "context" || 00000100 || K_0) + * K(2) = CMAC(secret, 00000002 || "label" || 0x00 || "context" || 00000100 || K_0) + * Output = K(1) || K(2) (32 bytes total, 16 bytes per CMAC block) + * -------------------------------------------------------------------------*/ +static int test_cmac_basic(void) +{ + psa_algorithm_t alg = PSA_ALG_SP800_108_COUNTER_CMAC; + psa_key_derivation_operation_t op = psa_key_derivation_operation_init(); + uint8_t out[32]; + uint8_t ref[32]; + psa_status_t st; + + st = psa_key_derivation_setup(&op, alg); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP tc3: CMAC not supported in this build\n"); + return 0; + } + if (expect_status("tc3 setup", st, PSA_SUCCESS) != 0) return 1; + + st = psa_key_derivation_set_capacity(&op, 32u); + if (expect_status("tc3 set_capacity", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_SECRET, + g_cmac_secret, sizeof(g_cmac_secret)); + if (expect_status("tc3 input SECRET", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_LABEL, + g_label, sizeof(g_label)); + if (expect_status("tc3 input LABEL", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_CONTEXT, + g_context, sizeof(g_context)); + if (expect_status("tc3 input CONTEXT", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_output_bytes(&op, out, 32u); + if (expect_status("tc3 output_bytes(32)", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + psa_key_derivation_abort(&op); + + if (reconstruct_cmac(g_cmac_secret, sizeof(g_cmac_secret), + g_label, sizeof(g_label), + g_context, sizeof(g_context), + 32u, ref, 32u) != 0) { + return 1; + } + + if (memcmp(out, ref, 32u) != 0) { + printf("FAIL tc3: CMAC output mismatch\n"); + return 1; + } + + printf("PASS tc3: CMAC K(1)||K(2) matches reconstruction\n"); + return 0; +} + +/* ------------------------------------------------------------------------- + * Test case 4: SECRET via psa_key_derivation_input_key with PSA_KEY_TYPE_DERIVE. + * + * Import the same HMAC secret bytes as a PSA_KEY_TYPE_DERIVE key with + * alg = PSA_ALG_SP800_108_COUNTER_HMAC(PSA_ALG_SHA_256). Derivation must + * produce identical output to tc1 (input_bytes with same secret). + * -------------------------------------------------------------------------*/ +static int test_hmac_input_key(void) +{ + psa_algorithm_t alg = PSA_ALG_SP800_108_COUNTER_HMAC(PSA_ALG_SHA_256); + psa_key_derivation_operation_t op = psa_key_derivation_operation_init(); + psa_key_attributes_t key_attrs = psa_key_attributes_init(); + psa_key_id_t kid = PSA_KEY_ID_NULL; + uint8_t out[64]; + uint8_t ref[64]; + psa_status_t st; + + /* Import the secret as a DERIVE key whose permitted algorithm is the KDF + * algorithm. input_key() checks key_alg == ctx->alg. */ + psa_set_key_type(&key_attrs, PSA_KEY_TYPE_DERIVE); + psa_set_key_bits(&key_attrs, + (psa_key_bits_t)(sizeof(g_hmac_secret) * 8u)); + psa_set_key_usage_flags(&key_attrs, PSA_KEY_USAGE_DERIVE); + psa_set_key_algorithm(&key_attrs, alg); + + st = psa_import_key(&key_attrs, g_hmac_secret, sizeof(g_hmac_secret), &kid); + if (expect_status("tc4 import DERIVE key", st, PSA_SUCCESS) != 0) return 1; + + st = psa_key_derivation_setup(&op, alg); + if (expect_status("tc4 setup", st, PSA_SUCCESS) != 0) { + psa_destroy_key(kid); + return 1; + } + + st = psa_key_derivation_set_capacity(&op, 64u); + if (expect_status("tc4 set_capacity", st, PSA_SUCCESS) != 0) { + psa_destroy_key(kid); + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_key(&op, PSA_KEY_DERIVATION_INPUT_SECRET, kid); + if (expect_status("tc4 input_key SECRET", st, PSA_SUCCESS) != 0) { + psa_destroy_key(kid); + psa_key_derivation_abort(&op); + return 1; + } + psa_destroy_key(kid); + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_LABEL, + g_label, sizeof(g_label)); + if (expect_status("tc4 input LABEL", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_CONTEXT, + g_context, sizeof(g_context)); + if (expect_status("tc4 input CONTEXT", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_output_bytes(&op, out, 64u); + if (expect_status("tc4 output_bytes", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + psa_key_derivation_abort(&op); + + /* Expected: same as tc1/tc2 reconstruction */ + if (reconstruct_hmac(g_hmac_secret, sizeof(g_hmac_secret), + g_label, sizeof(g_label), + g_context, sizeof(g_context), + 64u, ref, 64u) != 0) { + return 1; + } + + if (memcmp(out, ref, 64u) != 0) { + printf("FAIL tc4: input_key output differs from input_bytes output\n"); + return 1; + } + + printf("PASS tc4: input_key produces same output as input_bytes\n"); + return 0; +} + +/* ------------------------------------------------------------------------- + * Test case 5: Omitting LABEL and CONTEXT entirely. + * + * Both are optional; derivation must succeed with empty label/context. + * Reconstruction uses empty label/context (zero-length): + * K(i) = HMAC(secret, [i]_4 || 0x00 || [L]_4) + * -------------------------------------------------------------------------*/ +static int test_hmac_no_label_no_context(void) +{ + psa_algorithm_t alg = PSA_ALG_SP800_108_COUNTER_HMAC(PSA_ALG_SHA_256); + psa_key_derivation_operation_t op = psa_key_derivation_operation_init(); + uint8_t out[64]; + uint8_t ref[64]; + psa_status_t st; + + st = psa_key_derivation_setup(&op, alg); + if (expect_status("tc5 setup", st, PSA_SUCCESS) != 0) return 1; + + st = psa_key_derivation_set_capacity(&op, 64u); + if (expect_status("tc5 set_capacity", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + /* SECRET only — no LABEL, no CONTEXT */ + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_SECRET, + g_hmac_secret, sizeof(g_hmac_secret)); + if (expect_status("tc5 input SECRET", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_output_bytes(&op, out, 64u); + if (expect_status("tc5 output_bytes", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + psa_key_derivation_abort(&op); + + /* Reconstruction with empty label and context */ + if (reconstruct_hmac(g_hmac_secret, sizeof(g_hmac_secret), + NULL, 0u, + NULL, 0u, + 64u, ref, 64u) != 0) { + return 1; + } + + if (memcmp(out, ref, 64u) != 0) { + printf("FAIL tc5: empty label/context output mismatch\n"); + return 1; + } + + printf("PASS tc5: empty label+context derivation matches reconstruction\n"); + return 0; +} + +/* ------------------------------------------------------------------------- + * Test case 6: output_key produces a 128-bit AES key. + * + * Verify that psa_key_derivation_output_key works end-to-end (AES key + * type is unstructured, fits 16 bytes = 128 bits). + * -------------------------------------------------------------------------*/ +static int test_hmac_output_key(void) +{ + psa_algorithm_t alg = PSA_ALG_SP800_108_COUNTER_HMAC(PSA_ALG_SHA_256); + psa_key_derivation_operation_t op = psa_key_derivation_operation_init(); + psa_key_attributes_t out_attrs = psa_key_attributes_init(); + psa_key_id_t derived_key = PSA_KEY_ID_NULL; + psa_status_t st; + + st = psa_key_derivation_setup(&op, alg); + if (expect_status("tc6 setup", st, PSA_SUCCESS) != 0) return 1; + + st = psa_key_derivation_set_capacity(&op, 16u); + if (expect_status("tc6 set_capacity", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_SECRET, + g_hmac_secret, sizeof(g_hmac_secret)); + if (expect_status("tc6 input SECRET", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_LABEL, + g_label, sizeof(g_label)); + if (expect_status("tc6 input LABEL", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_CONTEXT, + g_context, sizeof(g_context)); + if (expect_status("tc6 input CONTEXT", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + psa_set_key_type(&out_attrs, PSA_KEY_TYPE_AES); + psa_set_key_bits(&out_attrs, 128u); + psa_set_key_usage_flags(&out_attrs, PSA_KEY_USAGE_ENCRYPT); + psa_set_key_algorithm(&out_attrs, PSA_ALG_CTR); + + st = psa_key_derivation_output_key(&out_attrs, &op, &derived_key); + if (expect_status("tc6 output_key(AES-128)", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + psa_destroy_key(derived_key); + psa_key_derivation_abort(&op); + + printf("PASS tc6: output_key produced AES-128 key\n"); + return 0; +} + +/* ------------------------------------------------------------------------- + * Test case 7: CMAC variant with a 15-byte secret must fail. + * + * AES key size must be 16, 24, or 32 bytes. A 15-byte secret is invalid. + * The error must occur at output_bytes() (CMAC validity is checked at + * derivation time, not at input_bytes() time). + * -------------------------------------------------------------------------*/ +static int test_cmac_invalid_key_len(void) +{ + psa_algorithm_t alg = PSA_ALG_SP800_108_COUNTER_CMAC; + psa_key_derivation_operation_t op = psa_key_derivation_operation_init(); + static const uint8_t bad_secret[15] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b + }; + uint8_t out[16]; + psa_status_t st; + + st = psa_key_derivation_setup(&op, alg); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP tc7: CMAC not supported in this build\n"); + return 0; + } + if (expect_status("tc7 setup", st, PSA_SUCCESS) != 0) return 1; + + st = psa_key_derivation_set_capacity(&op, 16u); + if (expect_status("tc7 set_capacity", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_SECRET, + bad_secret, sizeof(bad_secret)); + if (st != PSA_SUCCESS) { + /* Library detected the bad length at input time — also acceptable */ + if (st != PSA_ERROR_INVALID_ARGUMENT) { + printf("FAIL tc7: expected PSA_ERROR_INVALID_ARGUMENT or SUCCESS " + "at input_bytes, got %d\n", (int)st); + psa_key_derivation_abort(&op); + return 1; + } + printf("PASS tc7: CMAC 15-byte secret rejected at input_bytes " + "(PSA_ERROR_INVALID_ARGUMENT)\n"); + psa_key_derivation_abort(&op); + return 0; + } + + /* Library accepted the secret at input time; error must surface at output */ + st = psa_key_derivation_output_bytes(&op, out, 16u); + if (st == PSA_SUCCESS) { + printf("FAIL tc7: CMAC with 15-byte secret must not succeed\n"); + psa_key_derivation_abort(&op); + return 1; + } + if (st != PSA_ERROR_INVALID_ARGUMENT) { + printf("FAIL tc7: expected PSA_ERROR_INVALID_ARGUMENT at output_bytes, " + "got %d\n", (int)st); + psa_key_derivation_abort(&op); + return 1; + } + + psa_key_derivation_abort(&op); + printf("PASS tc7: CMAC 15-byte secret rejected at output_bytes " + "(PSA_ERROR_INVALID_ARGUMENT)\n"); + return 0; +} + +/* ------------------------------------------------------------------------- + * Test case 8: PSA_KEY_DERIVATION_INPUT_SALT must be rejected for SP800-108. + * + * Allowed steps are: SECRET, LABEL, CONTEXT only. SALT must return + * PSA_ERROR_INVALID_ARGUMENT. + * -------------------------------------------------------------------------*/ +static int test_hmac_salt_rejected(void) +{ + psa_algorithm_t alg = PSA_ALG_SP800_108_COUNTER_HMAC(PSA_ALG_SHA_256); + psa_key_derivation_operation_t op = psa_key_derivation_operation_init(); + static const uint8_t salt[] = { 0x01, 0x02, 0x03, 0x04 }; + psa_status_t st; + + st = psa_key_derivation_setup(&op, alg); + if (expect_status("tc8 setup", st, PSA_SUCCESS) != 0) return 1; + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_SALT, + salt, sizeof(salt)); + if (st == PSA_SUCCESS) { + printf("BUG tc8: library accepted SALT input for SP800-108 — " + "SALT is not a valid step for this algorithm\n"); + psa_key_derivation_abort(&op); + return 1; + } + if (st != PSA_ERROR_INVALID_ARGUMENT) { + printf("FAIL tc8: expected PSA_ERROR_INVALID_ARGUMENT for SALT, " + "got %d\n", (int)st); + psa_key_derivation_abort(&op); + return 1; + } + + psa_key_derivation_abort(&op); + printf("PASS tc8: SALT step correctly rejected with " + "PSA_ERROR_INVALID_ARGUMENT\n"); + return 0; +} + +/* ------------------------------------------------------------------------- + * main + * -------------------------------------------------------------------------*/ +int main(void) +{ + psa_status_t st; + + st = psa_crypto_init(); + if (st != PSA_SUCCESS) { + printf("FAIL psa_crypto_init status=%d\n", (int)st); + return 1; + } + + if (test_hmac_two_output_calls() != 0) return 1; + if (test_hmac_single_64_byte_call() != 0) return 1; + if (test_cmac_basic() != 0) return 1; + if (test_hmac_input_key() != 0) return 1; + if (test_hmac_no_label_no_context() != 0) return 1; + if (test_hmac_output_key() != 0) return 1; + if (test_cmac_invalid_key_len() != 0) return 1; + if (test_hmac_salt_rejected() != 0) return 1; + + printf("SP800-108 counter-mode KDF test: OK\n"); + return 0; +} diff --git a/test/psa_server/psa_xof_test.c b/test/psa_server/psa_xof_test.c new file mode 100644 index 0000000..ef30f70 --- /dev/null +++ b/test/psa_server/psa_xof_test.c @@ -0,0 +1,685 @@ +/* psa_xof_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 + */ + +#include "psa_api_test_user_settings.h" + +#ifndef WOLFSSL_USER_SETTINGS +#define WOLFSSL_USER_SETTINGS +#endif + +#include + +#include +#include + +#include + +/* ----------------------------------------------------------------------- + * Helpers + * ----------------------------------------------------------------------- */ + +static int expect_status(const char *label, psa_status_t got, + psa_status_t expected) +{ + if (got != expected) { + printf("FAIL %s status=%d expected=%d\n", label, (int)got, + (int)expected); + return 1; + } + return 0; +} + +static int expect_bytes(const char *label, const uint8_t *got, + const uint8_t *expected, size_t len) +{ + if (memcmp(got, expected, len) != 0) { + size_t i; + printf("FAIL %s output mismatch\n got:", label); + for (i = 0; i < len; i++) printf(" %02x", got[i]); + printf("\n exp:"); + for (i = 0; i < len; i++) printf(" %02x", expected[i]); + printf("\n"); + return 1; + } + return 0; +} + +/* ----------------------------------------------------------------------- + * Known-answer vectors + * + * NIST standard vectors (source: NIST FIPS 202 / SHA-3 test vectors): + * - SHAKE128 empty → first 32 bytes + * - SHAKE256 empty → first 32 bytes + * - SHAKE128 "The quick brown fox…" (43 bytes) → first 16 bytes + * + * Python-generated vectors (python3 -c "import hashlib; …"): + * - SHAKE128 bytes(range(100)) → 64 bytes + * - SHAKE256 bytes(range(100)) → 64 bytes + * - SHAKE128 bytes(range(100)) → 200 bytes (crosses rate=168 boundary) + * - SHAKE256 bytes(range(100)) → 200 bytes (crosses rate=136 boundary) + * ----------------------------------------------------------------------- */ + +/* NIST: SHAKE128, empty message, first 32 output bytes */ +static const uint8_t kat_shake128_empty_32[32] = { + 0x7f, 0x9c, 0x2b, 0xa4, 0xe8, 0x8f, 0x82, 0x7d, + 0x61, 0x60, 0x45, 0x50, 0x76, 0x05, 0x85, 0x3e, + 0xd7, 0x3b, 0x80, 0x93, 0xf6, 0xef, 0xbc, 0x88, + 0xeb, 0x1a, 0x6e, 0xac, 0xfa, 0x66, 0xef, 0x26 +}; + +/* NIST: SHAKE256, empty message, first 32 output bytes */ +static const uint8_t kat_shake256_empty_32[32] = { + 0x46, 0xb9, 0xdd, 0x2b, 0x0b, 0xa8, 0x8d, 0x13, + 0x23, 0x3b, 0x3f, 0xeb, 0x74, 0x3e, 0xeb, 0x24, + 0x3f, 0xcd, 0x52, 0xea, 0x62, 0xb8, 0x1b, 0x82, + 0xb5, 0x0c, 0x27, 0x64, 0x6e, 0xd5, 0x76, 0x2f +}; + +/* NIST: SHAKE128, "The quick brown fox jumps over the lazy dog" → first 16 B */ +static const uint8_t fox_msg[] = { + 'T','h','e',' ','q','u','i','c','k',' ','b','r','o','w','n',' ', + 'f','o','x',' ','j','u','m','p','s',' ','o','v','e','r',' ','t', + 'h','e',' ','l','a','z','y',' ','d','o','g' +}; +static const uint8_t kat_shake128_fox_16[16] = { + 0xf4, 0x20, 0x2e, 0x3c, 0x58, 0x52, 0xf9, 0x18, + 0x2a, 0x04, 0x30, 0xfd, 0x81, 0x44, 0xf0, 0xa7 +}; + +/* Pattern input: bytes(range(100)) */ +static uint8_t pattern100[100]; /* filled in main() */ + +/* Python hashlib: SHAKE128(bytes(range(100)), 64 bytes) */ +static const uint8_t kat_shake128_p100_64[64] = { + 0x04, 0xeb, 0xa3, 0x0b, 0x78, 0x55, 0x0e, 0xe4, + 0x61, 0xbb, 0x4d, 0x59, 0x1d, 0x2b, 0x36, 0x67, + 0xeb, 0x84, 0x40, 0x02, 0xee, 0xe5, 0xa1, 0xc7, + 0x19, 0x9f, 0x7d, 0x04, 0x20, 0x38, 0x5f, 0x11, + 0x18, 0xa3, 0x6d, 0xbd, 0x5a, 0xb1, 0x97, 0x39, + 0xee, 0xa2, 0xd2, 0xe1, 0x78, 0x90, 0x08, 0xf9, + 0x49, 0x23, 0x02, 0xb3, 0x11, 0x5e, 0x36, 0xf4, + 0x7e, 0x83, 0x8c, 0x8a, 0xf0, 0xeb, 0x8e, 0x93 +}; + +/* Python hashlib: SHAKE256(bytes(range(100)), 64 bytes) */ +static const uint8_t kat_shake256_p100_64[64] = { + 0xbc, 0x98, 0xc7, 0x6f, 0xbb, 0x61, 0xfc, 0xa7, + 0xda, 0x03, 0x2d, 0x69, 0x42, 0x3d, 0x14, 0x0a, + 0xbf, 0x2a, 0xb5, 0xaa, 0xb4, 0x27, 0x1c, 0x29, + 0x19, 0x7c, 0x84, 0x30, 0x3c, 0x0d, 0x90, 0xff, + 0x78, 0xfe, 0xb8, 0x85, 0xfe, 0xd1, 0xc9, 0x8c, + 0x9f, 0xa1, 0xbd, 0xe0, 0x66, 0xe2, 0xe1, 0x45, + 0x4d, 0xc6, 0xe2, 0xea, 0xbb, 0x3a, 0x2c, 0xfc, + 0xff, 0x06, 0x6b, 0x26, 0x6b, 0x03, 0x5c, 0x5c +}; + +/* Python hashlib: SHAKE128(bytes(range(100)), 200 bytes) + * Rate = 168 bytes, so 200 bytes crosses exactly one block boundary. */ +static const uint8_t kat_shake128_p100_200[200] = { + 0x04, 0xeb, 0xa3, 0x0b, 0x78, 0x55, 0x0e, 0xe4, + 0x61, 0xbb, 0x4d, 0x59, 0x1d, 0x2b, 0x36, 0x67, + 0xeb, 0x84, 0x40, 0x02, 0xee, 0xe5, 0xa1, 0xc7, + 0x19, 0x9f, 0x7d, 0x04, 0x20, 0x38, 0x5f, 0x11, + 0x18, 0xa3, 0x6d, 0xbd, 0x5a, 0xb1, 0x97, 0x39, + 0xee, 0xa2, 0xd2, 0xe1, 0x78, 0x90, 0x08, 0xf9, + 0x49, 0x23, 0x02, 0xb3, 0x11, 0x5e, 0x36, 0xf4, + 0x7e, 0x83, 0x8c, 0x8a, 0xf0, 0xeb, 0x8e, 0x93, + 0x56, 0x98, 0x15, 0xca, 0xd9, 0x98, 0xde, 0xce, + 0xd9, 0xbf, 0xb0, 0x64, 0xbe, 0xd1, 0xfc, 0xb8, + 0xb2, 0xc1, 0x4b, 0x78, 0x47, 0xa9, 0x5d, 0x8a, + 0xc3, 0xeb, 0x63, 0xa3, 0x0b, 0x62, 0x89, 0xd9, + 0x6f, 0xc8, 0x55, 0x39, 0x47, 0x27, 0x56, 0x0b, + 0x20, 0x1e, 0x07, 0x40, 0x63, 0xa5, 0x95, 0xc9, + 0xe4, 0x1a, 0xf0, 0x91, 0x36, 0x2e, 0x55, 0xfc, + 0x1e, 0x8b, 0x13, 0xc0, 0xa9, 0x20, 0xae, 0x83, + 0x96, 0x1e, 0x46, 0x64, 0xf9, 0xa1, 0x23, 0x5d, + 0x4d, 0x0f, 0x4e, 0xa2, 0xc9, 0x3c, 0x89, 0xf7, + 0xf8, 0x48, 0x08, 0xac, 0x94, 0x3d, 0x1a, 0x3d, + 0x92, 0x7b, 0x64, 0xb4, 0x0b, 0xf3, 0x3d, 0x47, + 0x0b, 0x42, 0x60, 0x1e, 0xff, 0x17, 0xc0, 0xb6, + 0x2e, 0x03, 0x2c, 0xb1, 0x02, 0xea, 0xcd, 0xa8, + 0x39, 0x2d, 0x75, 0x64, 0x1d, 0x8e, 0x3c, 0x4b, + 0x27, 0xd0, 0xa9, 0x48, 0x7d, 0x6a, 0xd7, 0xb0, + 0x4c, 0xa4, 0x70, 0x79, 0xa4, 0x59, 0xa6, 0x43 +}; + +/* Python hashlib: SHAKE256(bytes(range(100)), 200 bytes) + * Rate = 136 bytes, so 200 bytes crosses exactly one block boundary. */ +static const uint8_t kat_shake256_p100_200[200] = { + 0xbc, 0x98, 0xc7, 0x6f, 0xbb, 0x61, 0xfc, 0xa7, + 0xda, 0x03, 0x2d, 0x69, 0x42, 0x3d, 0x14, 0x0a, + 0xbf, 0x2a, 0xb5, 0xaa, 0xb4, 0x27, 0x1c, 0x29, + 0x19, 0x7c, 0x84, 0x30, 0x3c, 0x0d, 0x90, 0xff, + 0x78, 0xfe, 0xb8, 0x85, 0xfe, 0xd1, 0xc9, 0x8c, + 0x9f, 0xa1, 0xbd, 0xe0, 0x66, 0xe2, 0xe1, 0x45, + 0x4d, 0xc6, 0xe2, 0xea, 0xbb, 0x3a, 0x2c, 0xfc, + 0xff, 0x06, 0x6b, 0x26, 0x6b, 0x03, 0x5c, 0x5c, + 0xc2, 0xad, 0x2e, 0xc3, 0x27, 0x02, 0x1f, 0x9f, + 0x00, 0xbe, 0x0a, 0xba, 0x38, 0x54, 0x63, 0x74, + 0xdc, 0x5f, 0xc3, 0xf1, 0xa6, 0xa6, 0x1a, 0xe0, + 0x76, 0xd4, 0x99, 0x89, 0x97, 0x55, 0x0a, 0x2d, + 0xfa, 0x91, 0x73, 0x99, 0xa3, 0x45, 0xd2, 0xc0, + 0xc4, 0xf5, 0x6f, 0x44, 0x11, 0xf4, 0x12, 0xa7, + 0x4a, 0xdd, 0x45, 0xe2, 0xd0, 0x4b, 0xf4, 0x79, + 0xd1, 0xf6, 0xe4, 0xf9, 0x2a, 0xb0, 0x6f, 0x63, + 0xb0, 0xa7, 0x7b, 0x8a, 0x82, 0x80, 0xaf, 0xe8, + 0xe5, 0xa0, 0x6f, 0x63, 0x8e, 0xc5, 0x51, 0x60, + 0xa9, 0x84, 0x2e, 0x4c, 0x5e, 0x87, 0x59, 0x9d, + 0x16, 0x68, 0x38, 0x27, 0xa7, 0x1f, 0x50, 0x5c, + 0x8c, 0x61, 0x6a, 0x96, 0xe0, 0x72, 0xdd, 0x72, + 0x34, 0x44, 0x39, 0x10, 0xb1, 0x5c, 0xc0, 0x37, + 0x39, 0x4e, 0x12, 0x58, 0xc8, 0xfe, 0x3c, 0x10, + 0x67, 0x26, 0xad, 0x38, 0x28, 0x62, 0x0b, 0x03, + 0xdd, 0xf5, 0xc6, 0x50, 0x80, 0x3e, 0x56, 0x61 +}; + +/* ----------------------------------------------------------------------- + * TC-1: SHAKE128 and SHAKE256 empty-input KATs (32 bytes, one-shot) + * ----------------------------------------------------------------------- */ +static int test_empty_kat(void) +{ + psa_xof_operation_t op; + uint8_t out[32]; + psa_status_t st; + + /* SHAKE128 */ + op = psa_xof_operation_init(); + st = psa_xof_setup(&op, PSA_ALG_SHAKE128); + if (expect_status("tc1 shake128 setup", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op, out, sizeof(out)); + if (expect_status("tc1 shake128 output", st, PSA_SUCCESS) != 0) return 1; + if (expect_bytes("tc1 shake128 empty 32", out, kat_shake128_empty_32, + sizeof(out)) != 0) return 1; + (void)psa_xof_abort(&op); + + /* SHAKE256 */ + op = psa_xof_operation_init(); + st = psa_xof_setup(&op, PSA_ALG_SHAKE256); + if (expect_status("tc1 shake256 setup", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op, out, sizeof(out)); + if (expect_status("tc1 shake256 output", st, PSA_SUCCESS) != 0) return 1; + if (expect_bytes("tc1 shake256 empty 32", out, kat_shake256_empty_32, + sizeof(out)) != 0) return 1; + (void)psa_xof_abort(&op); + + return 0; +} + +/* ----------------------------------------------------------------------- + * TC-2: Non-empty input KATs (bytes(range(100)) → 64 bytes) + * Vectors generated with Python hashlib. + * ----------------------------------------------------------------------- */ +static int test_nonempty_kat(void) +{ + psa_xof_operation_t op; + uint8_t out[64]; + psa_status_t st; + + /* SHAKE128 */ + op = psa_xof_operation_init(); + st = psa_xof_setup(&op, PSA_ALG_SHAKE128); + if (expect_status("tc2 shake128 setup", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_update(&op, pattern100, sizeof(pattern100)); + if (expect_status("tc2 shake128 update", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op, out, sizeof(out)); + if (expect_status("tc2 shake128 output", st, PSA_SUCCESS) != 0) return 1; + if (expect_bytes("tc2 shake128 p100 64", out, kat_shake128_p100_64, + sizeof(out)) != 0) return 1; + (void)psa_xof_abort(&op); + + /* SHAKE256 */ + op = psa_xof_operation_init(); + st = psa_xof_setup(&op, PSA_ALG_SHAKE256); + if (expect_status("tc2 shake256 setup", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_update(&op, pattern100, sizeof(pattern100)); + if (expect_status("tc2 shake256 update", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op, out, sizeof(out)); + if (expect_status("tc2 shake256 output", st, PSA_SUCCESS) != 0) return 1; + if (expect_bytes("tc2 shake256 p100 64", out, kat_shake256_p100_64, + sizeof(out)) != 0) return 1; + (void)psa_xof_abort(&op); + + return 0; +} + +/* ----------------------------------------------------------------------- + * TC-3: Incremental absorb — 3 chunks vs one-shot + * Split pattern100 into [33, 33, 34] bytes. + * ----------------------------------------------------------------------- */ +static int test_incremental_absorb(void) +{ + psa_xof_operation_t op_incremental, op_oneshot; + uint8_t out_inc[64], out_one[64]; + psa_status_t st; + + /* SHAKE128 incremental */ + op_incremental = psa_xof_operation_init(); + st = psa_xof_setup(&op_incremental, PSA_ALG_SHAKE128); + if (expect_status("tc3 shake128 inc setup", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_update(&op_incremental, pattern100, 33); + if (expect_status("tc3 shake128 upd1", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_update(&op_incremental, pattern100 + 33, 33); + if (expect_status("tc3 shake128 upd2", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_update(&op_incremental, pattern100 + 66, 34); + if (expect_status("tc3 shake128 upd3", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op_incremental, out_inc, sizeof(out_inc)); + if (expect_status("tc3 shake128 inc output", st, PSA_SUCCESS) != 0) return 1; + (void)psa_xof_abort(&op_incremental); + + /* SHAKE128 one-shot */ + op_oneshot = psa_xof_operation_init(); + st = psa_xof_setup(&op_oneshot, PSA_ALG_SHAKE128); + if (expect_status("tc3 shake128 one setup", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_update(&op_oneshot, pattern100, sizeof(pattern100)); + if (expect_status("tc3 shake128 one update", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op_oneshot, out_one, sizeof(out_one)); + if (expect_status("tc3 shake128 one output", st, PSA_SUCCESS) != 0) return 1; + (void)psa_xof_abort(&op_oneshot); + + if (expect_bytes("tc3 shake128 inc==one", out_inc, out_one, + sizeof(out_inc)) != 0) return 1; + + /* SHAKE256 incremental */ + op_incremental = psa_xof_operation_init(); + st = psa_xof_setup(&op_incremental, PSA_ALG_SHAKE256); + if (expect_status("tc3 shake256 inc setup", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_update(&op_incremental, pattern100, 33); + if (expect_status("tc3 shake256 upd1", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_update(&op_incremental, pattern100 + 33, 33); + if (expect_status("tc3 shake256 upd2", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_update(&op_incremental, pattern100 + 66, 34); + if (expect_status("tc3 shake256 upd3", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op_incremental, out_inc, sizeof(out_inc)); + if (expect_status("tc3 shake256 inc output", st, PSA_SUCCESS) != 0) return 1; + (void)psa_xof_abort(&op_incremental); + + /* SHAKE256 one-shot */ + op_oneshot = psa_xof_operation_init(); + st = psa_xof_setup(&op_oneshot, PSA_ALG_SHAKE256); + if (expect_status("tc3 shake256 one setup", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_update(&op_oneshot, pattern100, sizeof(pattern100)); + if (expect_status("tc3 shake256 one update", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op_oneshot, out_one, sizeof(out_one)); + if (expect_status("tc3 shake256 one output", st, PSA_SUCCESS) != 0) return 1; + (void)psa_xof_abort(&op_oneshot); + + if (expect_bytes("tc3 shake256 inc==one", out_inc, out_one, + sizeof(out_inc)) != 0) return 1; + + return 0; +} + +/* ----------------------------------------------------------------------- + * TC-4a: Incremental squeeze — 1+7+25+31 = 64 bytes equals one-shot 64B + * TC-4b: 200-byte output in small pieces equals reference (block-boundary) + * ----------------------------------------------------------------------- */ +static int test_incremental_squeeze(void) +{ + psa_xof_operation_t op_pieces, op_oneshot; + uint8_t out_pieces[64], out_one[64]; + uint8_t out_big_pieces[200], out_big_ref[200]; + size_t off; + psa_status_t st; + + /* TC-4a SHAKE128: squeeze 1+7+25+31 bytes */ + op_pieces = psa_xof_operation_init(); + st = psa_xof_setup(&op_pieces, PSA_ALG_SHAKE128); + if (expect_status("tc4a shake128 pieces setup", st, PSA_SUCCESS) != 0) + return 1; + st = psa_xof_update(&op_pieces, pattern100, sizeof(pattern100)); + if (expect_status("tc4a shake128 update", st, PSA_SUCCESS) != 0) return 1; + off = 0; + st = psa_xof_output(&op_pieces, out_pieces + off, 1); off += 1; + if (expect_status("tc4a shake128 out1", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op_pieces, out_pieces + off, 7); off += 7; + if (expect_status("tc4a shake128 out7", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op_pieces, out_pieces + off, 25); off += 25; + if (expect_status("tc4a shake128 out25", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op_pieces, out_pieces + off, 31); off += 31; + if (expect_status("tc4a shake128 out31", st, PSA_SUCCESS) != 0) return 1; + (void)psa_xof_abort(&op_pieces); + + op_oneshot = psa_xof_operation_init(); + st = psa_xof_setup(&op_oneshot, PSA_ALG_SHAKE128); + if (expect_status("tc4a shake128 one setup", st, PSA_SUCCESS) != 0) + return 1; + st = psa_xof_update(&op_oneshot, pattern100, sizeof(pattern100)); + if (expect_status("tc4a shake128 one update", st, PSA_SUCCESS) != 0) + return 1; + st = psa_xof_output(&op_oneshot, out_one, sizeof(out_one)); + if (expect_status("tc4a shake128 one output", st, PSA_SUCCESS) != 0) + return 1; + (void)psa_xof_abort(&op_oneshot); + + if (expect_bytes("tc4a shake128 pieces==one", out_pieces, out_one, + sizeof(out_one)) != 0) return 1; + + /* TC-4a SHAKE256: same split */ + op_pieces = psa_xof_operation_init(); + st = psa_xof_setup(&op_pieces, PSA_ALG_SHAKE256); + if (expect_status("tc4a shake256 pieces setup", st, PSA_SUCCESS) != 0) + return 1; + st = psa_xof_update(&op_pieces, pattern100, sizeof(pattern100)); + if (expect_status("tc4a shake256 update", st, PSA_SUCCESS) != 0) return 1; + off = 0; + st = psa_xof_output(&op_pieces, out_pieces + off, 1); off += 1; + if (expect_status("tc4a shake256 out1", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op_pieces, out_pieces + off, 7); off += 7; + if (expect_status("tc4a shake256 out7", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op_pieces, out_pieces + off, 25); off += 25; + if (expect_status("tc4a shake256 out25", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op_pieces, out_pieces + off, 31); off += 31; + if (expect_status("tc4a shake256 out31", st, PSA_SUCCESS) != 0) return 1; + (void)psa_xof_abort(&op_pieces); + + op_oneshot = psa_xof_operation_init(); + st = psa_xof_setup(&op_oneshot, PSA_ALG_SHAKE256); + if (expect_status("tc4a shake256 one setup", st, PSA_SUCCESS) != 0) + return 1; + st = psa_xof_update(&op_oneshot, pattern100, sizeof(pattern100)); + if (expect_status("tc4a shake256 one update", st, PSA_SUCCESS) != 0) + return 1; + st = psa_xof_output(&op_oneshot, out_one, sizeof(out_one)); + if (expect_status("tc4a shake256 one output", st, PSA_SUCCESS) != 0) + return 1; + (void)psa_xof_abort(&op_oneshot); + + if (expect_bytes("tc4a shake256 pieces==one", out_pieces, out_one, + sizeof(out_one)) != 0) return 1; + + /* TC-4b SHAKE128: 200-byte output in 8-byte pieces vs reference + * 200 > rate(168), so this crosses the block boundary. */ + op_pieces = psa_xof_operation_init(); + st = psa_xof_setup(&op_pieces, PSA_ALG_SHAKE128); + if (expect_status("tc4b shake128 big setup", st, PSA_SUCCESS) != 0) + return 1; + st = psa_xof_update(&op_pieces, pattern100, sizeof(pattern100)); + if (expect_status("tc4b shake128 big update", st, PSA_SUCCESS) != 0) + return 1; + for (off = 0; off < sizeof(out_big_pieces); off += 8) { + st = psa_xof_output(&op_pieces, out_big_pieces + off, 8); + if (expect_status("tc4b shake128 big out chunk", st, + PSA_SUCCESS) != 0) return 1; + } + (void)psa_xof_abort(&op_pieces); + + op_oneshot = psa_xof_operation_init(); + st = psa_xof_setup(&op_oneshot, PSA_ALG_SHAKE128); + if (expect_status("tc4b shake128 big one setup", st, PSA_SUCCESS) != 0) + return 1; + st = psa_xof_update(&op_oneshot, pattern100, sizeof(pattern100)); + if (expect_status("tc4b shake128 big one update", st, PSA_SUCCESS) != 0) + return 1; + st = psa_xof_output(&op_oneshot, out_big_ref, sizeof(out_big_ref)); + if (expect_status("tc4b shake128 big one output", st, PSA_SUCCESS) != 0) + return 1; + (void)psa_xof_abort(&op_oneshot); + + if (expect_bytes("tc4b shake128 big pieces==ref", out_big_pieces, out_big_ref, + sizeof(out_big_ref)) != 0) return 1; + + /* Also verify one-shot matches the python KAT */ + if (expect_bytes("tc4b shake128 ref==kat", out_big_ref, kat_shake128_p100_200, + sizeof(kat_shake128_p100_200)) != 0) return 1; + + /* TC-4b SHAKE256: 200-byte output in 8-byte pieces vs reference + * 200 > rate(136), so this crosses the block boundary. */ + op_pieces = psa_xof_operation_init(); + st = psa_xof_setup(&op_pieces, PSA_ALG_SHAKE256); + if (expect_status("tc4b shake256 big setup", st, PSA_SUCCESS) != 0) + return 1; + st = psa_xof_update(&op_pieces, pattern100, sizeof(pattern100)); + if (expect_status("tc4b shake256 big update", st, PSA_SUCCESS) != 0) + return 1; + for (off = 0; off < sizeof(out_big_pieces); off += 8) { + st = psa_xof_output(&op_pieces, out_big_pieces + off, 8); + if (expect_status("tc4b shake256 big out chunk", st, + PSA_SUCCESS) != 0) return 1; + } + (void)psa_xof_abort(&op_pieces); + + op_oneshot = psa_xof_operation_init(); + st = psa_xof_setup(&op_oneshot, PSA_ALG_SHAKE256); + if (expect_status("tc4b shake256 big one setup", st, PSA_SUCCESS) != 0) + return 1; + st = psa_xof_update(&op_oneshot, pattern100, sizeof(pattern100)); + if (expect_status("tc4b shake256 big one update", st, PSA_SUCCESS) != 0) + return 1; + st = psa_xof_output(&op_oneshot, out_big_ref, sizeof(out_big_ref)); + if (expect_status("tc4b shake256 big one output", st, PSA_SUCCESS) != 0) + return 1; + (void)psa_xof_abort(&op_oneshot); + + if (expect_bytes("tc4b shake256 big pieces==ref", out_big_pieces, out_big_ref, + sizeof(out_big_ref)) != 0) return 1; + + if (expect_bytes("tc4b shake256 ref==kat", out_big_ref, kat_shake256_p100_200, + sizeof(kat_shake256_p100_200)) != 0) return 1; + + return 0; +} + +/* ----------------------------------------------------------------------- + * TC-5: Zero-length update and zero-length output are no-op successes + * ----------------------------------------------------------------------- */ +static int test_zero_length_ops(void) +{ + psa_xof_operation_t op; + uint8_t out[1]; + psa_status_t st; + + op = psa_xof_operation_init(); + st = psa_xof_setup(&op, PSA_ALG_SHAKE128); + if (expect_status("tc5 setup", st, PSA_SUCCESS) != 0) return 1; + + /* Zero-length update must succeed */ + st = psa_xof_update(&op, NULL, 0); + if (expect_status("tc5 zero update", st, PSA_SUCCESS) != 0) return 1; + + /* Absorb some real data so the squeeze result is deterministic */ + st = psa_xof_update(&op, pattern100, sizeof(pattern100)); + if (expect_status("tc5 real update", st, PSA_SUCCESS) != 0) return 1; + + /* Start squeezing */ + st = psa_xof_output(&op, out, sizeof(out)); + if (expect_status("tc5 first output", st, PSA_SUCCESS) != 0) return 1; + + /* Zero-length output after squeezing has started must succeed */ + st = psa_xof_output(&op, NULL, 0); + if (expect_status("tc5 zero output after squeeze", st, PSA_SUCCESS) != 0) + return 1; + + (void)psa_xof_abort(&op); + return 0; +} + +/* ----------------------------------------------------------------------- + * TC-6: State-machine error cases + * a) update after output → PSA_ERROR_BAD_STATE + * b) setup on an active op → PSA_ERROR_BAD_STATE + * c) abort then reuse (setup again) works + * ----------------------------------------------------------------------- */ +static int test_state_errors(void) +{ + psa_xof_operation_t op; + uint8_t out[8]; + psa_status_t st; + + /* TC-6a: update after output */ + op = psa_xof_operation_init(); + st = psa_xof_setup(&op, PSA_ALG_SHAKE128); + if (expect_status("tc6a setup", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op, out, sizeof(out)); + if (expect_status("tc6a first output", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_update(&op, pattern100, 10); + if (expect_status("tc6a update after output", st, + PSA_ERROR_BAD_STATE) != 0) return 1; + /* operation was aborted by the failed update */ + + /* TC-6b: setup on an active (not-yet-squeezed) op */ + op = psa_xof_operation_init(); + st = psa_xof_setup(&op, PSA_ALG_SHAKE128); + if (expect_status("tc6b first setup", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_update(&op, pattern100, 10); + if (expect_status("tc6b update", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_setup(&op, PSA_ALG_SHAKE128); + if (expect_status("tc6b setup on active op", st, + PSA_ERROR_BAD_STATE) != 0) return 1; + (void)psa_xof_abort(&op); + + /* TC-6c: abort then reuse */ + op = psa_xof_operation_init(); + st = psa_xof_setup(&op, PSA_ALG_SHAKE128); + if (expect_status("tc6c first setup", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_update(&op, pattern100, sizeof(pattern100)); + if (expect_status("tc6c update", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_abort(&op); + if (expect_status("tc6c abort", st, PSA_SUCCESS) != 0) return 1; + + /* Re-setup the same operation object */ + st = psa_xof_setup(&op, PSA_ALG_SHAKE128); + if (expect_status("tc6c reuse setup", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op, out, sizeof(out)); + if (expect_status("tc6c reuse output", st, PSA_SUCCESS) != 0) return 1; + /* Output must be the empty-message prefix */ + if (expect_bytes("tc6c reuse output==empty prefix", out, + kat_shake128_empty_32, sizeof(out)) != 0) return 1; + (void)psa_xof_abort(&op); + + return 0; +} + +/* ----------------------------------------------------------------------- + * TC-7: psa_xof_set_context on SHAKE → PSA_ERROR_INVALID_ARGUMENT + * ----------------------------------------------------------------------- */ +static int test_set_context_shake(void) +{ + psa_xof_operation_t op; + static const uint8_t ctx_data[] = { 0x01, 0x02, 0x03 }; + psa_status_t st; + + op = psa_xof_operation_init(); + st = psa_xof_setup(&op, PSA_ALG_SHAKE128); + if (expect_status("tc7 shake128 setup", st, PSA_SUCCESS) != 0) return 1; + + st = psa_xof_set_context(&op, ctx_data, sizeof(ctx_data)); + if (expect_status("tc7 shake128 set_context", st, + PSA_ERROR_INVALID_ARGUMENT) != 0) return 1; + /* operation aborted by the failed call; do not double-abort */ + + op = psa_xof_operation_init(); + st = psa_xof_setup(&op, PSA_ALG_SHAKE256); + if (expect_status("tc7 shake256 setup", st, PSA_SUCCESS) != 0) return 1; + + st = psa_xof_set_context(&op, ctx_data, sizeof(ctx_data)); + if (expect_status("tc7 shake256 set_context", st, + PSA_ERROR_INVALID_ARGUMENT) != 0) return 1; + + return 0; +} + +/* ----------------------------------------------------------------------- + * TC-8: PSA_ALG_ASCON_XOF128 setup → PSA_ERROR_NOT_SUPPORTED + * ----------------------------------------------------------------------- */ +static int test_ascon_xof_not_supported(void) +{ + psa_xof_operation_t op; + psa_status_t st; + + op = psa_xof_operation_init(); + st = psa_xof_setup(&op, PSA_ALG_ASCON_XOF128); + if (expect_status("tc8 ascon_xof128 setup", st, + PSA_ERROR_NOT_SUPPORTED) != 0) return 1; + + return 0; +} + +/* ----------------------------------------------------------------------- + * TC-9: Non-XOF algorithm (PSA_ALG_SHA_256) → PSA_ERROR_INVALID_ARGUMENT + * ----------------------------------------------------------------------- */ +static int test_non_xof_alg(void) +{ + psa_xof_operation_t op; + psa_status_t st; + + op = psa_xof_operation_init(); + st = psa_xof_setup(&op, PSA_ALG_SHA_256); + if (expect_status("tc9 sha256 as xof", st, + PSA_ERROR_INVALID_ARGUMENT) != 0) return 1; + + return 0; +} + +/* ----------------------------------------------------------------------- + * TC-NIST-FOX: fox message 16-byte prefix KAT (NIST) + * ----------------------------------------------------------------------- */ +static int test_fox_kat(void) +{ + psa_xof_operation_t op; + uint8_t out[16]; + psa_status_t st; + + op = psa_xof_operation_init(); + st = psa_xof_setup(&op, PSA_ALG_SHAKE128); + if (expect_status("tc-fox shake128 setup", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_update(&op, fox_msg, sizeof(fox_msg)); + if (expect_status("tc-fox shake128 update", st, PSA_SUCCESS) != 0) return 1; + st = psa_xof_output(&op, out, sizeof(out)); + if (expect_status("tc-fox shake128 output", st, PSA_SUCCESS) != 0) return 1; + if (expect_bytes("tc-fox shake128 fox 16", out, kat_shake128_fox_16, + sizeof(out)) != 0) return 1; + (void)psa_xof_abort(&op); + + return 0; +} + +/* ----------------------------------------------------------------------- + * main + * ----------------------------------------------------------------------- */ +int main(void) +{ + psa_status_t st; + size_t i; + + /* Build runtime pattern: bytes(range(100)) */ + for (i = 0; i < sizeof(pattern100); i++) + pattern100[i] = (uint8_t)i; + + st = psa_crypto_init(); + if (st != PSA_SUCCESS) { + printf("FAIL psa_crypto_init status=%d\n", (int)st); + return 1; + } + + if (test_empty_kat() != 0) return 1; + if (test_fox_kat() != 0) return 1; + if (test_nonempty_kat() != 0) return 1; + if (test_incremental_absorb() != 0) return 1; + if (test_incremental_squeeze() != 0) return 1; + if (test_zero_length_ops() != 0) return 1; + if (test_state_errors() != 0) return 1; + if (test_set_context_shake() != 0) return 1; + if (test_ascon_xof_not_supported() != 0) return 1; + if (test_non_xof_alg() != 0) return 1; + + printf("PSA XOF test: OK\n"); + return 0; +} From c93281ba4dc4b125eecab0d27eb3ed9a656a4fcd Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 19:13:02 +0200 Subject: [PATCH 21/24] build-matrix: survive feature-off builds and cover the new 1.4 features Running the build-config matrix against the 1.4 changes exposed -Werror failures when the new backends are compiled out: unused locals in psa_wrap_key/psa_unwrap_key without HAVE_AES_KEYWRAP, an unused key_bits in psa_decapsulate without WOLFSSL_HAVE_MLKEM, and an unused bits parameter in psa_pq_check_key_size_valid when every PQC backend is off. Fix all three. Sync build-variant.sh's baseline with the new wolfCrypt knobs (ML-KEM, verify-only LMS/XMSS, Ascon + experimental opt-in, AES-KW + AES direct, XChaCha, CMAC-KDF) and add matrix lanes that disable each new feature. Companion removals for existing lanes: chacha20-poly1305 also drops XChaCha, cmac drops the CMAC KDF, sha3-ed448 drops ML-KEM (it needs SHA-3/SHAKE), and AES key wrap explicitly carries WOLFSSL_AES_DIRECT, which wolfSSL requires for HAVE_AES_KEYWRAP. All 35 lanes build clean locally. --- .github/workflows/build-config-matrix.yml | 18 +++++++++++++++--- build-test/build-variant.sh | 11 +++++++++++ src/psa_kem.c | 1 + src/psa_key_wrap.c | 4 ++++ src/psa_pq.c | 3 +++ user_settings.h | 2 ++ 6 files changed, 36 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-config-matrix.yml b/.github/workflows/build-config-matrix.yml index 3e9a538..422433c 100644 --- a/.github/workflows/build-config-matrix.yml +++ b/.github/workflows/build-config-matrix.yml @@ -26,7 +26,7 @@ jobs: - name: sha512-family modifiers: "-WOLFSSL_SHA512 -WOLFSSL_SHA384 -HAVE_ED25519 -WOLFSSL_ED25519_STREAMING_VERIFY -HAVE_ED448 -WOLFSSL_ED448_STREAMING_VERIFY +NO_SHA512" - name: sha3-ed448 - modifiers: "-WOLFSSL_SHA3 -WOLFSSL_SHAKE128 -WOLFSSL_SHAKE256 -WOLFSSL_HAVE_MLDSA -HAVE_ED448 -WOLFSSL_ED448_STREAMING_VERIFY" + modifiers: "-WOLFSSL_SHA3 -WOLFSSL_SHAKE128 -WOLFSSL_SHAKE256 -WOLFSSL_HAVE_MLDSA -WOLFSSL_HAVE_MLKEM -HAVE_ED448 -WOLFSSL_ED448_STREAMING_VERIFY" - name: des3 modifiers: "-WOLFSSL_DES3 -WOLFSSL_DES_ECB +NO_DES3" - name: aes-gcm @@ -44,9 +44,9 @@ jobs: - name: des-ecb modifiers: "-WOLFSSL_DES_ECB" - name: cmac - modifiers: "-WOLFSSL_CMAC" + modifiers: "-WOLFSSL_CMAC -HAVE_CMAC_KDF" - name: chacha20-poly1305 - modifiers: "-HAVE_CHACHA -HAVE_POLY1305" + modifiers: "-HAVE_CHACHA -HAVE_POLY1305 -HAVE_XCHACHA" - name: curve25519 modifiers: "-HAVE_CURVE25519" - name: ed25519 @@ -59,6 +59,18 @@ jobs: modifiers: "-WOLFSSL_HAVE_MLDSA" - name: mldsa-44-only modifiers: "+WOLFSSL_NO_ML_DSA_65 +WOLFSSL_NO_ML_DSA_87" + - name: mlkem + modifiers: "-WOLFSSL_HAVE_MLKEM" + - name: lms-xmss + modifiers: "-WOLFSSL_HAVE_LMS -WOLFSSL_LMS_VERIFY_ONLY -WOLFSSL_HAVE_XMSS -WOLFSSL_XMSS_VERIFY_ONLY" + - name: ascon + modifiers: "-HAVE_ASCON" + - name: aes-keywrap + modifiers: "-HAVE_AES_KEYWRAP -WOLFSSL_AES_DIRECT" + - name: xchacha + modifiers: "-HAVE_XCHACHA" + - name: cmac-kdf + modifiers: "-HAVE_CMAC_KDF" - name: hkdf modifiers: "-HAVE_HKDF -HAVE_ECC_ENCRYPT" - name: tls-prf diff --git a/build-test/build-variant.sh b/build-test/build-variant.sh index 92729ef..b84ccd3 100755 --- a/build-test/build-variant.sh +++ b/build-test/build-variant.sh @@ -69,6 +69,17 @@ HAVE_CURVE448 HAVE_ED448 WOLFSSL_ED448_STREAMING_VERIFY WOLFSSL_HAVE_MLDSA +WOLFSSL_HAVE_MLKEM +WOLFSSL_HAVE_LMS +WOLFSSL_LMS_VERIFY_ONLY +WOLFSSL_HAVE_XMSS +WOLFSSL_XMSS_VERIFY_ONLY +WOLFSSL_EXPERIMENTAL_SETTINGS +HAVE_ASCON +WOLFSSL_AES_DIRECT +HAVE_AES_KEYWRAP +HAVE_XCHACHA +HAVE_CMAC_KDF " flags="${BASELINE}" diff --git a/src/psa_kem.c b/src/psa_kem.c index 2777adf..737f9c7 100644 --- a/src/psa_kem.c +++ b/src/psa_kem.c @@ -277,6 +277,7 @@ psa_status_t psa_decapsulate(psa_key_id_t key, ciphertext, ciphertext_length, ss); #else + (void)key_bits; status = PSA_ERROR_NOT_SUPPORTED; #endif diff --git a/src/psa_key_wrap.c b/src/psa_key_wrap.c index 47bee6c..1ef2ec2 100644 --- a/src/psa_key_wrap.c +++ b/src/psa_key_wrap.c @@ -48,6 +48,7 @@ psa_status_t psa_wrap_key(psa_key_id_t wrapping_key, size_t data_size, size_t *data_length) { +#ifdef HAVE_AES_KEYWRAP psa_status_t status; psa_key_attributes_t wrap_attr; psa_key_attributes_t target_attr; @@ -60,6 +61,7 @@ psa_status_t psa_wrap_key(psa_key_id_t wrapping_key, size_t plain_max; size_t exported_len = 0; int wret; +#endif wolfpsa_trace("psa_wrap_key(wrapping_key=%u alg=0x%08x key=%u)", (unsigned)wrapping_key, (unsigned)alg, (unsigned)key); @@ -199,6 +201,7 @@ psa_status_t psa_unwrap_key(const psa_key_attributes_t *attributes, size_t data_length, psa_key_id_t *key) { +#ifdef HAVE_AES_KEYWRAP psa_status_t status; psa_key_attributes_t wrap_attr; psa_key_type_t wrap_type; @@ -209,6 +212,7 @@ psa_status_t psa_unwrap_key(const psa_key_attributes_t *attributes, uint8_t *plaintext = NULL; size_t plain_len; int wret; +#endif wolfpsa_trace("psa_unwrap_key(wrapping_key=%u alg=0x%08x data_len=%zu)", (unsigned)wrapping_key, (unsigned)alg, data_length); diff --git a/src/psa_pq.c b/src/psa_pq.c index 82b44d1..9faef6b 100644 --- a/src/psa_pq.c +++ b/src/psa_pq.c @@ -77,6 +77,9 @@ psa_status_t psa_pq_check_key_type_supported(psa_key_type_t type) /* Check if a key size is valid for the given post-quantum key type */ psa_status_t psa_pq_check_key_size_valid(psa_key_type_t type, size_t bits) { + /* Unused when every PQC backend is compiled out. */ + (void)bits; + /* SLH-DSA is unsupported; reject before the switch (family of values). */ if (PSA_KEY_TYPE_IS_SLH_DSA(type)) { return PSA_ERROR_NOT_SUPPORTED; diff --git a/user_settings.h b/user_settings.h index e7d8159..3090fb7 100644 --- a/user_settings.h +++ b/user_settings.h @@ -87,6 +87,8 @@ * without this opt-in. */ #define WOLFSSL_EXPERIMENTAL_SETTINGS #define HAVE_ASCON +/* AES key wrap requires the direct (single-block) AES API. */ +#define WOLFSSL_AES_DIRECT #define HAVE_AES_KEYWRAP #define HAVE_XCHACHA #define HAVE_CMAC_KDF From aab839e2672d705d7820f635cad230ec4c802b46 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 19:17:30 +0200 Subject: [PATCH 22/24] chacha: remove dormant XChaCha20-Poly1305 helpers psa_xchacha20_poly1305_encrypt/decrypt sat behind HAVE_XCHACHA with no callers since their introduction and only started compiling when the PSA 1.4 work enabled XChaCha. The live XChaCha20-Poly1305 path is the one-shot implementation in psa_aead.c; drop the dead pair and their declarations. --- src/psa_chacha20_poly1305.c | 124 -------------------------------- wolfpsa/psa_chacha20_poly1305.h | 20 ------ 2 files changed, 144 deletions(-) diff --git a/src/psa_chacha20_poly1305.c b/src/psa_chacha20_poly1305.c index 857f5e3..ed5ae7f 100644 --- a/src/psa_chacha20_poly1305.c +++ b/src/psa_chacha20_poly1305.c @@ -172,128 +172,4 @@ psa_status_t psa_chacha20_poly1305_decrypt( return PSA_SUCCESS; } -#ifdef HAVE_XCHACHA -/* Encrypt using XChaCha20-Poly1305 */ -psa_status_t psa_xchacha20_poly1305_encrypt( - const uint8_t *key, size_t key_length, - psa_algorithm_t alg, - const uint8_t *nonce, size_t nonce_length, - const uint8_t *additional_data, size_t additional_data_length, - const uint8_t *plaintext, size_t plaintext_length, - uint8_t *ciphertext, size_t ciphertext_size, size_t *ciphertext_length) -{ - int ret; - size_t required_size; - - /* Check parameters */ - if (key == NULL || key_length != CHACHA20_POLY1305_AEAD_KEYSIZE) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - if (alg == PSA_ALG_CHACHA20_POLY1305) { - return PSA_ERROR_NOT_SUPPORTED; - } - - if (nonce == NULL || nonce_length != XCHACHA20_POLY1305_AEAD_NONCE_SIZE) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - /* Additional data can be NULL if length is 0 */ - if (additional_data == NULL && additional_data_length > 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - /* Plaintext can be NULL if length is 0 */ - if (plaintext == NULL && plaintext_length > 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - if (plaintext_length > SIZE_MAX - CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE) { - return PSA_ERROR_BUFFER_TOO_SMALL; - } - required_size = plaintext_length + CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE; - - /* Check output buffer size */ - if (ciphertext_size < required_size) { - return PSA_ERROR_BUFFER_TOO_SMALL; - } - - /* Encrypt using XChaCha20-Poly1305 */ - ret = wc_XChaCha20Poly1305_Encrypt( - ciphertext, ciphertext_size, - plaintext, plaintext_length, - additional_data, additional_data_length, - nonce, nonce_length, - key, key_length - ); - - if (ret != 0) { - return wc_error_to_psa_status(ret); - } - - /* Set output length */ - *ciphertext_length = required_size; - - return PSA_SUCCESS; -} - -/* Decrypt using XChaCha20-Poly1305 */ -psa_status_t psa_xchacha20_poly1305_decrypt( - const uint8_t *key, size_t key_length, - psa_algorithm_t alg, - const uint8_t *nonce, size_t nonce_length, - const uint8_t *additional_data, size_t additional_data_length, - const uint8_t *ciphertext, size_t ciphertext_length, - uint8_t *plaintext, size_t plaintext_size, size_t *plaintext_length) -{ - int ret; - - /* Check parameters */ - if (key == NULL || key_length != CHACHA20_POLY1305_AEAD_KEYSIZE) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - if (alg == PSA_ALG_CHACHA20_POLY1305) { - return PSA_ERROR_NOT_SUPPORTED; - } - - if (nonce == NULL || nonce_length != XCHACHA20_POLY1305_AEAD_NONCE_SIZE) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - /* Additional data can be NULL if length is 0 */ - if (additional_data == NULL && additional_data_length > 0) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - /* Ciphertext must include authentication tag */ - if (ciphertext_length < CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - /* Check output buffer size */ - if (plaintext_size < ciphertext_length - CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE) { - return PSA_ERROR_BUFFER_TOO_SMALL; - } - - /* Decrypt using XChaCha20-Poly1305 */ - ret = wc_XChaCha20Poly1305_Decrypt( - plaintext, plaintext_size, - ciphertext, ciphertext_length, - additional_data, additional_data_length, - nonce, nonce_length, - key, key_length - ); - - if (ret != 0) { - return wc_error_to_psa_status(ret); - } - - /* Set output length */ - *plaintext_length = ciphertext_length - CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE; - - return PSA_SUCCESS; -} -#endif /* HAVE_XCHACHA */ - #endif /* WOLFSSL_PSA_ENGINE && HAVE_CHACHA && HAVE_POLY1305 */ diff --git a/wolfpsa/psa_chacha20_poly1305.h b/wolfpsa/psa_chacha20_poly1305.h index c84bb14..0136c04 100644 --- a/wolfpsa/psa_chacha20_poly1305.h +++ b/wolfpsa/psa_chacha20_poly1305.h @@ -61,25 +61,5 @@ WOLFSSL_LOCAL psa_status_t psa_chacha20_poly1305_decrypt( const uint8_t *ciphertext, size_t ciphertext_length, uint8_t *plaintext, size_t plaintext_size, size_t *plaintext_length); -#ifdef HAVE_XCHACHA -/* Encrypt using XChaCha20-Poly1305 */ -WOLFSSL_LOCAL psa_status_t psa_xchacha20_poly1305_encrypt( - const uint8_t *key, size_t key_length, - psa_algorithm_t alg, - const uint8_t *nonce, size_t nonce_length, - const uint8_t *additional_data, size_t additional_data_length, - const uint8_t *plaintext, size_t plaintext_length, - uint8_t *ciphertext, size_t ciphertext_size, size_t *ciphertext_length); - -/* Decrypt using XChaCha20-Poly1305 */ -WOLFSSL_LOCAL psa_status_t psa_xchacha20_poly1305_decrypt( - const uint8_t *key, size_t key_length, - psa_algorithm_t alg, - const uint8_t *nonce, size_t nonce_length, - const uint8_t *additional_data, size_t additional_data_length, - const uint8_t *ciphertext, size_t ciphertext_length, - uint8_t *plaintext, size_t plaintext_size, size_t *plaintext_length); -#endif /* HAVE_XCHACHA */ - #endif /* WOLFSSL_PSA_ENGINE && HAVE_CHACHA && HAVE_POLY1305 */ #endif /* WOLFSSL_PSA_CHACHA20_POLY1305_H */ From 6e6fb80c4158510bb03ec8c1fa01e174f7c6447c Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 22:24:22 +0200 Subject: [PATCH 23/24] Fix Skoll follow-ups and PSA test regression --- src/psa_kem.c | 8 +--- src/psa_key_derivation.c | 3 ++ src/psa_key_storage.c | 9 ++-- src/psa_lms_xmss.c | 4 +- src/psa_xof.c | 3 +- test/psa_server/psa_rsa_pss_interop_test.c | 2 +- test/psa_server/psa_sp800_108_test.c | 56 ++++++++++++++++++++++ test/psa_server/psa_xof_test.c | 3 +- 8 files changed, 73 insertions(+), 15 deletions(-) diff --git a/src/psa_kem.c b/src/psa_kem.c index 737f9c7..1e90bf8 100644 --- a/src/psa_kem.c +++ b/src/psa_kem.c @@ -36,8 +36,6 @@ #include #include -#include - /* Validate that an output-key attributes type is acceptable for a KEM shared * secret (32 bytes of unstructured key material) and that the requested bit * size is compatible (0 = let the import infer; 256 = explicit 32-byte key). */ @@ -120,10 +118,6 @@ psa_status_t psa_encapsulate(psa_key_id_t key, return status; } - if (ciphertext_size > PSA_ENCAPSULATE_CIPHERTEXT_MAX_SIZE) { - /* Oversized buffer is fine; we'll copy the actual length later. */ - } - /* --- Load the KEM key and check policy --- */ status = wolfpsa_get_key_data(key, &key_attr, &key_data, &key_data_length); if (status != PSA_SUCCESS) { @@ -192,7 +186,7 @@ psa_status_t psa_encapsulate(psa_key_id_t key, } /* Only expose the ciphertext to the caller after the key import succeeded. */ - memcpy(ciphertext, ct_buf, ct_len); + XMEMCPY(ciphertext, ct_buf, ct_len); *ciphertext_length = ct_len; return PSA_SUCCESS; diff --git a/src/psa_key_derivation.c b/src/psa_key_derivation.c index e9d8bee..d08ffd4 100644 --- a/src/psa_key_derivation.c +++ b/src/psa_key_derivation.c @@ -388,6 +388,9 @@ static psa_status_t wolfpsa_kdf_validate_step(wolfpsa_kdf_ctx_t *ctx, step != PSA_KEY_DERIVATION_INPUT_CONTEXT) { return PSA_ERROR_INVALID_ARGUMENT; } + if ((ctx->steps_set & wolfpsa_kdf_step_mask(step)) != 0) { + return PSA_ERROR_BAD_STATE; + } return PSA_SUCCESS; } diff --git a/src/psa_key_storage.c b/src/psa_key_storage.c index 58edfa5..8555269 100644 --- a/src/psa_key_storage.c +++ b/src/psa_key_storage.c @@ -1826,7 +1826,7 @@ psa_status_t psa_export_public_key( status = PSA_ERROR_NOT_SUPPORTED; #endif } - else { + else if (PSA_KEY_TYPE_IS_ECC(attributes.type)) { #if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_EXPORT) && defined(HAVE_ECC_KEY_IMPORT) if (PSA_KEY_TYPE_IS_ECC_PUBLIC_KEY(attributes.type)) { if (data_size < key_data_length) { @@ -1895,6 +1895,8 @@ psa_status_t psa_export_public_key( #else status = PSA_ERROR_NOT_SUPPORTED; #endif + } + else { /* PQC key types — reached when neither RSA nor ECC matched the type * gate above. */ #if defined(WOLFSSL_HAVE_MLDSA) @@ -2189,13 +2191,14 @@ psa_status_t psa_check_key_usage(psa_key_id_t key, psa_algorithm_t alg, psa_key_usage_t usage) { - wolfpsa_trace("psa_check_key_usage(key=%u alg=0x%08x usage=0x%08x)", - (unsigned)key, (unsigned)alg, (unsigned)usage); psa_status_t status; psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_key_usage_t key_usage; psa_algorithm_t key_alg; + wolfpsa_trace("psa_check_key_usage(key=%u alg=0x%08x usage=0x%08x)", + (unsigned)key, (unsigned)alg, (unsigned)usage); + status = psa_get_key_attributes(key, &attributes); if (status != PSA_SUCCESS) { return status; diff --git a/src/psa_lms_xmss.c b/src/psa_lms_xmss.c index 24d29a8..5330d45 100644 --- a/src/psa_lms_xmss.c +++ b/src/psa_lms_xmss.c @@ -90,7 +90,7 @@ psa_status_t wolfpsa_lms_verify(const uint8_t *pub, size_t pub_len, return PSA_ERROR_INVALID_SIGNATURE; } if (ret != 0) { - return PSA_ERROR_INVALID_SIGNATURE; + return wc_error_to_psa_status(ret); } return PSA_SUCCESS; @@ -176,7 +176,7 @@ psa_status_t wolfpsa_xmss_verify(const uint8_t *pub, size_t pub_len, return PSA_ERROR_INVALID_SIGNATURE; } if (ret != 0) { - return PSA_ERROR_INVALID_SIGNATURE; + return wc_error_to_psa_status(ret); } return PSA_SUCCESS; diff --git a/src/psa_xof.c b/src/psa_xof.c index d9aa200..b6ce5e9 100644 --- a/src/psa_xof.c +++ b/src/psa_xof.c @@ -396,6 +396,7 @@ psa_status_t psa_xof_output(psa_xof_operation_t *operation, /* 2. If caller wants >= one full block, squeeze directly. */ if (output_length >= (size_t)ctx->block_size) { word32 n_blocks = (word32)(output_length / ctx->block_size); + word32 produced; switch (ctx->alg) { #ifdef WOLFSSL_SHAKE128 @@ -418,7 +419,7 @@ psa_status_t psa_xof_output(psa_xof_operation_t *operation, return wolfpsa_xof_fail(operation, wc_error_to_psa_status(ret)); - word32 produced = n_blocks * ctx->block_size; + produced = n_blocks * ctx->block_size; output += produced; output_length -= produced; continue; diff --git a/test/psa_server/psa_rsa_pss_interop_test.c b/test/psa_server/psa_rsa_pss_interop_test.c index c3e717b..b1aafbc 100644 --- a/test/psa_server/psa_rsa_pss_interop_test.c +++ b/test/psa_server/psa_rsa_pss_interop_test.c @@ -34,7 +34,7 @@ #define INVALID_DEVID -2 #endif -#define WOLFSSL_CERTS_DIR "../../wolfssl/certs" +#define WOLFSSL_CERTS_DIR "../wolfssl/certs" #define RSA_PRIVATE_KEY_PATH WOLFSSL_CERTS_DIR "/server-key.der" static int load_file(const char* path, uint8_t** data, size_t* len) diff --git a/test/psa_server/psa_sp800_108_test.c b/test/psa_server/psa_sp800_108_test.c index 6d98ff2..3d219e9 100644 --- a/test/psa_server/psa_sp800_108_test.c +++ b/test/psa_server/psa_sp800_108_test.c @@ -875,6 +875,61 @@ static int test_hmac_salt_rejected(void) return 0; } +/* ------------------------------------------------------------------------- + * Test case 9: SP800-108 steps must not be provided twice. + * -------------------------------------------------------------------------*/ +static int test_hmac_duplicate_step_rejected(void) +{ + psa_algorithm_t alg = PSA_ALG_SP800_108_COUNTER_HMAC(PSA_ALG_SHA_256); + psa_key_derivation_operation_t op = psa_key_derivation_operation_init(); + static const uint8_t secret[] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b + }; + static const uint8_t label[] = { 'l', 'a', 'b', 'e', 'l' }; + psa_status_t st; + + st = psa_key_derivation_setup(&op, alg); + if (expect_status("tc9 setup", st, PSA_SUCCESS) != 0) return 1; + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_SECRET, + secret, sizeof(secret)); + if (expect_status("tc9 input SECRET", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_SECRET, + secret, sizeof(secret)); + if (expect_status("tc9 duplicate SECRET", st, + PSA_ERROR_BAD_STATE) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_LABEL, + label, sizeof(label)); + if (expect_status("tc9 input LABEL", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + st = psa_key_derivation_input_bytes(&op, PSA_KEY_DERIVATION_INPUT_LABEL, + label, sizeof(label)); + if (expect_status("tc9 duplicate LABEL", st, + PSA_ERROR_BAD_STATE) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + + psa_key_derivation_abort(&op); + printf("PASS tc9: duplicate SP800-108 steps rejected with " + "PSA_ERROR_BAD_STATE\n"); + return 0; +} + /* ------------------------------------------------------------------------- * main * -------------------------------------------------------------------------*/ @@ -896,6 +951,7 @@ int main(void) if (test_hmac_output_key() != 0) return 1; if (test_cmac_invalid_key_len() != 0) return 1; if (test_hmac_salt_rejected() != 0) return 1; + if (test_hmac_duplicate_step_rejected() != 0) return 1; printf("SP800-108 counter-mode KDF test: OK\n"); return 0; diff --git a/test/psa_server/psa_xof_test.c b/test/psa_server/psa_xof_test.c index ef30f70..e2130eb 100644 --- a/test/psa_server/psa_xof_test.c +++ b/test/psa_server/psa_xof_test.c @@ -583,7 +583,7 @@ static int test_set_context_shake(void) st = psa_xof_set_context(&op, ctx_data, sizeof(ctx_data)); if (expect_status("tc7 shake128 set_context", st, PSA_ERROR_INVALID_ARGUMENT) != 0) return 1; - /* operation aborted by the failed call; do not double-abort */ + (void)psa_xof_abort(&op); op = psa_xof_operation_init(); st = psa_xof_setup(&op, PSA_ALG_SHAKE256); @@ -592,6 +592,7 @@ static int test_set_context_shake(void) st = psa_xof_set_context(&op, ctx_data, sizeof(ctx_data)); if (expect_status("tc7 shake256 set_context", st, PSA_ERROR_INVALID_ARGUMENT) != 0) return 1; + (void)psa_xof_abort(&op); return 0; } From ee9966960632670de5dc626513b64ebc7bb178a7 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 10 Jun 2026 23:53:44 +0200 Subject: [PATCH 24/24] Address review follow-ups: dead AEAD check_key branches, SP800-108 L docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - aead: remove unreachable XChaCha20-Poly1305/Ascon-AEAD128 branches from wolfpsa_aead_check_key; both algorithms are one-shot only and rejected by wolfpsa_aead_setup before the key check, and the one-shot paths validate keys inline. Left a note so they are not re-added. - kdf: document that SP800-108 counter-mode binds [L]_4 to the operation capacity per PSA 1.4 §10.8, so callers must set_capacity() to the exact total output before the first read to interoperate with implementations that derive L from the requested length. --- src/psa_aead.c | 23 +++-------------------- src/psa_key_derivation.c | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/psa_aead.c b/src/psa_aead.c index ced4f41..a1f49ee 100644 --- a/src/psa_aead.c +++ b/src/psa_aead.c @@ -146,27 +146,10 @@ static psa_status_t wolfpsa_aead_check_key(psa_key_id_t key, return PSA_ERROR_INVALID_ARGUMENT; } } -#ifdef HAVE_XCHACHA - else if (PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_XCHACHA20_POLY1305)) { - if (attributes->type != PSA_KEY_TYPE_XCHACHA20) { - wolfpsa_forcezero_free_key_data(*key_data, *key_data_length); - *key_data = NULL; - *key_data_length = 0; - return PSA_ERROR_INVALID_ARGUMENT; - } - } -#endif /* HAVE_XCHACHA */ -#ifdef HAVE_ASCON - else if (PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_ASCON_AEAD128)) { - if (attributes->type != PSA_KEY_TYPE_ASCON) { - wolfpsa_forcezero_free_key_data(*key_data, *key_data_length); - *key_data = NULL; - *key_data_length = 0; - return PSA_ERROR_INVALID_ARGUMENT; - } - } -#endif /* HAVE_ASCON */ else { + /* XChaCha20-Poly1305 and Ascon-AEAD128 never reach this function: + * they are one-shot only and rejected by wolfpsa_aead_setup before + * the key is checked; the one-shot paths validate the key inline. */ wolfpsa_forcezero_free_key_data(*key_data, *key_data_length); *key_data = NULL; *key_data_length = 0; diff --git a/src/psa_key_derivation.c b/src/psa_key_derivation.c index d08ffd4..568e180 100644 --- a/src/psa_key_derivation.c +++ b/src/psa_key_derivation.c @@ -1239,6 +1239,13 @@ static psa_status_t wolfpsa_kdf_pbkdf2(wolfpsa_kdf_ctx_t *ctx, * snapshotted from capacity at first output_bytes() call * * Output stream is K(1) || K(2) || ... truncated to output_length bytes. + * + * Interoperability note: L is bound to the operation's capacity, not to the + * amount of output actually read (PSA 1.4 §10.8). Callers MUST set the + * capacity to the exact total derivation length before the first output + * call to match other SP 800-108 implementations that encode L from the + * requested output length; with the default capacity (2^29 - 1 bytes) the + * output is self-consistent but not interoperable. */ static psa_status_t wolfpsa_kdf_sp800_108_hmac(wolfpsa_kdf_ctx_t *ctx, uint8_t *output, @@ -1379,6 +1386,8 @@ static psa_status_t wolfpsa_kdf_sp800_108_hmac(wolfpsa_kdf_ctx_t *ctx, * The secret (K_IN) must be a valid AES key: 16, 24, or 32 bytes. * Output block size is WC_AES_BLOCK_SIZE (16 bytes). * [L]_4 encodes total output length in bits as 4-byte big-endian. + * The interoperability note on wolfpsa_kdf_sp800_108_hmac() about binding + * L via psa_key_derivation_set_capacity() applies here as well. */ static psa_status_t wolfpsa_kdf_sp800_108_cmac(wolfpsa_kdf_ctx_t *ctx, uint8_t *output, @@ -1612,7 +1621,14 @@ psa_status_t psa_key_derivation_output_bytes(psa_key_derivation_operation_t *ope * any output has been consumed (spec: PSA 1.4 §10.8, SP800-108 * counter mode). At this point capacity has not yet been decremented * by wolfpsa_kdf_require_output, so capacity + output_length equals - * the original capacity set at setup / set_capacity time. */ + * the original capacity set at setup / set_capacity time. + * + * Callers that did not call psa_key_derivation_set_capacity() get + * L derived from the default capacity (2^29 - 1 bytes). That is + * the behavior the PSA spec mandates, but the resulting stream will + * not match SP 800-108 implementations that bind L to the requested + * output length — see the interoperability note on + * wolfpsa_kdf_sp800_108_hmac(). */ if (!ctx->output_started) { ctx->sp800_108_L_bytes = ctx->capacity; }