From 73c888111d08b1f0a2181d4c7ceab56aa08c6fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Mon, 20 Jul 2026 19:38:48 +0200 Subject: [PATCH 01/10] Fix psa_export_public_key fall-through for disabled PQC backends When a build does not compile the ML-DSA or ML-KEM backend, an ML-DSA or ML-KEM key could still be imported (psa_import_key validates only the key length, and the export type gate admits these types unconditionally). In that case psa_export_public_key matched none of the compiled dispatch branches and returned with status still holding its earlier PSA_SUCCESS value, so the caller saw success with an unwritten output length and an untouched buffer. Add a final else that sets PSA_ERROR_NOT_SUPPORTED, matching the RSA and ECC arms, so an admitted key type with no compiled backend returns an error instead of a spurious success. Fixes F-6070. --- src/psa_key_storage.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/psa_key_storage.c b/src/psa_key_storage.c index 8555269..6f2661d 100644 --- a/src/psa_key_storage.c +++ b/src/psa_key_storage.c @@ -1957,6 +1957,12 @@ psa_status_t psa_export_public_key( status = PSA_SUCCESS; } } + else { + /* Key type admitted by the gate above but its backend is not + * compiled in (for example ML-DSA/ML-KEM when the corresponding + * WOLFSSL_HAVE_* macro is undefined). */ + status = PSA_ERROR_NOT_SUPPORTED; + } } wolfpsa_forcezero_free_key_data(key_data, key_data_length); From c9237b51fbd1618e2975f3230f77aced1cf36721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Mon, 20 Jul 2026 19:56:47 +0200 Subject: [PATCH 02/10] Bound KDF output-cache size to avoid SP800-108 blowup The first output_bytes() call on a bounded-capacity derivation pre-computed and cached the whole keystream, sizing the cache to the operation capacity. For SP800-108 counter mode the default capacity is 2^29 - 1 bytes, so a derivation that omitted psa_key_derivation_set_capacity allocated about 512 MB and computed millions of MAC blocks just to return a few bytes. On memory-constrained targets that turned a valid request into PSA_ERROR_INSUFFICIENT_MEMORY. Only take the caching path when the total cached length stays within a fixed bound that comfortably covers HKDF's default capacity. Larger capacities fall through to the existing lazy per-call computation, which produces the same bytes because the SP800-108 L field is bound to the snapshotted capacity independently of how many bytes are computed at once. Fixes F-6250. --- src/psa_key_derivation.c | 20 ++++- test/psa_server/psa_sp800_108_test.c | 111 +++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 4 deletions(-) diff --git a/src/psa_key_derivation.c b/src/psa_key_derivation.c index 568e180..d119b1c 100644 --- a/src/psa_key_derivation.c +++ b/src/psa_key_derivation.c @@ -104,6 +104,15 @@ typedef struct wolfpsa_kdf_ctx { #define WOLFPSA_KDF_STEP_COST (1u << 7) #define WOLFPSA_KDF_STEP_CONTEXT (1u << 8) +/* Upper bound on how much of a bounded-capacity derivation is pre-computed + * and cached on the first output_bytes() call. HKDF's default capacity is at + * most 255 * hash_len (roughly 16 KB), which fits comfortably. SP800-108 + * counter mode defaults its capacity to 2^29 - 1 bytes; caching that whole + * keystream up front would allocate about 512 MB and compute millions of MAC + * blocks just to return a few bytes, so above this bound the derivation is + * computed lazily per call instead. */ +#define WOLFPSA_KDF_MAX_CACHE_BYTES (64u * 1024u) + static wolfpsa_kdf_ctx_t* wolfpsa_kdf_get_ctx(psa_key_derivation_operation_t *operation) { if (operation == NULL) { @@ -1648,10 +1657,13 @@ psa_status_t psa_key_derivation_output_bytes(psa_key_derivation_operation_t *ope if (ctx->output_cache == NULL && !ctx->is_raw_kdf && ctx->capacity != PSA_KEY_DERIVATION_UNLIMITED_CAPACITY - && ctx->output_offset == 0) { - /* First output_bytes() call with a bounded capacity: compute the - * full derivation once and cache it. Subsequent calls serve slices - * from the cache, avoiding O(n^2) recomputation. */ + && ctx->output_offset == 0 + && (output_length + ctx->capacity) <= WOLFPSA_KDF_MAX_CACHE_BYTES) { + /* First output_bytes() call with a small bounded capacity: compute + * the full derivation once and cache it. Subsequent calls serve + * slices from the cache, avoiding O(n^2) recomputation. Capacities + * above the cache bound (for example the SP800-108 counter-mode + * default) skip this path and compute lazily per call. */ size_t cache_length = output_length + ctx->capacity; ctx->output_cache = (uint8_t *)XMALLOC(cache_length, NULL, diff --git a/test/psa_server/psa_sp800_108_test.c b/test/psa_server/psa_sp800_108_test.c index 3d219e9..c9fc0d4 100644 --- a/test/psa_server/psa_sp800_108_test.c +++ b/test/psa_server/psa_sp800_108_test.c @@ -933,6 +933,116 @@ static int test_hmac_duplicate_step_rejected(void) /* ------------------------------------------------------------------------- * main * -------------------------------------------------------------------------*/ +/* Setup an SP800-108 HMAC-SHA256 operation with the given capacity and the + * standard secret/label/context inputs. Returns 0 on success, 1 on failure + * (op aborted). */ +static int sp800_hmac_setup(psa_key_derivation_operation_t *op, + psa_algorithm_t alg, size_t capacity) +{ + psa_status_t st; + + st = psa_key_derivation_setup(op, alg); + if (expect_status("large-cap setup", st, PSA_SUCCESS) != 0) + return 1; + st = psa_key_derivation_set_capacity(op, capacity); + if (expect_status("large-cap 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("large-cap 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("large-cap 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("large-cap context", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(op); + return 1; + } + return 0; +} + +/* Test case: large-capacity lazy path. + * + * A capacity above the output-cache bound forces the derivation onto the + * lazy, non-cached path. Verify prefix-consistency across the offset==0 + * direct-compute branch and the offset>0 recompute-and-slice branch: two + * consecutive output_bytes() calls, a single output_bytes(64) call on a fresh + * identical derivation, and the independent K(1)||K(2) reconstruction must all + * agree. Existing SP800-108 tests only use small capacities that stay on the + * cached path, so this exercises the otherwise-untested lazy branches. */ +static int test_hmac_large_capacity_lazy(void) +{ + psa_algorithm_t alg = PSA_ALG_SP800_108_COUNTER_HMAC(PSA_ALG_SHA_256); + size_t cap = 100000u; /* above the 64 KB output-cache bound */ + psa_key_derivation_operation_t op = psa_key_derivation_operation_init(); + uint8_t out_a[32]; + uint8_t out_b[32]; + uint8_t out_single[64]; + uint8_t ref[64]; + psa_status_t st; + + /* Two consecutive 32-byte calls: offset 0 (direct compute) then offset 32 + * (recompute-and-slice). */ + if (sp800_hmac_setup(&op, alg, cap) != 0) + return 1; + st = psa_key_derivation_output_bytes(&op, out_a, sizeof(out_a)); + if (expect_status("large-cap out_a", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + st = psa_key_derivation_output_bytes(&op, out_b, sizeof(out_b)); + if (expect_status("large-cap out_b", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + psa_key_derivation_abort(&op); + + /* Single 64-byte call on a fresh identical derivation. */ + op = psa_key_derivation_operation_init(); + if (sp800_hmac_setup(&op, alg, cap) != 0) + return 1; + st = psa_key_derivation_output_bytes(&op, out_single, sizeof(out_single)); + if (expect_status("large-cap single", st, PSA_SUCCESS) != 0) { + psa_key_derivation_abort(&op); + return 1; + } + psa_key_derivation_abort(&op); + + /* Independent reconstruction (L = capacity in bits). */ + if (reconstruct_hmac(g_hmac_secret, sizeof(g_hmac_secret), + g_label, sizeof(g_label), + g_context, sizeof(g_context), + cap, ref, sizeof(ref)) != 0) { + printf("FAIL large-cap: reconstruction failed\n"); + return 1; + } + + if (memcmp(out_a, ref, 32u) != 0) { + printf("FAIL large-cap: K(1) mismatch (lazy direct-compute)\n"); + return 1; + } + if (memcmp(out_b, ref + 32, 32u) != 0) { + printf("FAIL large-cap: K(2) mismatch (lazy recompute-and-slice)\n"); + return 1; + } + if (memcmp(out_single, ref, 64u) != 0) { + printf("FAIL large-cap: single-call mismatch\n"); + return 1; + } + + printf("PASS large-cap: SP800-108 lazy path prefix-consistent\n"); + return 0; +} + int main(void) { psa_status_t st; @@ -952,6 +1062,7 @@ int main(void) 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; + if (test_hmac_large_capacity_lazy() != 0) return 1; printf("SP800-108 counter-mode KDF test: OK\n"); return 0; From 32e8ae25b6cf6fbd77178b5ffd74f6658d37d040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 09:05:35 +0200 Subject: [PATCH 03/10] Reject key lifetimes that name an unsupported storage location A psa_key_lifetime_t encodes both a persistence level and a location. This build only implements local storage, but psa_import_key inspected just the persistence byte and never checked the location. A caller who requested a secure element or other vendor location, expecting the private key to be provisioned where it can never be exported, instead had the key written in plaintext to local storage with no error, and psa_get_key_attributes then reported the key as living in the secure element. Reject any lifetime whose location is not local storage with PSA_ERROR_NOT_SUPPORTED. The check sits in psa_import_key, which is the single point that psa_generate_key, psa_copy_key and psa_key_derivation_output_key all store through, so every key-creation entry point is covered. Fixes F-6251. --- src/psa_key_storage.c | 13 +++++++ test/psa_server/psa_14_misc_test.c | 55 ++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/src/psa_key_storage.c b/src/psa_key_storage.c index 6f2661d..16cb7df 100644 --- a/src/psa_key_storage.c +++ b/src/psa_key_storage.c @@ -950,6 +950,19 @@ psa_status_t psa_import_key( attr = *attributes; + /* Local storage is the only location this build implements. A key whose + * lifetime names any other location (for example a secure element or + * vendor location) must be rejected rather than silently written to + * plaintext local storage. This choke point also covers psa_generate_key, + * psa_copy_key and psa_key_derivation_output_key, which all store through + * psa_import_key. */ + if (PSA_KEY_LIFETIME_GET_LOCATION(attr.lifetime) != + PSA_KEY_LOCATION_LOCAL_STORAGE) { + wolfpsa_debug_import_reason("unsupported key lifetime location", &attr, + data_length); + return PSA_ERROR_NOT_SUPPORTED; + } + if (attr.policy.alg2 != PSA_ALG_NONE) { wolfpsa_debug_import_reason("unsupported secondary algorithm", &attr, data_length); diff --git a/test/psa_server/psa_14_misc_test.c b/test/psa_server/psa_14_misc_test.c index c3c9ec0..b4d121e 100644 --- a/test/psa_server/psa_14_misc_test.c +++ b/test/psa_server/psa_14_misc_test.c @@ -484,6 +484,57 @@ static int test_size_macros(void) return fail; } +/* Case 9: a key whose lifetime names a storage location this build does not + * implement (for example a secure element or vendor location) must be rejected + * rather than silently written to plaintext local storage. Covers both the + * import and generate entry points. */ +static int test_unsupported_lifetime_location(void) +{ + /* A non-local vendor / secure-element storage location. */ + psa_key_location_t se_location = + (psa_key_location_t)(PSA_KEY_LOCATION_VENDOR_FLAG | 0x01u); + psa_key_lifetime_t se_lifetime = + PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION( + PSA_KEY_PERSISTENCE_DEFAULT, se_location); + static const uint8_t aes_key[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + }; + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t 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); + psa_set_key_lifetime(&attrs, se_lifetime); + + /* Import path */ + st = psa_import_key(&attrs, aes_key, sizeof(aes_key), &key); + if (st == PSA_SUCCESS) { + (void)psa_destroy_key(key); + } + if (expect_status("import_key unsupported location", st, + PSA_ERROR_NOT_SUPPORTED) != 0) { + return 1; + } + + /* Generate path (funnels through psa_import_key) */ + key = 0; + st = psa_generate_key(&attrs, &key); + if (st == PSA_SUCCESS) { + (void)psa_destroy_key(key); + } + if (expect_status("generate_key unsupported location", st, + PSA_ERROR_NOT_SUPPORTED) != 0) { + return 1; + } + + return 0; +} + int main(void) { psa_status_t st; @@ -526,6 +577,10 @@ int main(void) if (test_size_macros() != 0) return 1; + /* Case 9: unsupported key lifetime location rejected */ + if (test_unsupported_lifetime_location() != 0) + return 1; + printf("PSA 1.4 misc test: OK\n"); return 0; } From ce59b9dcce17b17d4e47db0bc84bdac669418504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 10:55:16 +0200 Subject: [PATCH 04/10] Add deterministic ECDSA identical-signature test The deterministic ECDSA sign path enables RFC 6979 nonce generation, but no test signed with a deterministic-ECDSA policy key, so disabling that setup left randomized signatures that still verify and every roundtrip test still passed. Add a positive case that signs the same hash twice with a deterministic-ECDSA P-256 key and asserts the two signatures are identical and verify. A randomized nonce produces differing signatures and fails the check, so a regression that drops the deterministic setup is caught. Fixes F-6969. --- test/psa_server/psa_14_misc_test.c | 75 ++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/test/psa_server/psa_14_misc_test.c b/test/psa_server/psa_14_misc_test.c index b4d121e..0386483 100644 --- a/test/psa_server/psa_14_misc_test.c +++ b/test/psa_server/psa_14_misc_test.c @@ -535,6 +535,77 @@ static int test_unsupported_lifetime_location(void) return 0; } +/* Case 10: deterministic ECDSA (RFC 6979) must produce identical signatures + * for the same key and hash, and those signatures must verify. This exercises + * the deterministic nonce setup on the sign path; a randomized ECDSA nonce + * (deterministic setup deleted or disabled) yields two different signatures + * and fails the identical-signature assertion. */ +static int test_deterministic_ecdsa(void) +{ + psa_algorithm_t alg = PSA_ALG_DETERMINISTIC_ECDSA(PSA_ALG_SHA_256); + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key = 0; + static const uint8_t hash[32] = { + 0x00, 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 + }; + uint8_t sig1[PSA_SIGNATURE_MAX_SIZE]; + uint8_t sig2[PSA_SIGNATURE_MAX_SIZE]; + size_t sig1_len = 0; + size_t sig2_len = 0; + 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_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH); + psa_set_key_algorithm(&attrs, alg); + + st = psa_generate_key(&attrs, &key); + if (expect_status("det_ecdsa: generate", st, PSA_SUCCESS) != 0) + return 1; + + st = psa_sign_hash(key, alg, hash, sizeof(hash), sig1, sizeof(sig1), + &sig1_len); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP test_deterministic_ecdsa (deterministic ECDSA not" + " supported by this build)\n"); + (void)psa_destroy_key(key); + return 0; + } + if (expect_status("det_ecdsa: sign #1", st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + st = psa_sign_hash(key, alg, hash, sizeof(hash), sig2, sizeof(sig2), + &sig2_len); + if (expect_status("det_ecdsa: sign #2", st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + if (sig1_len != sig2_len || memcmp(sig1, sig2, sig1_len) != 0) { + printf("FAIL det_ecdsa: signatures differ, deterministic nonce not" + " applied\n"); + (void)psa_destroy_key(key); + return 1; + } + + st = psa_verify_hash(key, alg, hash, sizeof(hash), sig1, sig1_len); + if (expect_status("det_ecdsa: verify", st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + (void)psa_destroy_key(key); + printf("PASS: deterministic ECDSA identical signatures\n"); + return 0; +} + int main(void) { psa_status_t st; @@ -581,6 +652,10 @@ int main(void) if (test_unsupported_lifetime_location() != 0) return 1; + /* Case 10: deterministic ECDSA identical-signature check */ + if (test_deterministic_ecdsa() != 0) + return 1; + printf("PSA 1.4 misc test: OK\n"); return 0; } From 0c59f49d45ac0db5c2b580b89700e260268e9fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 11:07:20 +0200 Subject: [PATCH 05/10] Reject shortened-tag one-shot XChaCha and Ascon AEAD requests The one-shot XChaCha20-Poly1305 and Ascon-AEAD128 paths always emit and require a 16-byte tag, but the dispatch uses PSA_ALG_AEAD_EQUAL, which masks out the tag-length bits, so a shortened-tag or at-least-this-length variant reached these helpers and was served with a full 16-byte tag. A caller who requested a 12-byte tag then saw the wrong output length, produced ciphertext a conforming peer could not parse, and could not decrypt a validly shortened tag. These primitives do not implement truncated tags, so reject any operation algorithm other than the native full-length base with PSA_ERROR_NOT_SUPPORTED in all four one-shot helpers, mirroring the multipart ChaCha20-Poly1305 path. Fixes F-6074. --- src/psa_aead.c | 24 +++++ test/psa_server/psa_ascon_xchacha_test.c | 129 +++++++++++++++++++++++ 2 files changed, 153 insertions(+) diff --git a/src/psa_aead.c b/src/psa_aead.c index a1f49ee..004f4f2 100644 --- a/src/psa_aead.c +++ b/src/psa_aead.c @@ -876,6 +876,12 @@ static psa_status_t wolfpsa_xchacha_oneshot_encrypt( /* Encrypt produces plaintext_length bytes of ciphertext plus 16-byte tag. */ size_t out_len; + /* Only the native 16-byte tag is supported. A shortened-tag or + * at-least-this-length variant differs from the base algorithm and must be + * rejected rather than silently emitting a full-length tag. */ + if (alg != PSA_ALG_XCHACHA20_POLY1305) { + return PSA_ERROR_NOT_SUPPORTED; + } if (nonce_length != XCHACHA20_POLY1305_AEAD_NONCE_SIZE) { return PSA_ERROR_INVALID_ARGUMENT; } @@ -955,6 +961,12 @@ static psa_status_t wolfpsa_xchacha_oneshot_decrypt( int ret; size_t pt_len; + /* Only the native 16-byte tag is supported. A shortened-tag or + * at-least-this-length variant differs from the base algorithm and must be + * rejected rather than mis-framing the trailing tag bytes. */ + if (alg != PSA_ALG_XCHACHA20_POLY1305) { + return PSA_ERROR_NOT_SUPPORTED; + } if (nonce_length != XCHACHA20_POLY1305_AEAD_NONCE_SIZE) { return PSA_ERROR_INVALID_ARGUMENT; } @@ -1040,6 +1052,12 @@ static psa_status_t wolfpsa_ascon_oneshot_encrypt( int ret; size_t out_len; + /* Only the native 16-byte tag is supported. A shortened-tag or + * at-least-this-length variant differs from the base algorithm and must be + * rejected rather than silently emitting a full-length tag. */ + if (alg != PSA_ALG_ASCON_AEAD128) { + return PSA_ERROR_NOT_SUPPORTED; + } if (nonce_length != ASCON_AEAD128_NONCE_SZ) { return PSA_ERROR_INVALID_ARGUMENT; } @@ -1140,6 +1158,12 @@ static psa_status_t wolfpsa_ascon_oneshot_decrypt( int ret; size_t ct_len; /* ciphertext body length (without tag) */ + /* Only the native 16-byte tag is supported. A shortened-tag or + * at-least-this-length variant differs from the base algorithm and must be + * rejected rather than mis-framing the trailing tag bytes. */ + if (alg != PSA_ALG_ASCON_AEAD128) { + return PSA_ERROR_NOT_SUPPORTED; + } if (nonce_length != ASCON_AEAD128_NONCE_SZ) { return PSA_ERROR_INVALID_ARGUMENT; } diff --git a/test/psa_server/psa_ascon_xchacha_test.c b/test/psa_server/psa_ascon_xchacha_test.c index 10c065a..877ccde 100644 --- a/test/psa_server/psa_ascon_xchacha_test.c +++ b/test/psa_server/psa_ascon_xchacha_test.c @@ -631,6 +631,132 @@ static int test_xchacha_key_policy(void) return 0; } +/* ========================================================================= + * Test 5 — Shortened / at-least-this-length tag variants must be rejected + * + * The one-shot XChaCha20-Poly1305 and Ascon-AEAD128 paths only implement the + * native 16-byte tag. A shortened-tag (or at-least-this-length) algorithm must + * be rejected with PSA_ERROR_NOT_SUPPORTED rather than silently producing or + * requiring a 16-byte tag while the caller expects a shorter one. + * ====================================================================== */ + +static int test_aead_shortened_tag_rejected(void) +{ + psa_key_attributes_t attrs; + psa_key_id_t key_id = PSA_KEY_ID_NULL; + uint8_t enc_out[130]; + uint8_t dec_out[130]; + size_t out_len = 0; + psa_status_t st; + psa_algorithm_t xch_short = + PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_XCHACHA20_POLY1305, 12); + psa_algorithm_t xch_atleast = + PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG(PSA_ALG_XCHACHA20_POLY1305, + 16); + psa_algorithm_t asc_short = + PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_ASCON_AEAD128, 12); + + /* --- XChaCha20-Poly1305 --------------------------------------------- */ + attrs = psa_key_attributes_init(); + 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 aead_shortened_tag xchacha (not supported by this build)\n"); + } + else { + if (expect_status("shortened xchacha import", st, PSA_SUCCESS) != 0) + return 1; + + out_len = 0; + st = psa_aead_encrypt(key_id, xch_short, + xchacha_nonce, sizeof(xchacha_nonce), + NULL, 0, xchacha_pt, sizeof(xchacha_pt), + enc_out, sizeof(enc_out), &out_len); + if (expect_status("xchacha shortened-tag encrypt rejected", + st, PSA_ERROR_NOT_SUPPORTED) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + out_len = 0; + st = psa_aead_encrypt(key_id, xch_atleast, + xchacha_nonce, sizeof(xchacha_nonce), + NULL, 0, xchacha_pt, sizeof(xchacha_pt), + enc_out, sizeof(enc_out), &out_len); + if (expect_status("xchacha at-least-tag encrypt rejected", + st, PSA_ERROR_NOT_SUPPORTED) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + out_len = 0; + st = psa_aead_decrypt(key_id, xch_short, + xchacha_nonce, sizeof(xchacha_nonce), + NULL, 0, enc_out, sizeof(xchacha_pt) + 12u, + dec_out, sizeof(dec_out), &out_len); + if (expect_status("xchacha shortened-tag decrypt rejected", + st, PSA_ERROR_NOT_SUPPORTED) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + (void)psa_destroy_key(key_id); + } + + /* --- Ascon-AEAD128 -------------------------------------------------- */ + key_id = PSA_KEY_ID_NULL; + attrs = psa_key_attributes_init(); + 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 aead_shortened_tag ascon (not supported by this build)\n"); + } + else { + if (expect_status("shortened ascon import", st, PSA_SUCCESS) != 0) + return 1; + + out_len = 0; + st = psa_aead_encrypt(key_id, asc_short, + ascon_aead128_nonce, sizeof(ascon_aead128_nonce), + NULL, 0, ascon_aead128_pt, + sizeof(ascon_aead128_pt), + enc_out, sizeof(enc_out), &out_len); + if (expect_status("ascon shortened-tag encrypt rejected", + st, PSA_ERROR_NOT_SUPPORTED) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + out_len = 0; + st = psa_aead_decrypt(key_id, asc_short, + ascon_aead128_nonce, sizeof(ascon_aead128_nonce), + NULL, 0, ascon_aead128_ct_tag, + sizeof(ascon_aead128_pt) + 12u, + dec_out, sizeof(dec_out), &out_len); + if (expect_status("ascon shortened-tag decrypt rejected", + st, PSA_ERROR_NOT_SUPPORTED) != 0) { + (void)psa_destroy_key(key_id); + return 1; + } + + (void)psa_destroy_key(key_id); + } + + printf("aead_shortened_tag_rejected: OK\n"); + return 0; +} + /* ========================================================================= * main * ====================================================================== */ @@ -657,6 +783,9 @@ int main(void) if (test_xchacha_key_policy() != 0) return 1; + if (test_aead_shortened_tag_rejected() != 0) + return 1; + printf("psa_ascon_xchacha_test: all tests passed\n"); return 0; } From 137cbc18374e9de90cc217654580bd0bee999699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 11:27:32 +0200 Subject: [PATCH 06/10] Reject shortened-tag ChaCha20-Poly1305 AEAD at setup AEAD setup validated the requested tag length for GCM and CCM but not for ChaCha20-Poly1305, so a shortened-tag algorithm was accepted with a tag length below 16. The operation was then unusable: encrypt sized its internal buffer to the shortened tag while the ChaCha helper requires a full 16-byte tag and returned a buffer-too-small error, and decrypt always treated the last 16 bytes as the tag and failed verification. ChaCha20-Poly1305 has no truncated-tag interface, so reject any tag length other than the native 16 bytes with PSA_ERROR_NOT_SUPPORTED at setup, matching how GCM and CCM validate their tag lengths. Fixes F-6254. --- src/psa_aead.c | 13 +++++- test/psa_server/psa_14_misc_test.c | 72 ++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/psa_aead.c b/src/psa_aead.c index 004f4f2..f4a2f5d 100644 --- a/src/psa_aead.c +++ b/src/psa_aead.c @@ -33,7 +33,7 @@ #include #include #include -#ifdef HAVE_XCHACHA +#if defined(HAVE_XCHACHA) || (defined(HAVE_CHACHA) && defined(HAVE_POLY1305)) #include #endif #ifdef HAVE_ASCON @@ -296,6 +296,17 @@ static psa_status_t wolfpsa_aead_setup(psa_aead_operation_t *operation, return PSA_ERROR_INVALID_ARGUMENT; } #endif +#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) + /* ChaCha20-Poly1305 has no truncated-tag interface; only the native + * 16-byte tag is supported. Reject shortened-tag variants here rather than + * accepting them and failing the encrypt/decrypt roundtrip later. */ + if (PSA_ALG_AEAD_EQUAL(alg, PSA_ALG_CHACHA20_POLY1305) && + ctx->tag_length != CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return PSA_ERROR_NOT_SUPPORTED; + } +#endif ctx->key = (uint8_t *)XMALLOC(key_data_length, NULL, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/test/psa_server/psa_14_misc_test.c b/test/psa_server/psa_14_misc_test.c index 0386483..cd6fa2b 100644 --- a/test/psa_server/psa_14_misc_test.c +++ b/test/psa_server/psa_14_misc_test.c @@ -606,6 +606,74 @@ static int test_deterministic_ecdsa(void) return 0; } +/* Case 11: multipart ChaCha20-Poly1305 with a shortened tag must be rejected + * at setup with PSA_ERROR_NOT_SUPPORTED. Truncated Poly1305 tags are not + * implemented, so a shortened-tag algorithm must not be accepted and then fail + * the encrypt/decrypt roundtrip. */ +static int test_chacha20_poly1305_shortened_tag(void) +{ + psa_algorithm_t alg = + PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CHACHA20_POLY1305, 8); + static const uint8_t key_bytes[32] = { + 0x00, 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 + }; + static const uint8_t nonce[12] = { 0 }; + static const uint8_t pt[16] = { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f + }; + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key = 0; + psa_aead_operation_t op = PSA_AEAD_OPERATION_INIT; + uint8_t ct[sizeof(pt) + 16]; + size_t ct_len = 0; + psa_status_t st; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_CHACHA20); + psa_set_key_bits(&attrs, 256u); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + /* Bind the key policy to the shortened-tag algorithm so the key-policy + * check passes and setup reaches the tag-length validation. */ + psa_set_key_algorithm(&attrs, alg); + + st = psa_import_key(&attrs, key_bytes, sizeof(key_bytes), &key); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP chacha20_poly1305_shortened_tag (not supported by this" + " build)\n"); + return 0; + } + if (expect_status("chacha short-tag import", st, PSA_SUCCESS) != 0) + return 1; + + /* Multipart setup must reject the shortened tag. */ + st = psa_aead_encrypt_setup(&op, key, alg); + if (expect_status("chacha short-tag encrypt_setup", st, + PSA_ERROR_NOT_SUPPORTED) != 0) { + (void)psa_aead_abort(&op); + (void)psa_destroy_key(key); + return 1; + } + (void)psa_aead_abort(&op); + + /* One-shot path (routes through setup) must reject it too. */ + ct_len = 0; + st = psa_aead_encrypt(key, alg, nonce, sizeof(nonce), NULL, 0, + pt, sizeof(pt), ct, sizeof(ct), &ct_len); + if (expect_status("chacha short-tag encrypt", st, + PSA_ERROR_NOT_SUPPORTED) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + (void)psa_destroy_key(key); + printf("PASS: ChaCha20-Poly1305 shortened tag rejected\n"); + return 0; +} + int main(void) { psa_status_t st; @@ -656,6 +724,10 @@ int main(void) if (test_deterministic_ecdsa() != 0) return 1; + /* Case 11: ChaCha20-Poly1305 shortened tag rejected at setup */ + if (test_chacha20_poly1305_shortened_tag() != 0) + return 1; + printf("PSA 1.4 misc test: OK\n"); return 0; } From 00851a089ca34d23aa8d2730374f35edc6370ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 13:03:14 +0200 Subject: [PATCH 07/10] Map RSA_BUFFER_E to PSA_ERROR_BUFFER_TOO_SMALL The shared error translator mapped BUFFER_E to PSA_ERROR_BUFFER_TOO_SMALL but had no case for RSA_BUFFER_E, which is a distinct wolfCrypt error code that the RSA primitives return when the output buffer is smaller than the modulus. Such a return fell through to the default branch and became PSA_ERROR_GENERIC_ERROR, so an undersized RSA signature or encryption buffer reported the wrong error and broke the usual retry-with-a-larger-buffer idiom. Add a RSA_BUFFER_E case next to BUFFER_E so it also maps to PSA_ERROR_BUFFER_TOO_SMALL, matching the sibling translator in psa_key_storage.c. Fixes F-6684. --- src/psa_engine.c | 1 + test/psa_server/psa_14_misc_test.c | 51 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/psa_engine.c b/src/psa_engine.c index 70c3546..cb6fbee 100644 --- a/src/psa_engine.c +++ b/src/psa_engine.c @@ -73,6 +73,7 @@ psa_status_t wc_error_to_psa_status(int ret) status = PSA_ERROR_INVALID_ARGUMENT; break; case BUFFER_E: + case RSA_BUFFER_E: status = PSA_ERROR_BUFFER_TOO_SMALL; break; case MEMORY_E: diff --git a/test/psa_server/psa_14_misc_test.c b/test/psa_server/psa_14_misc_test.c index cd6fa2b..ab31ae3 100644 --- a/test/psa_server/psa_14_misc_test.c +++ b/test/psa_server/psa_14_misc_test.c @@ -674,6 +674,53 @@ static int test_chacha20_poly1305_shortened_tag(void) return 0; } +/* Case 12: an undersized RSA signature buffer must be reported as + * PSA_ERROR_BUFFER_TOO_SMALL, not PSA_ERROR_GENERIC_ERROR. wc_RsaSSL_Sign + * returns RSA_BUFFER_E when the output buffer is smaller than the modulus, and + * the error translator must map that to BUFFER_TOO_SMALL so callers can retry + * with a larger buffer. */ +static int test_rsa_sign_buffer_too_small(void) +{ + psa_algorithm_t alg = PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_SHA_256); + static const uint8_t hash[32] = { + 0x00, 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 + }; + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key = 0; + uint8_t sig[16]; /* far smaller than a 2048-bit (256-byte) signature */ + size_t sig_len = 0; + psa_status_t st; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_RSA_KEY_PAIR); + psa_set_key_bits(&attrs, 2048u); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_SIGN_HASH); + psa_set_key_algorithm(&attrs, alg); + + st = psa_generate_key(&attrs, &key); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP rsa_sign_buffer_too_small (RSA not supported by this" + " build)\n"); + return 0; + } + if (expect_status("rsa buffer-too-small: generate", st, PSA_SUCCESS) != 0) + return 1; + + st = psa_sign_hash(key, alg, hash, sizeof(hash), sig, sizeof(sig), + &sig_len); + if (expect_status("rsa sign undersized buffer", st, + PSA_ERROR_BUFFER_TOO_SMALL) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + (void)psa_destroy_key(key); + printf("PASS: RSA sign undersized buffer -> BUFFER_TOO_SMALL\n"); + return 0; +} + int main(void) { psa_status_t st; @@ -728,6 +775,10 @@ int main(void) if (test_chacha20_poly1305_shortened_tag() != 0) return 1; + /* Case 12: undersized RSA signature buffer -> BUFFER_TOO_SMALL */ + if (test_rsa_sign_buffer_too_small() != 0) + return 1; + printf("PSA 1.4 misc test: OK\n"); return 0; } From ed45118ba20fa866c4b306388697968010f2058c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 13:17:06 +0200 Subject: [PATCH 08/10] Document that DER integer encoders are public-data only The DER INTEGER encoders used by the RSA public-key export path branch on the bytes of the value being serialized: the leading-zero skip and the high-bit pad decision both depend on the input. That variable-time behavior is fine for the public modulus and exponent they serialize today, but would leak the number of leading zero bytes if the helpers were ever reused for a secret integer. Add a note on both helpers stating they run in input-dependent time and must only serialize public integers, so a future caller does not route a private exponent, CRT parameter, or private scalar through them without switching to a constant-time encoder. No behavior change. Fixes F-6683. --- src/psa_key_storage.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/psa_key_storage.c b/src/psa_key_storage.c index 16cb7df..2edb0c0 100644 --- a/src/psa_key_storage.c +++ b/src/psa_key_storage.c @@ -601,6 +601,10 @@ static size_t psa_der_write_len(uint8_t* out, size_t len) } } +/* DER INTEGER encoders. These branch on the value being serialized (leading + * zero skip and high-bit pad), so they are variable-time and for public + * integers only, such as the RSA modulus and public exponent. Serialize a + * secret integer with a constant-time fixed-width encoder instead. */ static size_t psa_der_int_size(const uint8_t* val, size_t len) { size_t offset = 0; @@ -624,6 +628,8 @@ static size_t psa_der_int_size(const uint8_t* val, size_t len) value_len + (size_t)add_zero; } +/* Public integers only; see the note on psa_der_int_size above. This encoder + * is variable-time with respect to the input bytes. */ static size_t psa_der_write_int(uint8_t* out, const uint8_t* val, size_t len) { size_t offset = 0; From af8d6dd942adbca3fea426311306ca67e5313572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 13:21:44 +0200 Subject: [PATCH 09/10] Require all requested usage bits in key permission checks The per-operation key permission helpers for MAC, cipher, AEAD and asymmetric operations authorized a key when any requested usage bit was present, using (key_usage & usage) == 0 to reject. That is correct only while every caller passes a single usage flag, which they do today, so there is no current bypass. It is a fail-open sharp edge for maintenance: a future caller passing a combined mask would have a key that holds only one of the requested permissions silently authorized. Switch the four helpers to the all-bits-required form (key_usage & usage) != usage, matching psa_check_key_usage and the PSA Crypto API contract. This is a no-op for the current single-bit callers. Fixes F-6504. --- src/psa_aead.c | 2 +- src/psa_asymmetric_api.c | 2 +- src/psa_cipher.c | 2 +- src/psa_mac.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/psa_aead.c b/src/psa_aead.c index f4a2f5d..90bfa7a 100644 --- a/src/psa_aead.c +++ b/src/psa_aead.c @@ -157,7 +157,7 @@ static psa_status_t wolfpsa_aead_check_key(psa_key_id_t key, } key_usage = psa_get_key_usage_flags(attributes); - if ((key_usage & usage) == 0) { + if ((key_usage & usage) != usage) { wolfpsa_forcezero_free_key_data(*key_data, *key_data_length); *key_data = NULL; *key_data_length = 0; diff --git a/src/psa_asymmetric_api.c b/src/psa_asymmetric_api.c index 799812d..e926dc7 100644 --- a/src/psa_asymmetric_api.c +++ b/src/psa_asymmetric_api.c @@ -267,7 +267,7 @@ static psa_status_t wolfpsa_asymmetric_check_key(psa_key_id_t key, } key_usage = psa_get_key_usage_flags(attributes); - if ((key_usage & usage) == 0) { + if ((key_usage & usage) != usage) { wolfpsa_forcezero_free_key_data(*key_data, *key_data_length); *key_data = NULL; *key_data_length = 0; diff --git a/src/psa_cipher.c b/src/psa_cipher.c index 6830c50..5bc442b 100644 --- a/src/psa_cipher.c +++ b/src/psa_cipher.c @@ -218,7 +218,7 @@ static psa_status_t wolfpsa_cipher_check_key( } key_usage = psa_get_key_usage_flags(attributes); - if ((key_usage & usage) == 0) { + if ((key_usage & usage) != usage) { wolfpsa_forcezero_free_key_data(*key_data, *key_data_length); *key_data = NULL; *key_data_length = 0; diff --git a/src/psa_mac.c b/src/psa_mac.c index ea102c4..91b84ec 100644 --- a/src/psa_mac.c +++ b/src/psa_mac.c @@ -176,7 +176,7 @@ static psa_status_t wolfpsa_mac_check_key(psa_key_id_t key, } key_usage = psa_get_key_usage_flags(attributes); - if ((key_usage & usage) == 0) { + if ((key_usage & usage) != usage) { wolfpsa_forcezero_free_key_data(*key_data, *key_data_length); *key_data = NULL; *key_data_length = 0; From 56e038eeffe379386ac8ac0d1cf9da0bc7835b33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 15:14:10 +0200 Subject: [PATCH 10/10] Return NOT_SUPPORTED for unsupported GCM nonce lengths psa_aead_set_nonce rejected every GCM nonce outside 12 to 24 bytes with PSA_ERROR_INVALID_ARGUMENT. GCM accepts any non-empty nonce, so lengths 1 to 11 and above 24 are valid for the algorithm but simply not supported by this implementation, which the PSA API signals with PSA_ERROR_NOT_SUPPORTED. INVALID_ARGUMENT is reserved for a length that is invalid for the algorithm itself, which for GCM means only a zero-length nonce. Keep INVALID_ARGUMENT for a zero-length GCM nonce and return NOT_SUPPORTED for other lengths outside the supported 12 to 24 byte range. CCM and the fixed-length AEAD nonce checks are unchanged, since those lengths are the only ones valid for their algorithms. Fixes F-6505. --- src/psa_aead.c | 9 ++- test/psa_server/psa_14_misc_test.c | 114 +++++++++++++++++++++++++++++ test/psa_server/psa_api_test.c | 5 +- 3 files changed, 125 insertions(+), 3 deletions(-) diff --git a/src/psa_aead.c b/src/psa_aead.c index 90bfa7a..6d90eb0 100644 --- a/src/psa_aead.c +++ b/src/psa_aead.c @@ -381,9 +381,16 @@ psa_status_t psa_aead_set_nonce(psa_aead_operation_t *operation, } if (PSA_ALG_AEAD_EQUAL(ctx->alg, PSA_ALG_GCM)) { - if (nonce_length < 12 || nonce_length > PSA_AEAD_NONCE_MAX_SIZE) { + /* GCM (SP 800-38D) accepts any non-empty nonce. A zero-length nonce is + * invalid for the algorithm; other lengths outside the supported 12 to + * 24 byte range are valid for GCM but not supported by this + * implementation. */ + if (nonce_length == 0) { return PSA_ERROR_INVALID_ARGUMENT; } + if (nonce_length < 12 || nonce_length > PSA_AEAD_NONCE_MAX_SIZE) { + return PSA_ERROR_NOT_SUPPORTED; + } } else if (PSA_ALG_AEAD_EQUAL(ctx->alg, PSA_ALG_CCM)) { if (nonce_length < 7 || nonce_length > 13) { diff --git a/test/psa_server/psa_14_misc_test.c b/test/psa_server/psa_14_misc_test.c index ab31ae3..2a69c62 100644 --- a/test/psa_server/psa_14_misc_test.c +++ b/test/psa_server/psa_14_misc_test.c @@ -721,6 +721,116 @@ static int test_rsa_sign_buffer_too_small(void) return 0; } +/* Case 13: GCM nonce-length error codes. GCM (SP 800-38D) accepts any + * non-empty nonce; a zero-length nonce is invalid for the algorithm + * (INVALID_ARGUMENT), while non-empty lengths outside the supported 12-24 byte + * range are valid for GCM but not supported by this implementation + * (NOT_SUPPORTED). A 12-byte nonce is accepted. */ +static int test_gcm_nonce_lengths(void) +{ + static const uint8_t key_bytes[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + }; + static const uint8_t nonce[25] = { 0 }; + static const uint8_t pt[16] = { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f + }; + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key = 0; + uint8_t ct[sizeof(pt) + 16]; + uint8_t ptout[sizeof(pt)]; + size_t ct_len = 0; + size_t pt_len = 0; + psa_status_t st; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_AES); + 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_GCM); + + st = psa_import_key(&attrs, key_bytes, sizeof(key_bytes), &key); + if (st == PSA_ERROR_NOT_SUPPORTED) { + printf("SKIP gcm_nonce_lengths (GCM not supported by this build)\n"); + return 0; + } + if (expect_status("gcm nonce import", st, PSA_SUCCESS) != 0) + return 1; + + /* 0 bytes: invalid for GCM */ + st = psa_aead_encrypt(key, PSA_ALG_GCM, nonce, 0, NULL, 0, + pt, sizeof(pt), ct, sizeof(ct), &ct_len); + if (expect_status("gcm nonce 0", st, PSA_ERROR_INVALID_ARGUMENT) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + /* 8 bytes: valid GCM nonce, unsupported here */ + st = psa_aead_encrypt(key, PSA_ALG_GCM, nonce, 8, NULL, 0, + pt, sizeof(pt), ct, sizeof(ct), &ct_len); + if (expect_status("gcm nonce 8", st, PSA_ERROR_NOT_SUPPORTED) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + /* 25 bytes: valid GCM nonce, exceeds supported max */ + st = psa_aead_encrypt(key, PSA_ALG_GCM, nonce, 25, NULL, 0, + pt, sizeof(pt), ct, sizeof(ct), &ct_len); + if (expect_status("gcm nonce 25", st, PSA_ERROR_NOT_SUPPORTED) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + /* 12 bytes: supported (lower boundary) */ + st = psa_aead_encrypt(key, PSA_ALG_GCM, nonce, 12, NULL, 0, + pt, sizeof(pt), ct, sizeof(ct), &ct_len); + if (expect_status("gcm nonce 12", st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + /* 24 bytes: supported (upper boundary, PSA_AEAD_NONCE_MAX_SIZE) */ + st = psa_aead_encrypt(key, PSA_ALG_GCM, nonce, 24, NULL, 0, + pt, sizeof(pt), ct, sizeof(ct), &ct_len); + if (expect_status("gcm nonce 24", st, PSA_SUCCESS) != 0) { + (void)psa_destroy_key(key); + return 1; + } + + /* The decrypt direction reaches the same nonce validation. ct holds the + * 24-byte-nonce ciphertext from above; only the nonce length is exercised + * here, so the tag never verifies. */ + st = psa_aead_decrypt(key, PSA_ALG_GCM, nonce, 0, NULL, 0, + ct, ct_len, ptout, sizeof(ptout), &pt_len); + if (expect_status("gcm decrypt nonce 0", st, PSA_ERROR_INVALID_ARGUMENT) + != 0) { + (void)psa_destroy_key(key); + return 1; + } + + st = psa_aead_decrypt(key, PSA_ALG_GCM, nonce, 8, NULL, 0, + ct, ct_len, ptout, sizeof(ptout), &pt_len); + if (expect_status("gcm decrypt nonce 8", st, PSA_ERROR_NOT_SUPPORTED) + != 0) { + (void)psa_destroy_key(key); + return 1; + } + + st = psa_aead_decrypt(key, PSA_ALG_GCM, nonce, 25, NULL, 0, + ct, ct_len, ptout, sizeof(ptout), &pt_len); + if (expect_status("gcm decrypt nonce 25", st, PSA_ERROR_NOT_SUPPORTED) + != 0) { + (void)psa_destroy_key(key); + return 1; + } + + (void)psa_destroy_key(key); + printf("PASS: GCM nonce length handling\n"); + return 0; +} + int main(void) { psa_status_t st; @@ -779,6 +889,10 @@ int main(void) if (test_rsa_sign_buffer_too_small() != 0) return 1; + /* Case 13: GCM nonce-length error codes */ + if (test_gcm_nonce_lengths() != 0) + return 1; + printf("PSA 1.4 misc test: OK\n"); return 0; } diff --git a/test/psa_server/psa_api_test.c b/test/psa_server/psa_api_test.c index aeeff8e..fe6029d 100644 --- a/test/psa_server/psa_api_test.c +++ b/test/psa_server/psa_api_test.c @@ -3770,8 +3770,9 @@ static int test_aead_gcm_rejects_short_nonce(void) } st = psa_aead_set_nonce(&op, short_nonce, sizeof(short_nonce)); - if (check_true(st == PSA_ERROR_INVALID_ARGUMENT, - "psa_aead_set_nonce rejects 11-byte GCM nonce") != TEST_OK) { + if (check_true(st == PSA_ERROR_NOT_SUPPORTED, + "psa_aead_set_nonce reports 11-byte GCM nonce unsupported") + != TEST_OK) { goto cleanup; }