Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
9368edd
F-5550: enforce the full key-agreement policy, not just the base algo…
danielinux Jun 9, 2026
35ff7e3
F-5557: reject oversized data_length in psa_import_key to prevent buf…
danielinux Jun 9, 2026
3fd680f
F-5552: add known-answer tests for non-SHA-256 hash dispatch
danielinux Jun 9, 2026
4d93f95
F-5549: reject NULL reference hash in psa_hash_compare
danielinux Jun 9, 2026
d6d4e2f
F-4557: add AES-CMAC known-answer test to cover block-cipher-MAC disp…
danielinux Jun 9, 2026
9528519
F-4553: assign auto key ids from the vendor range to avoid persistent…
danielinux Jun 9, 2026
af941c9
F-4552: enable side-channel hardening in the default build
danielinux Jun 9, 2026
f0b3a3d
F-4091: add test coverage for psa_cipher_generate_iv
danielinux Jun 9, 2026
63ec17d
F-3858: add test coverage for psa_aead_generate_nonce
danielinux Jun 9, 2026
ea0e48a
F-3857: reject duplicate persistent key ids in psa_import_key
danielinux Jun 9, 2026
e068596
F-5554: honor PSA_ALG_ANY_HASH wildcard sign/verify policies
danielinux Jun 9, 2026
297a5dd
F-5551: add negative AEAD authentication-failure tag tests
danielinux Jun 9, 2026
7ced088
F-4559: free wolfCrypt MAC context on mac_setup error paths
danielinux Jun 9, 2026
85daf4d
F-4097: zeroize export buffer on key-data short read
danielinux Jun 9, 2026
6a77adb
F-4095: enforce 64-byte prehash length for Ed25519ph/Ed448ph sign/verify
danielinux Jun 9, 2026
de3664a
F-4094: enforce PSA_KEY_TYPE_RAW_DATA for non-PBKDF2 verify_key
danielinux Jun 9, 2026
2db1421
F-3865: zeroize plaintext block on psa_cipher_update encrypt paths
danielinux Jun 9, 2026
661adfc
F-3861: reject second psa_aead_set_lengths call with BAD_STATE
danielinux Jun 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/psa_aead.c
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ psa_status_t psa_aead_set_lengths(psa_aead_operation_t *operation,
if (ctx == NULL) {
return PSA_ERROR_BAD_STATE;
}
if (ctx->lengths_set) {
return PSA_ERROR_BAD_STATE;
}
if (ctx->nonce_length != 0 || ctx->aad_length != 0 || ctx->input_length != 0) {
return PSA_ERROR_BAD_STATE;
}
Expand Down
106 changes: 84 additions & 22 deletions src/psa_asymmetric_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,48 @@ psa_status_t psa_asymmetric_verify_ed448(psa_key_type_t key_type,
size_t signature_length);
#endif

/* Return non-zero if a key whose permitted-algorithm policy is the
* key-agreement algorithm 'key_alg' may be used for the requested
* key-agreement algorithm 'alg'. The base algorithm (e.g. ECDH) must match and
* the embedded KDF must match exactly, except that a raw key-agreement policy
* (no KDF) is the most permissive form and therefore permits any KDF. This
* keeps the KDF embedded in the policy as a real domain-separation barrier:
* a key restricted to e.g. ECDH+HKDF must not be usable for raw agreement or
* for a different KDF. */
static int wolfpsa_key_agreement_alg_permitted(psa_algorithm_t key_alg,
psa_algorithm_t alg)
{
if (PSA_ALG_KEY_AGREEMENT_GET_BASE(key_alg) !=
PSA_ALG_KEY_AGREEMENT_GET_BASE(alg)) {
return 0;
}
if (PSA_ALG_IS_RAW_KEY_AGREEMENT(key_alg)) {
return 1;
}
return PSA_ALG_KEY_AGREEMENT_GET_KDF(key_alg) ==
PSA_ALG_KEY_AGREEMENT_GET_KDF(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. */
static int wolfpsa_sign_alg_permitted(psa_algorithm_t key_alg,
psa_algorithm_t alg)
{
if (key_alg == alg) {
return 1;
}
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));
}
return 0;
}

static psa_status_t wolfpsa_asymmetric_check_key(psa_key_id_t key,
psa_key_usage_t usage,
psa_algorithm_t alg,
Expand Down Expand Up @@ -200,15 +242,14 @@ static psa_status_t wolfpsa_asymmetric_check_key(psa_key_id_t key,

/* Algorithm match checks */
if (PSA_ALG_IS_KEY_AGREEMENT(alg) && PSA_ALG_IS_KEY_AGREEMENT(key_alg)) {
if (PSA_ALG_KEY_AGREEMENT_GET_BASE(key_alg) !=
PSA_ALG_KEY_AGREEMENT_GET_BASE(alg)) {
if (!wolfpsa_key_agreement_alg_permitted(key_alg, alg)) {
wolfpsa_forcezero_free_key_data(*key_data, *key_data_length);
*key_data = NULL;
*key_data_length = 0;
return PSA_ERROR_NOT_PERMITTED;
}
}
else if (key_alg != alg) {
else if (!wolfpsa_sign_alg_permitted(key_alg, alg)) {
wolfpsa_forcezero_free_key_data(*key_data, *key_data_length);
*key_data = NULL;
*key_data_length = 0;
Expand Down Expand Up @@ -648,16 +689,21 @@ psa_status_t psa_verify_message(psa_key_id_t key,
return status;
}

psa_status_t psa_raw_key_agreement(psa_algorithm_t alg,
psa_key_id_t private_key,
const uint8_t *peer_key,
size_t peer_key_length,
uint8_t *output,
size_t output_size,
size_t *output_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
* combined PSA_ALG_KEY_AGREEMENT(PSA_ALG_ECDH, kdf)) so that the KDF embedded
* in the policy is enforced, not just the base algorithm. Shared by
* psa_raw_key_agreement(), psa_key_agreement() and
* psa_key_derivation_key_agreement(). */
psa_status_t wolfpsa_key_agreement_secret(psa_algorithm_t alg,
psa_key_id_t private_key,
const uint8_t *peer_key,
size_t peer_key_length,
uint8_t *output,
size_t output_size,
size_t *output_length)
{
wolfpsa_trace("psa_raw_key_agreement(alg=0x%08x key=%u peer_len=%zu)",
(unsigned)alg, (unsigned)private_key, peer_key_length);
psa_key_attributes_t attributes;
uint8_t *key_data = NULL;
size_t key_data_length = 0;
Expand All @@ -668,13 +714,6 @@ psa_status_t psa_raw_key_agreement(psa_algorithm_t alg,
int curve_id;
word32 out_len;

if (output == NULL || output_length == NULL) {
return PSA_ERROR_INVALID_ARGUMENT;
}

if (!PSA_ALG_IS_RAW_KEY_AGREEMENT(alg)) {
return PSA_ERROR_INVALID_ARGUMENT;
}
if (PSA_ALG_KEY_AGREEMENT_GET_BASE(alg) != PSA_ALG_ECDH) {
return PSA_ERROR_NOT_SUPPORTED;
}
Expand Down Expand Up @@ -801,6 +840,29 @@ psa_status_t psa_raw_key_agreement(psa_algorithm_t alg,
return PSA_SUCCESS;
}

psa_status_t psa_raw_key_agreement(psa_algorithm_t alg,
psa_key_id_t private_key,
const uint8_t *peer_key,
size_t peer_key_length,
uint8_t *output,
size_t output_size,
size_t *output_length)
{
wolfpsa_trace("psa_raw_key_agreement(alg=0x%08x key=%u peer_len=%zu)",
(unsigned)alg, (unsigned)private_key, peer_key_length);

if (output == NULL || output_length == NULL) {
return PSA_ERROR_INVALID_ARGUMENT;
}
if (!PSA_ALG_IS_RAW_KEY_AGREEMENT(alg)) {
return PSA_ERROR_INVALID_ARGUMENT;
}

return wolfpsa_key_agreement_secret(alg, private_key, peer_key,
peer_key_length, output, output_size,
output_length);
}

psa_status_t psa_key_agreement(psa_key_id_t private_key,
const uint8_t *peer_key,
size_t peer_key_length,
Expand Down Expand Up @@ -841,9 +903,9 @@ psa_status_t psa_key_agreement(psa_key_id_t private_key,
return PSA_ERROR_INSUFFICIENT_MEMORY;
}

status = psa_raw_key_agreement(PSA_ALG_KEY_AGREEMENT_GET_BASE(alg),
private_key, peer_key, peer_key_length,
secret, secret_len, &output_len);
status = wolfpsa_key_agreement_secret(alg, private_key, peer_key,
peer_key_length, secret, secret_len,
&output_len);
if (status != PSA_SUCCESS) {
wc_ForceZero(secret, secret_len);
XFREE(secret, NULL, DYNAMIC_TYPE_TMP_BUFFER);
Expand Down
3 changes: 3 additions & 0 deletions src/psa_cipher.c
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,7 @@ psa_status_t psa_cipher_update(psa_cipher_operation_t *operation,
return wolfpsa_cipher_fail(operation,
wc_error_to_psa_status(ret));
}
wc_ForceZero(block, sizeof(block));
output_offset += block_size;
input_offset += needed;
ctx->partial_len = 0;
Expand Down Expand Up @@ -916,6 +917,7 @@ psa_status_t psa_cipher_update(psa_cipher_operation_t *operation,
return wolfpsa_cipher_fail(operation,
wc_error_to_psa_status(ret));
}
wc_ForceZero(block, sizeof(block));
output_offset += block_size;
input_offset += needed;
ctx->partial_len = 0;
Expand Down Expand Up @@ -1140,6 +1142,7 @@ psa_status_t psa_cipher_update(psa_cipher_operation_t *operation,
return wolfpsa_cipher_fail(operation,
wc_error_to_psa_status(ret));
}
wc_ForceZero(block, sizeof(block));
output_offset += block_size;
input_offset += needed;
ctx->partial_len = 0;
Expand Down
28 changes: 23 additions & 5 deletions src/psa_ed25519_ed448.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,18 @@ 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)) {
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 the stored private seed and derive the public component. */
ret = wc_ed25519_import_private_only(key_buffer, (word32)key_buffer_size,
&ed_key);
Expand Down Expand Up @@ -140,8 +145,12 @@ 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)) {
return PSA_ERROR_INVALID_ARGUMENT;
}


/* Initialize ED25519 key */
ret = wc_ed25519_init(&ed_key);
if (ret != 0) {
Expand Down Expand Up @@ -352,7 +361,12 @@ 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) {
return PSA_ERROR_INVALID_ARGUMENT;
}

/* Initialize ED448 key */
ret = wc_ed448_init(&ed_key);
if (ret != 0) {
Expand Down Expand Up @@ -416,8 +430,12 @@ 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) {
return PSA_ERROR_INVALID_ARGUMENT;
}


/* Initialize ED448 key */
ret = wc_ed448_init(&ed_key);
if (ret != 0) {
Expand Down
2 changes: 1 addition & 1 deletion src/psa_hash_engine.c
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,7 @@ psa_status_t psa_hash_compare(psa_algorithm_t alg,
}

/* Check if the reference hash length is valid */
if (!PSA_ALG_IS_HASH(alg)) {
if (!PSA_ALG_IS_HASH(alg) || hash == NULL) {
return PSA_ERROR_INVALID_ARGUMENT;
}

Expand Down
25 changes: 20 additions & 5 deletions src/psa_key_derivation.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@
#include <wolfcrypt/src/misc.c>
#endif

/* Defined in psa_asymmetric_api.c: compute the raw ECDH shared secret while
* enforcing the private key's full key-agreement policy (base algorithm and
* embedded KDF), not just the base algorithm. */
extern psa_status_t wolfpsa_key_agreement_secret(psa_algorithm_t alg,
psa_key_id_t private_key,
const uint8_t *peer_key,
size_t peer_key_length,
uint8_t *output,
size_t output_size,
size_t *output_length);

typedef struct wolfpsa_kdf_ctx {
psa_algorithm_t alg;
psa_algorithm_t ka_alg;
Expand Down Expand Up @@ -749,9 +760,9 @@ psa_status_t psa_key_derivation_key_agreement(psa_key_derivation_operation_t *op
return PSA_ERROR_INSUFFICIENT_MEMORY;
}

status = psa_raw_key_agreement(ctx->ka_alg, private_key,
peer_key, peer_key_length,
secret, secret_len, &output_len);
status = wolfpsa_key_agreement_secret(
PSA_ALG_KEY_AGREEMENT(ctx->ka_alg, ctx->alg), private_key,
peer_key, peer_key_length, secret, secret_len, &output_len);
if (status == PSA_SUCCESS) {
status = psa_key_derivation_input_bytes(operation,
step,
Expand Down Expand Up @@ -1414,8 +1425,12 @@ psa_status_t psa_key_derivation_verify_key(psa_key_derivation_operation_t *opera
return PSA_ERROR_NOT_PERMITTED;
}

if (PSA_ALG_IS_PBKDF2(ctx->alg) &&
psa_get_key_type(&attributes) != PSA_KEY_TYPE_PASSWORD_HASH) {
if (PSA_ALG_IS_PBKDF2(ctx->alg)) {
if (psa_get_key_type(&attributes) != PSA_KEY_TYPE_PASSWORD_HASH) {
return PSA_ERROR_INVALID_ARGUMENT;
}
}
else if (psa_get_key_type(&attributes) != PSA_KEY_TYPE_RAW_DATA) {
return PSA_ERROR_INVALID_ARGUMENT;
}

Expand Down
39 changes: 37 additions & 2 deletions src/psa_key_storage.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@

/* Key storage state */
static int g_key_storage_initialized = 0;
static psa_key_id_t g_next_key_id = 1;
static psa_key_id_t g_next_key_id = PSA_KEY_ID_VENDOR_MIN;

typedef struct wolfpsa_volatile_key_node {
psa_key_id_t id;
Expand Down Expand Up @@ -871,6 +871,14 @@ psa_status_t psa_import_key(
/* Always treat key_id as output-only. */
*key_id = PSA_KEY_ID_NULL;

/* Reject lengths that do not fit the int-based storage API or that would
* overflow the serialized buffer_size computation below. */
if (data_length > (size_t)INT_MAX) {
wolfpsa_debug_import_reason("key data length too large", attributes,
data_length);
return PSA_ERROR_INVALID_ARGUMENT;
}

attr = *attributes;

if (attr.policy.alg2 != PSA_ALG_NONE) {
Expand Down Expand Up @@ -948,7 +956,13 @@ psa_status_t psa_import_key(
*key_id = attr_id;
}
else {
if (g_next_key_id == PSA_KEY_ID_NULL) {
/* Auto-assign implementation key ids from the vendor range so
* they cannot collide with caller-specified persistent ids, which
* the PSA API reserves to the user range. A collision would let an
* auto-assigned volatile key shadow a persistent record on read and
* survive psa_destroy_key() on disk. */
if (g_next_key_id < PSA_KEY_ID_VENDOR_MIN ||
g_next_key_id > PSA_KEY_ID_VENDOR_MAX) {
return PSA_ERROR_INSUFFICIENT_STORAGE;
}
*key_id = g_next_key_id++;
Expand Down Expand Up @@ -985,6 +999,23 @@ psa_status_t psa_import_key(
}
}
else {
/* The PSA Crypto API requires psa_import_key() to fail with
* PSA_ERROR_ALREADY_EXISTS when a persistent key already exists with
* the requested id; the volatile path enforces the same above. Probe
* for an existing record before opening a write handle, otherwise the
* atomic rename in wolfPSA_Store_Close() would silently destroy the
* previously stored key. */
ret = wolfPSA_Store_Open(WOLFPSA_STORE_KEY, (unsigned long)*key_id, 0,
1, &store);
if (ret == 0) {
wolfPSA_Store_Close(store);
store = NULL;
wc_ForceZero(buffer, buffer_size);
XFREE(buffer, NULL, DYNAMIC_TYPE_TMP_BUFFER);
*key_id = PSA_KEY_ID_NULL;
return PSA_ERROR_ALREADY_EXISTS;
}

/* Open and write key to persistent storage */
ret = wolfPSA_Store_OpenSz(WOLFPSA_STORE_KEY, (unsigned long)*key_id, 0,
0, (int)data_length, &store);
Expand Down Expand Up @@ -1364,6 +1395,10 @@ psa_status_t psa_export_key(
wolfPSA_Store_Close(store);
store = NULL;
if (ret != (int)key_data_length) {
/* A short/failed read may have left partial key material in the
* caller-owned buffer; zeroize it like the sibling read paths. */
wc_ForceZero(data, key_data_length);
*data_length = 0;
return PSA_ERROR_STORAGE_FAILURE;
}
*data_length = key_data_length;
Expand Down
Loading
Loading