From 25876c6f5800a7ffa6b53132413adfe5fac72f5e Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 30 Oct 2026 19:36:06 -0700 Subject: [PATCH 1/3] Fix Ed448 key/sig size, build without HAVE_ECC --- .github/workflows/build-config-matrix.yml | 5 +++ src/psa_asymmetric_api.c | 10 +++++ test/psa_server/psa_eddsa_mont_export_test.c | 11 ++++++ test/psa_server/psa_pure_eddsa_context_test.c | 38 +++++++++++++++++++ wolfpsa/psa/crypto_sizes.h | 5 ++- 5 files changed, 67 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-config-matrix.yml b/.github/workflows/build-config-matrix.yml index b1d015b2..1557bb00 100644 --- a/.github/workflows/build-config-matrix.yml +++ b/.github/workflows/build-config-matrix.yml @@ -84,6 +84,9 @@ jobs: modifiers: "-HAVE_PBKDF2 +NO_PBKDF2" - name: ecc384 modifiers: "-WOLFSSL_SP_384 -HAVE_ECC384" + - name: no-ecc + modifiers: "-HAVE_ECC -HAVE_ECC_ENCRYPT" + target: libwolfpsa.so - name: rsa-1024 modifiers: "-WOLFSSL_SP_1024 -RSA_MIN_SIZE +RSA_MIN_SIZE=2048" # wolfPSA's own sources only: the bundled wolfCrypt sources do not @@ -108,4 +111,6 @@ jobs: - name: Build ${{ matrix.config.name }} env: BUILD_TARGET: ${{ matrix.config.target || 'libwolfpsa.a' }} + # Fail on undefined symbols + LDFLAGS: -Wl,-z,defs run: ./build-test/build-variant.sh "${{ matrix.config.name }}" ${{ matrix.config.modifiers }} diff --git a/src/psa_asymmetric_api.c b/src/psa_asymmetric_api.c index b241b02c..44a40c45 100644 --- a/src/psa_asymmetric_api.c +++ b/src/psa_asymmetric_api.c @@ -105,6 +105,7 @@ psa_status_t psa_asymmetric_decrypt_rsa(psa_key_type_t key_type, uint8_t *output, size_t output_size, size_t *output_length); +#ifdef HAVE_ECC psa_status_t psa_asymmetric_sign_ecc(psa_key_type_t key_type, size_t key_bits, const uint8_t *key_buffer, @@ -124,6 +125,7 @@ psa_status_t psa_asymmetric_verify_ecc(psa_key_type_t key_type, size_t hash_length, const uint8_t *signature, size_t signature_length); +#endif /* HAVE_ECC */ #ifdef HAVE_ED25519 psa_status_t psa_asymmetric_sign_ed25519(psa_key_type_t key_type, size_t key_bits, @@ -604,6 +606,7 @@ static psa_status_t wolfpsa_sign_hash_worker(psa_key_id_t key, } } #endif +#ifdef HAVE_ECC else if (PSA_KEY_TYPE_IS_ECC(attributes.type)) { status = psa_asymmetric_sign_ecc(attributes.type, attributes.bits, key_data, key_data_length, @@ -611,6 +614,7 @@ static psa_status_t wolfpsa_sign_hash_worker(psa_key_id_t key, signature, signature_size, signature_length); } +#endif /* HAVE_ECC */ else { status = PSA_ERROR_NOT_SUPPORTED; } @@ -745,12 +749,14 @@ static psa_status_t wolfpsa_verify_hash_worker(psa_key_id_t key, } } #endif +#ifdef HAVE_ECC else if (PSA_KEY_TYPE_IS_ECC(attributes.type)) { status = psa_asymmetric_verify_ecc(attributes.type, attributes.bits, key_data, key_data_length, alg, hash, hash_length, signature, signature_length); } +#endif /* HAVE_ECC */ else { status = PSA_ERROR_NOT_SUPPORTED; } @@ -1036,6 +1042,7 @@ static psa_status_t wolfpsa_sign_message_worker(psa_key_id_t key, } } #endif +#ifdef HAVE_ECC else if (PSA_KEY_TYPE_IS_ECC(attributes.type)) { status = psa_asymmetric_sign_ecc(attributes.type, attributes.bits, key_data, key_data_length, @@ -1043,6 +1050,7 @@ static psa_status_t wolfpsa_sign_message_worker(psa_key_id_t key, signature, signature_size, signature_length); } +#endif /* HAVE_ECC */ else { status = PSA_ERROR_NOT_SUPPORTED; } @@ -1284,12 +1292,14 @@ static psa_status_t wolfpsa_verify_message_worker(psa_key_id_t key, } } #endif +#ifdef HAVE_ECC else if (PSA_KEY_TYPE_IS_ECC(attributes.type)) { status = psa_asymmetric_verify_ecc(attributes.type, attributes.bits, key_data, key_data_length, alg, hash, hash_length, signature, signature_length); } +#endif /* HAVE_ECC */ else { status = PSA_ERROR_NOT_SUPPORTED; } diff --git a/test/psa_server/psa_eddsa_mont_export_test.c b/test/psa_server/psa_eddsa_mont_export_test.c index 2543f668..03bc9bc6 100644 --- a/test/psa_server/psa_eddsa_mont_export_test.c +++ b/test/psa_server/psa_eddsa_mont_export_test.c @@ -47,6 +47,8 @@ static int test_export_pair_public(psa_ecc_family_t family, psa_key_type_t key_type; uint8_t pub[PSA_KEY_EXPORT_ECC_PUBLIC_KEY_MAX_SIZE(448)]; size_t pub_size = sizeof(pub); + uint8_t pair[PSA_KEY_EXPORT_ECC_KEY_PAIR_MAX_SIZE(448)]; + size_t pair_len = 0; psa_status_t st; int ok = 0; @@ -76,6 +78,15 @@ static int test_export_pair_public(psa_ecc_family_t family, goto destroy; } + st = psa_export_key(key_id, pair, + PSA_EXPORT_KEY_OUTPUT_SIZE(key_type, bits), + &pair_len); + if (st != PSA_SUCCESS) { + printf("FAIL %s key pair export into PSA_EXPORT_KEY_OUTPUT_SIZE " + "status=%d\n", label, (int)st); + goto destroy; + } + ok = 1; destroy: diff --git a/test/psa_server/psa_pure_eddsa_context_test.c b/test/psa_server/psa_pure_eddsa_context_test.c index da2cc45c..0373948d 100644 --- a/test/psa_server/psa_pure_eddsa_context_test.c +++ b/test/psa_server/psa_pure_eddsa_context_test.c @@ -135,6 +135,42 @@ static int test_pure_eddsa_rejects_context(void) return rc; } +static int test_pure_eddsa_sign_output_size(psa_key_bits_t bits) +{ + const psa_key_type_t type = + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS); + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key_id = PSA_KEY_ID_NULL; + psa_status_t st; + uint8_t message[32]; + uint8_t signature[128]; + size_t signature_length = 0; + + memset(message, 0xab, sizeof(message)); + psa_set_key_type(&attrs, type); + psa_set_key_bits(&attrs, bits); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_SIGN_MESSAGE); + psa_set_key_algorithm(&attrs, PSA_ALG_PURE_EDDSA); + + st = psa_generate_key(&attrs, &key_id); + if (st == PSA_SUCCESS) { + st = psa_sign_message(key_id, PSA_ALG_PURE_EDDSA, + message, sizeof(message), signature, + PSA_SIGN_OUTPUT_SIZE(type, bits, + PSA_ALG_PURE_EDDSA), + &signature_length); + psa_destroy_key(key_id); + } + if (st != PSA_SUCCESS) { + printf("FAIL pure_eddsa %u-bit sign into PSA_SIGN_OUTPUT_SIZE: %d\n", + (unsigned)bits, (int)st); + return 1; + } + printf("PASS pure_eddsa %u-bit sign into PSA_SIGN_OUTPUT_SIZE\n", + (unsigned)bits); + return 0; +} + int main(void) { int rc; @@ -144,6 +180,8 @@ int main(void) return 1; } rc = test_pure_eddsa_rejects_context(); + rc |= test_pure_eddsa_sign_output_size(255); + rc |= test_pure_eddsa_sign_output_size(448); if (rc == 0) { printf("psa_pure_eddsa_context_test: all tests passed\n"); } diff --git a/wolfpsa/psa/crypto_sizes.h b/wolfpsa/psa/crypto_sizes.h index 5c17dea3..5ca93579 100644 --- a/wolfpsa/psa/crypto_sizes.h +++ b/wolfpsa/psa/crypto_sizes.h @@ -272,7 +272,8 @@ #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_ECC(key_type) ? \ + 2u * PSA_KEY_EXPORT_ECC_KEY_PAIR_MAX_SIZE(key_bits) : \ PSA_KEY_TYPE_IS_ML_DSA(key_type) ? \ ((void) alg, \ (key_bits) == 128u ? 2420u : \ @@ -348,7 +349,7 @@ #define PSA_KEY_EXPORT_ECC_KEY_PAIR_MAX_SIZE(key_bits) \ - (PSA_BITS_TO_BYTES(key_bits)) + ((key_bits) == 448u ? 57u : PSA_BITS_TO_BYTES(key_bits)) #define PSA_KEY_EXPORT_FFDH_KEY_PAIR_MAX_SIZE(key_bits) \ From c60535e008df0a83f20c18720af3d9f6410f2213 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Mon, 2 Nov 2026 12:05:00 -0800 Subject: [PATCH 2/3] PR feedback: updpate PSA_SIGNATURE_MAX_SIZE, SECP size check, length checks in test, CI build fix --- .github/workflows/build-config-matrix.yml | 8 ++++++-- src/psa_asymmetric_api.c | 18 ++++++++++++++++-- test/psa_server/psa_eddsa_mont_export_test.c | 7 +++++++ test/psa_server/psa_pure_eddsa_context_test.c | 12 +++++++++--- wolfpsa/psa/crypto_sizes.h | 19 +++++++++++++++++-- 5 files changed, 55 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-config-matrix.yml b/.github/workflows/build-config-matrix.yml index 1557bb00..14f44fb1 100644 --- a/.github/workflows/build-config-matrix.yml +++ b/.github/workflows/build-config-matrix.yml @@ -80,13 +80,17 @@ jobs: modifiers: "-HAVE_HKDF -HAVE_ECC_ENCRYPT" - name: tls-prf modifiers: "-WOLFSSL_HAVE_PRF" + # This config stays as a static library (not shared) because of a + # missing symbol in this build of wolfSSL. 'target' field and the + # LDFLAGS environment variable below can be removed after + # wolfSSL#11958 merges. - name: pbkdf2 modifiers: "-HAVE_PBKDF2 +NO_PBKDF2" + target: libwolfpsa.a - name: ecc384 modifiers: "-WOLFSSL_SP_384 -HAVE_ECC384" - name: no-ecc modifiers: "-HAVE_ECC -HAVE_ECC_ENCRYPT" - target: libwolfpsa.so - name: rsa-1024 modifiers: "-WOLFSSL_SP_1024 -RSA_MIN_SIZE +RSA_MIN_SIZE=2048" # wolfPSA's own sources only: the bundled wolfCrypt sources do not @@ -110,7 +114,7 @@ jobs: - name: Build ${{ matrix.config.name }} env: - BUILD_TARGET: ${{ matrix.config.target || 'libwolfpsa.a' }} + BUILD_TARGET: ${{ matrix.config.target || 'libwolfpsa.so' }} # Fail on undefined symbols LDFLAGS: -Wl,-z,defs run: ./build-test/build-variant.sh "${{ matrix.config.name }}" ${{ matrix.config.modifiers }} diff --git a/src/psa_asymmetric_api.c b/src/psa_asymmetric_api.c index 44a40c45..0aa5467d 100644 --- a/src/psa_asymmetric_api.c +++ b/src/psa_asymmetric_api.c @@ -369,6 +369,20 @@ static psa_status_t wolfpsa_check_context(psa_algorithm_t alg, return PSA_ERROR_INVALID_ARGUMENT; } +/* Required signature size, or 0 when this build cannot sign with key_type */ +static size_t wolfpsa_sign_output_size(psa_key_type_t key_type, + size_t key_bits, + psa_algorithm_t alg) +{ +#ifndef HAVE_ECC + if (PSA_KEY_TYPE_IS_ECC(key_type) && + key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS)) { + return 0; + } +#endif /* !HAVE_ECC */ + return PSA_SIGN_OUTPUT_SIZE(key_type, key_bits, alg); +} + psa_status_t psa_asymmetric_encrypt(psa_key_id_t key, psa_algorithm_t alg, const uint8_t *input, @@ -564,7 +578,7 @@ static psa_status_t wolfpsa_sign_hash_worker(psa_key_id_t key, * contract status for every signature family. Checked after the * algorithm and key-type rejections above: a call that can never succeed * must report why, not a buffer problem. */ - sig_size = PSA_SIGN_OUTPUT_SIZE(attributes.type, attributes.bits, alg); + sig_size = wolfpsa_sign_output_size(attributes.type, attributes.bits, alg); if (sig_size != 0 && signature_size < sig_size) { wolfpsa_forcezero_free_key_data(key_data, key_data_length); return PSA_ERROR_BUFFER_TOO_SMALL; @@ -877,7 +891,7 @@ static psa_status_t wolfpsa_sign_message_worker(psa_key_id_t key, /* Required signature capacity, so a zero-capacity buffer gets the * contract status for every signature family. */ - sig_size = PSA_SIGN_OUTPUT_SIZE(attributes.type, attributes.bits, alg); + sig_size = wolfpsa_sign_output_size(attributes.type, attributes.bits, alg); if (sig_size != 0 && signature_size < sig_size) { wolfpsa_forcezero_free_key_data(key_data, key_data_length); return PSA_ERROR_BUFFER_TOO_SMALL; diff --git a/test/psa_server/psa_eddsa_mont_export_test.c b/test/psa_server/psa_eddsa_mont_export_test.c index 03bc9bc6..a5672f7b 100644 --- a/test/psa_server/psa_eddsa_mont_export_test.c +++ b/test/psa_server/psa_eddsa_mont_export_test.c @@ -87,6 +87,13 @@ static int test_export_pair_public(psa_ecc_family_t family, goto destroy; } + if (pair_len != PSA_EXPORT_KEY_OUTPUT_SIZE(key_type, bits)) { + printf("FAIL %s key pair export len=%u expected=%u\n", label, + (unsigned)pair_len, + (unsigned)PSA_EXPORT_KEY_OUTPUT_SIZE(key_type, bits)); + goto destroy; + } + ok = 1; destroy: diff --git a/test/psa_server/psa_pure_eddsa_context_test.c b/test/psa_server/psa_pure_eddsa_context_test.c index 0373948d..665515e2 100644 --- a/test/psa_server/psa_pure_eddsa_context_test.c +++ b/test/psa_server/psa_pure_eddsa_context_test.c @@ -139,6 +139,8 @@ static int test_pure_eddsa_sign_output_size(psa_key_bits_t bits) { const psa_key_type_t type = PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS); + const size_t sig_size = + PSA_SIGN_OUTPUT_SIZE(type, bits, PSA_ALG_PURE_EDDSA); psa_key_attributes_t attrs = psa_key_attributes_init(); psa_key_id_t key_id = PSA_KEY_ID_NULL; psa_status_t st; @@ -156,9 +158,7 @@ static int test_pure_eddsa_sign_output_size(psa_key_bits_t bits) if (st == PSA_SUCCESS) { st = psa_sign_message(key_id, PSA_ALG_PURE_EDDSA, message, sizeof(message), signature, - PSA_SIGN_OUTPUT_SIZE(type, bits, - PSA_ALG_PURE_EDDSA), - &signature_length); + sig_size, &signature_length); psa_destroy_key(key_id); } if (st != PSA_SUCCESS) { @@ -166,6 +166,12 @@ static int test_pure_eddsa_sign_output_size(psa_key_bits_t bits) (unsigned)bits, (int)st); return 1; } + if (signature_length != sig_size) { + printf("FAIL pure_eddsa %u-bit signature len=%u expected=%u\n", + (unsigned)bits, (unsigned)signature_length, + (unsigned)sig_size); + return 1; + } printf("PASS pure_eddsa %u-bit sign into PSA_SIGN_OUTPUT_SIZE\n", (unsigned)bits); return 0; diff --git a/wolfpsa/psa/crypto_sizes.h b/wolfpsa/psa/crypto_sizes.h index 5ca93579..078f41ff 100644 --- a/wolfpsa/psa/crypto_sizes.h +++ b/wolfpsa/psa/crypto_sizes.h @@ -270,10 +270,14 @@ (PSA_BITS_TO_BYTES(curve_bits) * 2u) +/* ECDSA or EdDSA: an Ed448 signature is 114 bytes, not 2 * 56 */ +#define PSA_VENDOR_ECC_SIGNATURE_SIZE(key_bits) \ + ((key_bits) == 448u ? 114u : PSA_ECDSA_SIGNATURE_SIZE(key_bits)) + + #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) ? \ - 2u * PSA_KEY_EXPORT_ECC_KEY_PAIR_MAX_SIZE(key_bits) : \ + PSA_KEY_TYPE_IS_ECC(key_type) ? PSA_VENDOR_ECC_SIGNATURE_SIZE(key_bits) : \ PSA_KEY_TYPE_IS_ML_DSA(key_type) ? \ ((void) alg, \ (key_bits) == 128u ? 2420u : \ @@ -292,6 +296,15 @@ #undef PSA_SIGNATURE_MAX_SIZE #define PSA_SIGNATURE_MAX_SIZE PSA_VENDOR_ECDSA_SIGNATURE_MAX_SIZE #endif +#if defined(PSA_WANT_ECC_TWISTED_EDWARDS_448) && \ + (PSA_VENDOR_ECC_SIGNATURE_SIZE(448u) > PSA_SIGNATURE_MAX_SIZE) +#undef PSA_SIGNATURE_MAX_SIZE +#define PSA_SIGNATURE_MAX_SIZE PSA_VENDOR_ECC_SIGNATURE_SIZE(448u) +#elif defined(PSA_WANT_ECC_TWISTED_EDWARDS_255) && \ + (PSA_VENDOR_ECC_SIGNATURE_SIZE(255u) > PSA_SIGNATURE_MAX_SIZE) +#undef PSA_SIGNATURE_MAX_SIZE +#define PSA_SIGNATURE_MAX_SIZE PSA_VENDOR_ECC_SIGNATURE_SIZE(255u) +#endif #if (defined(PSA_WANT_ALG_RSA_PKCS1V15_SIGN) || defined(PSA_WANT_ALG_RSA_PSS)) && \ (PSA_BITS_TO_BYTES(PSA_VENDOR_RSA_MAX_KEY_BITS) > PSA_SIGNATURE_MAX_SIZE) #undef PSA_SIGNATURE_MAX_SIZE @@ -367,6 +380,8 @@ (key_type) == PSA_KEY_TYPE_RSA_PUBLIC_KEY ? PSA_KEY_EXPORT_RSA_PUBLIC_KEY_MAX_SIZE(key_bits) : \ (key_type) == PSA_KEY_TYPE_DSA_KEY_PAIR ? PSA_KEY_EXPORT_DSA_KEY_PAIR_MAX_SIZE(key_bits) : \ (key_type) == PSA_KEY_TYPE_DSA_PUBLIC_KEY ? PSA_KEY_EXPORT_DSA_PUBLIC_KEY_MAX_SIZE(key_bits) : \ + (key_type) == PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_MONTGOMERY) ? \ + PSA_BITS_TO_BYTES(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 */ \ From 62b1d212e115c3070981983e87dadbb8bf739e86 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Mon, 2 Nov 2026 18:46:52 -0800 Subject: [PATCH 3/3] Add RSA size, padding and status checks --- .gitignore | 1 + Makefile | 1 + src/psa_rsa.c | 85 ++++++----- test/Makefile | 1 + test/psa_server/psa_rsa_status_test.c | 200 ++++++++++++++++++++++++++ 5 files changed, 253 insertions(+), 35 deletions(-) create mode 100644 test/psa_server/psa_rsa_status_test.c diff --git a/.gitignore b/.gitignore index 9542e257..8c997395 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ test/psa_des3_pkcs7_test test/psa_ecc_curve_caps_test test/psa_ecc_ecdh_curve_test test/psa_ecc_sig_len_test +test/psa_rsa_status_test test/psa_ecc_verify_curve_test test/psa_eddsa_mont_export_test test/psa_eddsa_mont_gen_test diff --git a/Makefile b/Makefile index 2e09f216..f05411c2 100644 --- a/Makefile +++ b/Makefile @@ -187,6 +187,7 @@ UNIT_TESTS := psa_api_test \ psa_ecc_curve_caps_test \ psa_xof_no_backend_test \ psa_ecc_sig_len_test \ + psa_rsa_status_test \ psa_xof_set_context_test \ psa_cipher_inplace_test \ psa_cipher_overlap_test \ diff --git a/src/psa_rsa.c b/src/psa_rsa.c index 418981d3..261f9e29 100644 --- a/src/psa_rsa.c +++ b/src/psa_rsa.c @@ -93,6 +93,36 @@ static int wc_psa_get_mgf(int hash_type) } #endif /* WC_RSA_PSS || WOLFSSL_RSA_OAEP */ +/* PSA requires the hash to be as long as the one the algorithm names. Raw + * PKCS#1 v1.5 signs any input. */ +static int wc_psa_rsa_hash_length_ok(psa_algorithm_t alg, size_t hash_length) +{ + size_t expected; + + if (alg == PSA_ALG_RSA_PKCS1V15_SIGN_RAW) { + return 1; + } + expected = PSA_HASH_LENGTH(PSA_ALG_SIGN_GET_HASH(alg)); + return expected != 0 && hash_length == expected; +} + +/* Status for a failed RSA operation. Callers check the output buffer size + * first, so a buffer, padding or range error refers to the input. */ +static psa_status_t wc_psa_rsa_status(int ret, psa_status_t bad_input) +{ + switch (ret) { + case BUFFER_E: + case RSA_BUFFER_E: + case BAD_PADDING_E: + case RSA_PAD_E: + case PSS_SALTLEN_E: + case RSA_OUT_OF_RANGE_E: + return bad_input; + default: + return wc_error_to_psa_status(ret); + } +} + #ifdef WC_RSA_PSS static int wc_psa_rsa_pss_check_padding(const byte* hash, word32 hash_length, const byte* encoded, word32 encoded_length, @@ -136,7 +166,6 @@ psa_status_t psa_asymmetric_sign_rsa(psa_key_type_t key_type, int padding; int hash_type; int hash_oid; - size_t expected_hash_len; byte* sig_input = NULL; word32 sig_input_len = 0; word32 sig_input_alloc_len = 0; @@ -150,7 +179,8 @@ psa_status_t psa_asymmetric_sign_rsa(psa_key_type_t key_type, } if ((wolfpsa_check_word32_length(key_buffer_size) != PSA_SUCCESS) || (wolfpsa_check_word32_length(hash_length) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(signature_size) != PSA_SUCCESS)) { + (wolfpsa_check_word32_length(signature_size) != PSA_SUCCESS) || + !wc_psa_rsa_hash_length_ok(alg, hash_length)) { return PSA_ERROR_INVALID_ARGUMENT; } @@ -194,18 +224,12 @@ psa_status_t psa_asymmetric_sign_rsa(psa_key_type_t key_type, wc_FreeRsaKey(&rsa_key); return PSA_ERROR_NOT_SUPPORTED; } - /* PSA requires the digest to be exactly as long as the hash - * named by the algorithm; anything else is INVALID_ARGUMENT. - * wc_EncodeSignature() would otherwise happily wrap a 20-byte - * digest in a SHA-256 DigestInfo, and since the verify path - * re-encodes the same way that malformed structure would - * round-trip to success in violation of RFC 8017 A.2.4. The - * WC_MAX_DIGEST_SIZE term is retained as the buffer - * precondition: wc_EncodeSignature() does not bound its own - * write, and the two constants can diverge. */ - expected_hash_len = PSA_HASH_LENGTH(PSA_ALG_SIGN_GET_HASH(alg)); - if (expected_hash_len == 0 || hash_length != expected_hash_len || - hash_length > WC_MAX_DIGEST_SIZE) { + /* The hash length is checked above, so wc_EncodeSignature() + * cannot wrap a 20-byte digest in a SHA-256 DigestInfo. This is + * the buffer precondition: wc_EncodeSignature() does not bound + * its own write, and WC_MAX_DIGEST_SIZE and PSA_HASH_LENGTH() + * can diverge. */ + if (hash_length > WC_MAX_DIGEST_SIZE) { wc_FreeRng(&rng); wc_FreeRsaKey(&rsa_key); return PSA_ERROR_INVALID_ARGUMENT; @@ -256,7 +280,7 @@ psa_status_t psa_asymmetric_sign_rsa(psa_key_type_t key_type, wc_FreeRsaKey(&rsa_key); if (ret < 0) { - return wc_error_to_psa_status(ret); + return wc_psa_rsa_status(ret, PSA_ERROR_INVALID_ARGUMENT); } *signature_length = (size_t)ret; @@ -280,7 +304,6 @@ psa_status_t psa_asymmetric_verify_rsa(psa_key_type_t key_type, word32 idx = 0; int padding; int hash_type; - size_t expected_hash_len; (void)key_bits; @@ -291,7 +314,8 @@ psa_status_t psa_asymmetric_verify_rsa(psa_key_type_t key_type, } if ((wolfpsa_check_word32_length(key_buffer_size) != PSA_SUCCESS) || (wolfpsa_check_word32_length(hash_length) != PSA_SUCCESS) || - (wolfpsa_check_word32_length(signature_length) != PSA_SUCCESS)) { + (wolfpsa_check_word32_length(signature_length) != PSA_SUCCESS) || + !wc_psa_rsa_hash_length_ok(alg, hash_length)) { return PSA_ERROR_INVALID_ARGUMENT; } @@ -314,6 +338,10 @@ psa_status_t psa_asymmetric_verify_rsa(psa_key_type_t key_type, wc_FreeRsaKey(&rsa_key); return wc_error_to_psa_status(ret); } + if (signature_length != (size_t)wc_RsaEncryptSize(&rsa_key)) { + wc_FreeRsaKey(&rsa_key); + return PSA_ERROR_INVALID_SIGNATURE; + } /* Get padding type and hash type */ padding = wc_psa_get_rsa_padding(alg); @@ -359,15 +387,8 @@ psa_status_t psa_asymmetric_verify_rsa(psa_key_type_t key_type, wc_FreeRsaKey(&rsa_key); return PSA_ERROR_NOT_SUPPORTED; } - /* Same bound as the sign path: PSA requires the digest to match - * the length of the hash named by the algorithm, and the - * WC_MAX_DIGEST_SIZE term is the buffer precondition for the - * unbounded write wc_EncodeSignature() performs. Accepting a - * mismatched length here would let a DigestInfo whose OID and - * digest length disagree verify successfully. */ - expected_hash_len = PSA_HASH_LENGTH(PSA_ALG_SIGN_GET_HASH(alg)); - if (expected_hash_len == 0 || hash_length != expected_hash_len || - hash_length > WC_MAX_DIGEST_SIZE) { + /* Same buffer precondition as the sign path */ + if (hash_length > WC_MAX_DIGEST_SIZE) { wc_ForceZero(decoded, sizeof(decoded)); wc_FreeRsaKey(&rsa_key); return PSA_ERROR_INVALID_ARGUMENT; @@ -452,13 +473,7 @@ psa_status_t psa_asymmetric_verify_rsa(psa_key_type_t key_type, wc_FreeRsaKey(&rsa_key); if (ret < 0) { - /* The shared mapper sends RSA_PAD_E to PSA_ERROR_INVALID_PADDING for - * the decrypt path. On a verification a failed unpad is a signature - * mismatch, and psa_verify_hash does not define INVALID_PADDING. */ - if (ret == RSA_PAD_E) { - return PSA_ERROR_INVALID_SIGNATURE; - } - return wc_error_to_psa_status(ret); + return wc_psa_rsa_status(ret, PSA_ERROR_INVALID_SIGNATURE); } if ((size_t)ret != hash_length) { @@ -575,7 +590,7 @@ psa_status_t psa_asymmetric_encrypt_rsa(psa_key_type_t key_type, wc_FreeRsaKey(&rsa_key); if (ret < 0) { - return wc_error_to_psa_status(ret); + return wc_psa_rsa_status(ret, PSA_ERROR_INVALID_ARGUMENT); } *output_length = (size_t)ret; @@ -721,7 +736,7 @@ psa_status_t psa_asymmetric_decrypt_rsa(psa_key_type_t key_type, status = PSA_ERROR_BUFFER_TOO_SMALL; } else if (ret < 0) { - status = wc_error_to_psa_status(ret); + status = wc_psa_rsa_status(ret, PSA_ERROR_INVALID_PADDING); } else { if (ret > 0) { diff --git a/test/Makefile b/test/Makefile index 5877f3a6..0d83d94f 100644 --- a/test/Makefile +++ b/test/Makefile @@ -107,6 +107,7 @@ PSA_REGRESSION_TESTS = psa_aead_multipart_test \ psa_ecc_curve_caps_test \ psa_xof_no_backend_test \ psa_ecc_sig_len_test \ + psa_rsa_status_test \ psa_xof_set_context_test \ psa_cipher_inplace_test \ psa_cipher_overlap_test \ diff --git a/test/psa_server/psa_rsa_status_test.c b/test/psa_server/psa_rsa_status_test.c new file mode 100644 index 00000000..bd97e3af --- /dev/null +++ b/test/psa_server/psa_rsa_status_test.c @@ -0,0 +1,200 @@ +/* psa_rsa_status_test.c + * + * RSA operations report a bad input with the status PSA defines for the + * operation: INVALID_SIGNATURE for a signature that does not verify, + * INVALID_PADDING for a ciphertext that does not decrypt, INVALID_ARGUMENT + * for a hash or message of the wrong length. + * + * 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 +#include +#include + +#define KEY_BITS 2048 +#define KEY_BYTES (KEY_BITS / 8) +/* PKCS#1 v1.5 padding needs 11 bytes */ +#define V15_TOO_LONG (KEY_BYTES - 10) + +static int failures; + +static void expect(psa_status_t got, psa_status_t want, const char *what) +{ + if (got != want) { + printf("FAIL: %s: got %d want %d\n", what, (int)got, (int)want); + failures++; + } +} + +static psa_key_id_t import_pair(const uint8_t *der, size_t der_len, + psa_algorithm_t alg) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key = PSA_KEY_ID_NULL; + + psa_set_key_type(&attrs, PSA_KEY_TYPE_RSA_KEY_PAIR); + psa_set_key_usage_flags(&attrs, + PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH | + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&attrs, alg); + expect(psa_import_key(&attrs, der, der_len, &key), PSA_SUCCESS, + "import key pair"); + return key; +} + +static void check_v15_sign(const uint8_t *der, size_t der_len) +{ + const psa_algorithm_t alg = PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_SHA_256); + psa_key_id_t key = import_pair(der, der_len, alg); + uint8_t hash[PSA_HASH_LENGTH(PSA_ALG_SHA_256)]; + uint8_t sig[KEY_BYTES + 1]; + size_t sig_len = 0; + psa_status_t status; + + memset(hash, 0x2a, sizeof(hash)); + memset(sig, 0, sizeof(sig)); + status = psa_sign_hash(key, alg, hash, sizeof(hash), sig, sizeof(sig), + &sig_len); + expect(status, PSA_SUCCESS, "v1.5 sign"); + if (status == PSA_SUCCESS) { + expect(psa_verify_hash(key, alg, hash, sizeof(hash), sig, + sig_len - 1), + PSA_ERROR_INVALID_SIGNATURE, "v1.5 verify, signature short"); + expect(psa_verify_hash(key, alg, hash, sizeof(hash), sig, + sig_len + 1), + PSA_ERROR_INVALID_SIGNATURE, "v1.5 verify, signature long"); + } + psa_destroy_key(key); +} + +static void check_v15_raw(const uint8_t *der, size_t der_len) +{ + const psa_algorithm_t alg = PSA_ALG_RSA_PKCS1V15_SIGN_RAW; + psa_key_id_t key = import_pair(der, der_len, alg); + uint8_t msg[V15_TOO_LONG]; + uint8_t sig[KEY_BYTES]; + size_t sig_len = 0; + + memset(msg, 0x5c, sizeof(msg)); + expect(psa_sign_hash(key, alg, msg, sizeof(msg), sig, sizeof(sig), + &sig_len), + PSA_ERROR_INVALID_ARGUMENT, "raw v1.5 sign, input too long"); + psa_destroy_key(key); +} + +static void check_pss(const uint8_t *der, size_t der_len) +{ + const psa_algorithm_t alg = PSA_ALG_RSA_PSS(PSA_ALG_SHA_256); + psa_key_id_t key = import_pair(der, der_len, alg); + uint8_t hash[PSA_HASH_LENGTH(PSA_ALG_SHA_256)]; + uint8_t sig[KEY_BYTES]; + size_t sig_len = 0; + psa_status_t status; + + memset(hash, 0x2a, sizeof(hash)); + memset(sig, 0, sizeof(sig)); + expect(psa_sign_hash(key, alg, hash, sizeof(hash) - 1, sig, sizeof(sig), + &sig_len), + PSA_ERROR_INVALID_ARGUMENT, "PSS sign, hash short"); + expect(psa_verify_hash(key, alg, hash, sizeof(hash) - 1, sig, + sizeof(sig)), + PSA_ERROR_INVALID_ARGUMENT, "PSS verify, hash short"); + + status = psa_sign_hash(key, alg, hash, sizeof(hash), sig, sizeof(sig), + &sig_len); + expect(status, PSA_SUCCESS, "PSS sign"); + if (status == PSA_SUCCESS) { + sig[sig_len - 1] ^= 0x01; + expect(psa_verify_hash(key, alg, hash, sizeof(hash), sig, sig_len), + PSA_ERROR_INVALID_SIGNATURE, "PSS verify, signature changed"); + } + psa_destroy_key(key); +} + +static void check_encrypt(const uint8_t *der, size_t der_len) +{ + const psa_algorithm_t v15 = PSA_ALG_RSA_PKCS1V15_CRYPT; + const psa_algorithm_t oaep = PSA_ALG_RSA_OAEP(PSA_ALG_SHA_256); + static const uint8_t label[] = { 'a', 'b', 'c' }; + psa_key_id_t key = import_pair(der, der_len, v15); + uint8_t msg[V15_TOO_LONG]; + uint8_t out[KEY_BYTES]; + size_t out_len = 0; + uint8_t plain[KEY_BYTES]; + size_t plain_len = 0; + psa_status_t status; + + memset(msg, 0x5c, sizeof(msg)); + expect(psa_asymmetric_encrypt(key, v15, msg, sizeof(msg), NULL, 0, out, + sizeof(out), &out_len), + PSA_ERROR_INVALID_ARGUMENT, "v1.5 encrypt, input too long"); + psa_destroy_key(key); + + key = import_pair(der, der_len, oaep); + status = psa_asymmetric_encrypt(key, oaep, msg, 16, label, sizeof(label), + out, sizeof(out), &out_len); + expect(status, PSA_SUCCESS, "OAEP encrypt"); + if (status == PSA_SUCCESS) { + expect(psa_asymmetric_decrypt(key, oaep, out, out_len, NULL, 0, + plain, sizeof(plain), &plain_len), + PSA_ERROR_INVALID_PADDING, "OAEP decrypt, wrong label"); + } + psa_destroy_key(key); +} + +int main(void) +{ + psa_key_attributes_t attrs = psa_key_attributes_init(); + psa_key_id_t key = PSA_KEY_ID_NULL; + static uint8_t der[PSA_KEY_EXPORT_RSA_KEY_PAIR_MAX_SIZE(KEY_BITS)]; + size_t der_len = 0; + psa_status_t status; + + status = psa_crypto_init(); + expect(status, PSA_SUCCESS, "psa_crypto_init"); + if (status != PSA_SUCCESS) { + return 1; + } + + psa_set_key_type(&attrs, PSA_KEY_TYPE_RSA_KEY_PAIR); + psa_set_key_bits(&attrs, KEY_BITS); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_EXPORT); + status = psa_generate_key(&attrs, &key); + if (status == PSA_SUCCESS) { + status = psa_export_key(key, der, sizeof(der), &der_len); + } + psa_destroy_key(key); + expect(status, PSA_SUCCESS, "generate and export key pair"); + if (status != PSA_SUCCESS) { + return 1; + } + + check_v15_sign(der, der_len); + check_v15_raw(der, der_len); + check_pss(der, der_len); + check_encrypt(der, der_len); + + if (failures == 0) { + printf("RSA status tests: all passed\n"); + return 0; + } + printf("RSA status tests: %d failure(s)\n", failures); + return 1; +}