From 7ccee7dd32d5a5aac357bcda068ea622833d8dfa Mon Sep 17 00:00:00 2001 From: Sameeh Jubran Date: Mon, 2 Feb 2026 11:30:20 +0200 Subject: [PATCH 1/2] pkcs7: add RSA-PSS support for SignedData Add full RSA-PSS (RSASSA-PSS) support to PKCS#7 SignedData encoding and verification. This change enables SignerInfo.signatureAlgorithm to use id-RSASSA-PSS with explicit RSASSA-PSS-params (hash, MGF1, salt length), as required by RFC 4055 and CMS profiles. Key changes: - Add RSA-PSS encode and verify paths for PKCS7 SignedData - Encode full RSASSA-PSS AlgorithmIdentifier parameters - Decode RSA-PSS parameters from SignerInfo for verification - Treat RSA-PSS like ECDSA (sign raw digest, not DigestInfo) - Fix certificate signatureAlgorithm parameter length handling - Add API test coverage for RSA-PSS SignedData This resolves failures when using RSA-PSS signer certificates (e.g. -173 invalid signature algorithm) and maintains backward compatibility with RSA PKCS#1 v1.5 and ECDSA. Signed-off-by: Sameeh Jubran --- .github/workflows/os-check.yml | 2 + doc/dox_comments/header_files/cryptocb.h | 12 + doc/dox_comments/header_files/doxygen_pages.h | 10 + doc/dox_comments/header_files/pkcs7.h | 2 +- examples/configs/README.md | 2 +- examples/configs/user_settings_pkcs7.h | 1 + tests/api/test_pkcs7.c | 91 ++++ tests/api/test_pkcs7.h | 13 + wolfcrypt/src/aes.c | 8 + wolfcrypt/src/asn.c | 364 ++++++++----- wolfcrypt/src/pkcs7.c | 478 +++++++++++++++++- wolfssl/wolfcrypt/asn.h | 6 + wolfssl/wolfcrypt/pkcs7.h | 8 + 13 files changed, 866 insertions(+), 131 deletions(-) diff --git a/.github/workflows/os-check.yml b/.github/workflows/os-check.yml index 763716b027a..724fa8191db 100644 --- a/.github/workflows/os-check.yml +++ b/.github/workflows/os-check.yml @@ -53,6 +53,8 @@ jobs: '--enable-opensslall --enable-opensslextra CPPFLAGS=-DWC_RNG_SEED_CB', '--enable-opensslall --enable-opensslextra CPPFLAGS=''-DWC_RNG_SEED_CB -DWOLFSSL_NO_GETPID'' ', + # PKCS#7 with RSA-PSS (CMS RSASSA-PSS signers) + '--enable-pkcs7 CPPFLAGS=-DWC_RSA_PSS', '--enable-opensslextra CPPFLAGS=''-DWOLFSSL_NO_CA_NAMES'' ', '--enable-opensslextra=x509small', 'CPPFLAGS=''-DWOLFSSL_EXTRA'' ', diff --git a/doc/dox_comments/header_files/cryptocb.h b/doc/dox_comments/header_files/cryptocb.h index b6a21922f3f..aefd733996c 100644 --- a/doc/dox_comments/header_files/cryptocb.h +++ b/doc/dox_comments/header_files/cryptocb.h @@ -52,6 +52,18 @@ } } #endif + #if defined(WC_RSA_PSS) && !defined(NO_RSA) + if (info->pk.type == WC_PK_TYPE_RSA_PSS) { + /* RSA-PSS sign/verify (e.g. PKCS#7 SignedData, X.509). + * Uses info->pk.rsa (in/inLen = digest, out/outLen = signature, + * key, rng). With WOLF_CRYPTO_CB_RSA_PAD, info->pk.rsa.padding + * supplies hash and salt length. */ + ret = wc_RsaFunction(info->pk.rsa.in, info->pk.rsa.inLen, + info->pk.rsa.out, info->pk.rsa.outLen, info->pk.rsa.type, + info->pk.rsa.key, info->pk.rsa.rng); + break; + } + #endif #ifdef HAVE_ECC if (info->pk.type == WC_PK_TYPE_ECDSA_SIGN) { // ECDSA diff --git a/doc/dox_comments/header_files/doxygen_pages.h b/doc/dox_comments/header_files/doxygen_pages.h index 58ae75852e0..beee6c62415 100644 --- a/doc/dox_comments/header_files/doxygen_pages.h +++ b/doc/dox_comments/header_files/doxygen_pages.h @@ -51,6 +51,7 @@
  • \ref MD5
  • \ref Password
  • \ref PKCS7
  • +
  • \ref PKCS7_RSA_PSS
  • \ref PKCS11
  • \ref Poly1305
  • \ref RIPEMD
  • @@ -97,4 +98,13 @@ \sa wc_CryptoCb_AesSetKey \sa \ref Crypto Callbacks */ +/*! + \page PKCS7_RSA_PSS PKCS#7 RSA-PSS (CMS) + PKCS#7 SignedData supports RSA-PSS signers (CMS RSASSA-PSS). When WC_RSA_PSS + is defined, use wc_PKCS7_InitWithCert with a signer certificate that has + RSA-PSS (id-RSASSA-PSS) and set hashOID and optional rng; encode produces + full RSASSA-PSS-params (hashAlgorithm, mgfAlgorithm, saltLength, + trailerField). Verify accepts NULL, empty, or absent parameters with + RFC defaults. See \ref PKCS7 for the main API. +*/ diff --git a/doc/dox_comments/header_files/pkcs7.h b/doc/dox_comments/header_files/pkcs7.h index 69d927c6925..d7f679fafd2 100644 --- a/doc/dox_comments/header_files/pkcs7.h +++ b/doc/dox_comments/header_files/pkcs7.h @@ -147,7 +147,7 @@ int wc_PKCS7_EncodeData(wc_PKCS7* pkcs7, byte* output, \brief This function builds the PKCS7 signed data content type, encoding the PKCS7 structure into a buffer containing a parsable PKCS7 - signed data packet. + signed data packet. For RSA-PSS signers (WC_RSA_PSS), see \ref PKCS7_RSA_PSS. \return Success On successfully encoding the PKCS7 data into the buffer, returns the index parsed up to in the PKCS7 structure. This index also diff --git a/examples/configs/README.md b/examples/configs/README.md index bb876539531..0a2f3193fa3 100644 --- a/examples/configs/README.md +++ b/examples/configs/README.md @@ -23,7 +23,7 @@ Example wolfSSL configuration file templates for use when autoconf is not availa * `user_settings_openssl_compat.h`: OpenSSL compatibility layer for drop-in replacement. Enables OPENSSL_ALL and related APIs. * `user_settings_baremetal.h`: Bare metal configuration. No filesystem, static memory only, minimal footprint. * `user_settings_rsa_only.h`: RSA-only configuration (no ECC). For legacy systems requiring RSA cipher suites. -* `user_settings_pkcs7.h`: PKCS#7/CMS configuration for signing and encryption. S/MIME, firmware signing. +* `user_settings_pkcs7.h`: PKCS#7/CMS configuration for signing and encryption. S/MIME, firmware signing. For RSA-PSS SignedData (CMS RSASSA-PSS), define `WC_RSA_PSS`; see doxygen \ref PKCS7_RSA_PSS. * `user_settings_ca.h`: Certificate Authority / PKI operations. Certificate generation, signing, CRL, OCSP. * `user_settings_wolfboot_keytools.h`: wolfBoot key generation and signing tool. Supports ECC, RSA, ED25519, ED448, and post-quantum (ML-DSA/Dilithium, LMS, XMSS). * `user_settings_wolfssh.h`: Minimum options for building wolfSSH. See comment at top for ./configure used to generate. diff --git a/examples/configs/user_settings_pkcs7.h b/examples/configs/user_settings_pkcs7.h index 1d93f215d68..a7cf51665f6 100644 --- a/examples/configs/user_settings_pkcs7.h +++ b/examples/configs/user_settings_pkcs7.h @@ -115,6 +115,7 @@ extern "C" { #undef NO_RSA #define WOLFSSL_KEY_GEN #define WC_RSA_NO_PADDING + #define WC_RSA_PSS /* RSA-PSS SignedData (id-RSASSA-PSS); see PKCS7_RSA_PSS */ #else #define NO_RSA #endif diff --git a/tests/api/test_pkcs7.c b/tests/api/test_pkcs7.c index eeaeec1cf68..2d6e3a23272 100644 --- a/tests/api/test_pkcs7.c +++ b/tests/api/test_pkcs7.c @@ -947,6 +947,97 @@ int test_wc_PKCS7_EncodeSignedData(void) } /* END test_wc_PKCS7_EncodeSignedData */ +/* + * Testing wc_PKCS7_EncodeSignedData() with RSA-PSS signer certificate. + * Uses certs/rsapss/client-rsapss.der and client-rsapss-priv.der. + * Requires both encode and round-trip verify to succeed. + */ +#if defined(HAVE_PKCS7) && defined(WC_RSA_PSS) && !defined(NO_RSA) && \ + !defined(NO_FILESYSTEM) && !defined(NO_SHA256) +int test_wc_PKCS7_EncodeSignedData_RSA_PSS(void) +{ + EXPECT_DECLS; + PKCS7* pkcs7 = NULL; + WC_RNG rng; + byte output[FOURK_BUF]; + byte cert[FOURK_BUF]; + byte key[FOURK_BUF]; + word32 outputSz = (word32)sizeof(output); + word32 certSz = 0; + word32 keySz = 0; + XFILE fp = XBADFILE; + byte data[] = "Test data for RSA-PSS SignedData."; + + XMEMSET(&rng, 0, sizeof(WC_RNG)); + XMEMSET(output, 0, outputSz); + XMEMSET(cert, 0, sizeof(cert)); + XMEMSET(key, 0, sizeof(key)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_Init(pkcs7, HEAP_HINT, INVALID_DEVID), 0); + + ExpectTrue((fp = XFOPEN("./certs/rsapss/client-rsapss.der", "rb")) != XBADFILE); + ExpectIntGT(certSz = (word32)XFREAD(cert, 1, sizeof(cert), fp), 0); + if (fp != XBADFILE) { + XFCLOSE(fp); + fp = XBADFILE; + } + + ExpectTrue((fp = XFOPEN("./certs/rsapss/client-rsapss-priv.der", "rb")) != XBADFILE); + ExpectIntGT(keySz = (word32)XFREAD(key, 1, sizeof(key), fp), 0); + if (fp != XBADFILE) + XFCLOSE(fp); + + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, cert, certSz), 0); + + if (pkcs7 != NULL) { + /* Force RSA-PSS so SignerInfo uses id-RSASSA-PSS (cert may use RSA + * in subjectPublicKeyInfo). WC_RSA_PSS is guaranteed by outer guard. */ + pkcs7->publicKeyOID = RSAPSSk; + + pkcs7->content = data; + pkcs7->contentSz = (word32)sizeof(data); + pkcs7->contentOID = DATA; + pkcs7->hashOID = SHA256h; + pkcs7->encryptOID = RSAk; + pkcs7->privateKey = key; + pkcs7->privateKeySz = keySz; + pkcs7->rng = &rng; + pkcs7->signedAttribs = NULL; + pkcs7->signedAttribsSz = 0; + } + + /* EncodeSignedData with RSA-PSS cert: require encode and verify success */ + { + int outLen = wc_PKCS7_EncodeSignedData(pkcs7, output, outputSz); + ExpectIntGT(outLen, 0); + if (outLen > 0) { + int verifyRet = wc_PKCS7_VerifySignedData(pkcs7, output, + (word32)outLen); + ExpectIntEQ(verifyRet, 0); + + if (pkcs7 != NULL) { + /* Verify decoded RSASSA-PSS parameters match what we + * encoded: + * hashAlgorithm = SHA-256 + * maskGenAlgorithm = MGF1-SHA-256 + * saltLength = 32 (== SHA-256 digest length) */ + ExpectIntEQ(pkcs7->pssHashType, (int)WC_HASH_TYPE_SHA256); + ExpectIntEQ(pkcs7->pssMgf, WC_MGF1SHA256); + ExpectIntEQ(pkcs7->pssSaltLen, 32); + } + } + } + + wc_PKCS7_Free(pkcs7); + DoExpectIntEQ(wc_FreeRng(&rng), 0); + + return EXPECT_RESULT(); +} /* END test_wc_PKCS7_EncodeSignedData_RSA_PSS */ +#endif + + /* * Testing wc_PKCS7_EncodeSignedData_ex() and wc_PKCS7_VerifySignedData_ex() */ diff --git a/tests/api/test_pkcs7.h b/tests/api/test_pkcs7.h index accd348cebc..51ffe8ba2a0 100644 --- a/tests/api/test_pkcs7.h +++ b/tests/api/test_pkcs7.h @@ -29,6 +29,10 @@ int test_wc_PKCS7_Init(void); int test_wc_PKCS7_InitWithCert(void); int test_wc_PKCS7_EncodeData(void); int test_wc_PKCS7_EncodeSignedData(void); +#if defined(HAVE_PKCS7) && defined(WC_RSA_PSS) && !defined(NO_RSA) && \ + !defined(NO_FILESYSTEM) && !defined(NO_SHA256) +int test_wc_PKCS7_EncodeSignedData_RSA_PSS(void); +#endif int test_wc_PKCS7_EncodeSignedData_ex(void); int test_wc_PKCS7_VerifySignedData_RSA(void); int test_wc_PKCS7_VerifySignedData_ECC(void); @@ -55,10 +59,19 @@ int test_wc_PKCS7_VerifySignedData_PKCS7ContentSeq(void); TEST_DECL_GROUP("pkcs7", test_wc_PKCS7_New), \ TEST_DECL_GROUP("pkcs7", test_wc_PKCS7_Init) +#if defined(HAVE_PKCS7) && defined(WC_RSA_PSS) && !defined(NO_RSA) && \ + !defined(NO_FILESYSTEM) && !defined(NO_SHA256) +#define TEST_PKCS7_RSA_PSS_SD_DECL \ + TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeSignedData_RSA_PSS), +#else +#define TEST_PKCS7_RSA_PSS_SD_DECL +#endif + #define TEST_PKCS7_SIGNED_DATA_DECLS \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_InitWithCert), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeData), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeSignedData), \ + TEST_PKCS7_RSA_PSS_SD_DECL \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeSignedData_ex), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_RSA), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_ECC), \ diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index f9ee50eb093..1a1ccc50b83 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -47,6 +47,11 @@ block cipher mechanism that uses n-bit binary string parameter key with 128-bits #include +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wlanguage-extension-token" +#endif + #ifdef WOLFSSL_AESNI #include #include @@ -17147,5 +17152,8 @@ int wc_AesCtsDecryptFinal(Aes* aes, byte* out, word32* outSz) #endif /* WOLFSSL_AES_CTS */ +#if defined(__clang__) +#pragma clang diagnostic pop +#endif #endif /* !NO_AES */ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index ee97f77b8d7..597ce35d5d1 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -2539,7 +2539,7 @@ int GetASNHeader(const byte* input, byte tag, word32* inOutIdx, int* len, return GetASNHeader_ex(input, tag, inOutIdx, len, maxIdx, 1); } -#ifndef WOLFSSL_ASN_TEMPLATE +#if !defined(WOLFSSL_ASN_TEMPLATE) || (defined(WC_RSA_PSS) && !defined(NO_RSA)) static int GetHeader(const byte* input, byte* tag, word32* inOutIdx, int* len, word32 maxIdx, int check) { @@ -2894,7 +2894,11 @@ static int GetInteger7Bit(const byte* input, word32* inOutIdx, word32 maxIdx) return b; } #endif /* !NO_CERTS */ +#endif /* !WOLFSSL_ASN_TEMPLATE */ +/* GetInteger16Bit: available in non-template builds (for cert parsing) and + * also in template builds when WC_RSA_PSS is enabled (for DecodeRsaPssParams). */ +#if !defined(WOLFSSL_ASN_TEMPLATE) || (defined(WC_RSA_PSS) && !defined(NO_RSA)) #if ((defined(WC_RSA_PSS) && !defined(NO_RSA)) || !defined(NO_CERTS)) /* Get the DER/BER encoding of an ASN.1 INTEGER that has a value of no more than * 16 bits. @@ -2932,7 +2936,7 @@ static int GetInteger16Bit(const byte* input, word32* inOutIdx, word32 maxIdx) return ASN_PARSE_E; } n = input[idx++]; - n = (n << 8) | input[idx++]; + n = (word16)((n << 8) | input[idx++]); } else return ASN_PARSE_E; @@ -2940,8 +2944,8 @@ static int GetInteger16Bit(const byte* input, word32* inOutIdx, word32 maxIdx) *inOutIdx = idx; return n; } -#endif /* WC_RSA_PSS && !NO_RSA */ -#endif /* !WOLFSSL_ASN_TEMPLATE */ +#endif /* WC_RSA_PSS || !NO_CERTS */ +#endif /* !WOLFSSL_ASN_TEMPLATE || WC_RSA_PSS */ #if !defined(NO_DSA) && !defined(NO_SHA) static const char sigSha1wDsaName[] = "SHAwDSA"; @@ -3132,10 +3136,12 @@ const char* GetSigName(int oid) { #if !defined(WOLFSSL_ASN_TEMPLATE) || defined(HAVE_PKCS7) || \ - defined(OPENSSL_EXTRA) || defined(WOLFSSL_CERT_GEN) + defined(OPENSSL_EXTRA) || defined(WOLFSSL_CERT_GEN) || defined(WC_RSA_PSS) #if !defined(NO_DSA) || defined(HAVE_ECC) || !defined(NO_CERTS) || \ defined(WOLFSSL_CERT_GEN) || \ - (!defined(NO_RSA) && (defined(WOLFSSL_KEY_GEN) || defined(OPENSSL_EXTRA))) + (!defined(NO_RSA) && \ + (defined(WOLFSSL_KEY_GEN) || defined(OPENSSL_EXTRA))) || \ + (defined(WC_RSA_PSS) && !defined(NO_RSA)) /* Set the DER/BER encoding of the ASN.1 INTEGER header. * * When output is NULL, calculate the header length only. @@ -3146,7 +3152,7 @@ const char* GetSigName(int oid) { * @param [out] output Buffer to write into. * @return Number of bytes added to the buffer. */ -int SetASNInt(int len, byte firstByte, byte* output) +WOLFSSL_LOCAL int SetASNInt(int len, byte firstByte, byte* output) { int idx = 0; @@ -7452,80 +7458,26 @@ static int RsaPssHashOidToSigOid(word32 oid, word32* sigOid) } #endif -#ifdef WOLFSSL_ASN_TEMPLATE -/* ASN tag for hashAlgorithm. */ -#define ASN_TAG_RSA_PSS_HASH (ASN_CONTEXT_SPECIFIC | 0) -/* ASN tag for maskGenAlgorithm. */ -#define ASN_TAG_RSA_PSS_MGF (ASN_CONTEXT_SPECIFIC | 1) -/* ASN tag for saltLength. */ -#define ASN_TAG_RSA_PSS_SALTLEN (ASN_CONTEXT_SPECIFIC | 2) -/* ASN tag for trailerField. */ -#define ASN_TAG_RSA_PSS_TRAILER (ASN_CONTEXT_SPECIFIC | 3) - -/* ASN.1 template for RSA PSS parameters. */ -static const ASNItem rsaPssParamsASN[] = { -/* SEQ */ { 0, ASN_SEQUENCE, 1, 1, 0 }, -/* HASH */ { 1, ASN_TAG_RSA_PSS_HASH, 1, 1, 1 }, -/* HASHSEQ */ { 2, ASN_SEQUENCE, 1, 1, 0 }, -/* HASHOID */ { 3, ASN_OBJECT_ID, 0, 0, 0 }, -/* HASHNULL */ { 3, ASN_TAG_NULL, 0, 0, 1 }, -/* MGF */ { 1, ASN_TAG_RSA_PSS_MGF, 1, 1, 1 }, -/* MGFSEQ */ { 2, ASN_SEQUENCE, 1, 1, 0 }, -/* MGFOID */ { 3, ASN_OBJECT_ID, 0, 0, 0 }, -/* MGFPARAM */ { 3, ASN_SEQUENCE, 1, 1, 0 }, -/* MGFHOID */ { 4, ASN_OBJECT_ID, 0, 0, 0 }, -/* MGFHNULL */ { 4, ASN_TAG_NULL, 0, 0, 1 }, -/* SALTLEN */ { 1, ASN_TAG_RSA_PSS_SALTLEN, 1, 1, 1 }, -/* SALTLENINT */ { 2, ASN_INTEGER, 0, 0, 0 }, -/* TRAILER */ { 1, ASN_TAG_RSA_PSS_TRAILER, 1, 1, 1 }, -/* TRAILERINT */ { 2, ASN_INTEGER, 0, 0, 0 }, -}; -enum { - RSAPSSPARAMSASN_IDX_SEQ = 0, - RSAPSSPARAMSASN_IDX_HASH, - RSAPSSPARAMSASN_IDX_HASHSEQ, - RSAPSSPARAMSASN_IDX_HASHOID, - RSAPSSPARAMSASN_IDX_HASHNULL, - RSAPSSPARAMSASN_IDX_MGF, - RSAPSSPARAMSASN_IDX_MGFSEQ, - RSAPSSPARAMSASN_IDX_MGFOID, - RSAPSSPARAMSASN_IDX_MGFPARAM, - RSAPSSPARAMSASN_IDX_MGFHOID, - RSAPSSPARAMSASN_IDX_MGFHNULL, - RSAPSSPARAMSASN_IDX_SALTLEN, - RSAPSSPARAMSASN_IDX_SALTLENINT, - RSAPSSPARAMSASN_IDX_TRAILER, - RSAPSSPARAMSASN_IDX_TRAILERINT -}; - -/* Number of items in ASN.1 template for an algorithm identifier. */ -#define rsaPssParamsASN_Length (sizeof(rsaPssParamsASN) / sizeof(ASNItem)) -#else -/* ASN tag for hashAlgorithm. */ -#define ASN_TAG_RSA_PSS_HASH (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 0) -/* ASN tag for maskGenAlgorithm. */ -#define ASN_TAG_RSA_PSS_MGF (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 1) -/* ASN tag for saltLength. */ -#define ASN_TAG_RSA_PSS_SALTLEN (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 2) -/* ASN tag for trailerField. */ -#define ASN_TAG_RSA_PSS_TRAILER (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 3) -#endif +/* RSASSA-PSS-params EXPLICIT context tags (constructed, per X.680/X.690). + * These are the on-wire tag bytes for [0]..[3] in the SEQUENCE. */ +#define ASN_TAG_RSA_PSS_HASH (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 0) +#define ASN_TAG_RSA_PSS_MGF (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 1) +#define ASN_TAG_RSA_PSS_SALTLEN (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 2) +#define ASN_TAG_RSA_PSS_TRAILER (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 3) /* Decode the RSA PSS parameters. * - * @param [in] params Buffer holding BER encoded RSA PSS parameters. - * @param [in] sz Size of data in buffer in bytes. - * @param [out] hash Hash algorithm to use on message. - * @param [out] mgf MGF algorithm to use with PSS padding. - * @param [out] saltLen Length of salt in PSS padding. - * @return BAD_FUNC_ARG when the params is NULL. - * @return ASN_PARSE_E when the decoding fails. - * @return 0 on success. + * Uses manual (non-template) ASN.1 parsing for both WOLFSSL_ASN_TEMPLATE and + * non-template builds. The ASN template approach (rsaPssParamsASN / + * GetASN_Items) was removed because the EXPLICIT constructed context tags + * [0]..[3] in RSASSA-PSS-params interact poorly with the template engine's + * implicit-tag assumptions, causing mis-parses on real-world certificates. + * The manual parser below handles all tag forms correctly and has been + * validated against OpenSSL-generated and wolfSSL-generated RSA-PSS blobs. */ static int DecodeRsaPssParams(const byte* params, word32 sz, enum wc_HashType* hash, int* mgf, int* saltLen) { -#ifndef WOLFSSL_ASN_TEMPLATE int ret = 0; word32 idx = 0; int len = 0; @@ -7533,25 +7485,59 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, byte tag; int length; + /* params points to AlgorithmIdentifier.parameters TLV (NULL or SEQUENCE). */ + WOLFSSL_MSG_EX("DecodeRsaPssParams: enter, sz=%u", sz); if (params == NULL) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at params_null"); ret = BAD_FUNC_ARG; } + if ((ret == 0) && (sz == 0)) { + WOLFSSL_MSG("DecodeRsaPssParams: sz=0, returning defaults"); + *hash = WC_HASH_TYPE_SHA; + *mgf = WC_MGF1SHA1; + *saltLen = 20; + return 0; + } + if (ret == 0 && params[0] == ASN_TAG_NULL) { + word32 tIdx = 0; + byte tTag; + int tLen; + if (GetHeader(params, &tTag, &tIdx, &tLen, sz, 0) >= 0 && tLen == 0) { + *hash = WC_HASH_TYPE_SHA; + *mgf = WC_MGF1SHA1; + *saltLen = 20; + return 0; /* defaults */ + } + return ASN_PARSE_E; + } + if ((ret == 0) && (params[0] != (ASN_SEQUENCE | ASN_CONSTRUCTED))) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at sequence"); + return ASN_PARSE_E; + } + /* Decode RSASSA-PSS-params SEQUENCE content. */ if ((ret == 0) && (GetSequence_ex(params, &idx, &len, sz, 1) < 0)) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at sequence"); ret = ASN_PARSE_E; } + + /* [0] hashAlgorithm */ if (ret == 0) { if ((idx < sz) && (params[idx] == ASN_TAG_RSA_PSS_HASH)) { /* Hash algorithm to use on message. */ if (GetHeader(params, &tag, &idx, &length, sz, 0) < 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at hash_header"); ret = ASN_PARSE_E; } if (ret == 0) { if (GetAlgoId(params, &idx, &oid, oidHashType, sz) < 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at hash_algo"); ret = ASN_PARSE_E; } } if (ret == 0) { ret = RsaPssHashOidToType(oid, hash); + if (ret != 0) + WOLFSSL_MSG("DecodeRsaPssParams: fail at hash_oid"); } } else { @@ -7559,36 +7545,68 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, *hash = WC_HASH_TYPE_SHA; } } + + /* [1] maskGenAlgorithm -- AlgorithmIdentifier { OID id-mgf1, hash AlgoId } + * Parse manually: read the MGF SEQUENCE + OID, then the hash AlgoId + * parameter, because GetAlgoId (in template builds) consumes the entire + * AlgorithmIdentifier including the hash parameter inside it. */ if (ret == 0) { if ((idx < sz) && (params[idx] == ASN_TAG_RSA_PSS_MGF)) { - /* MGF and hash algorithm to use with padding. */ + int mgfSeqLen = 0; + + WOLFSSL_MSG_EX("DecodeRsaPssParams: found MGF tag 0x%02x at idx %u", + params[idx], idx); if (GetHeader(params, &tag, &idx, &length, sz, 0) < 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at mgf_header"); ret = ASN_PARSE_E; } + /* Read MGF AlgorithmIdentifier SEQUENCE header */ + if (ret == 0) { + if (GetSequence(params, &idx, &mgfSeqLen, sz) < 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at mgf_seq"); + ret = ASN_PARSE_E; + } + } + /* Read MGF OID (id-mgf1) directly */ if (ret == 0) { - if (GetAlgoId(params, &idx, &oid, oidIgnoreType, sz) < 0) { + if (GetObjectId(params, &idx, &oid, oidIgnoreType, sz) < 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at mgf_oid"); ret = ASN_PARSE_E; } } if ((ret == 0) && (oid != MGF1_OID)) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at mgf_oid_value"); ret = ASN_PARSE_E; } + /* Read hash AlgorithmIdentifier (parameter of MGF1) */ if (ret == 0) { ret = GetAlgoId(params, &idx, &oid, oidHashType, sz); if (ret == 0) { ret = RsaPssHashOidToMgf1(oid, mgf); + if (ret != 0) + WOLFSSL_MSG("DecodeRsaPssParams: fail at mgf_hash_oid"); } + else + WOLFSSL_MSG("DecodeRsaPssParams: fail at mgf_hash_algo"); } } else { /* Default MGF/Hash algorithm. */ + if (idx < sz) { + WOLFSSL_MSG_EX("DecodeRsaPssParams: no MGF tag, got 0x%02x at" + " idx %u, using default MGF1-SHA1", + params[idx], idx); + } *mgf = WC_MGF1SHA1; } } + + /* [2] saltLength */ if (ret == 0) { if ((idx < sz) && (params[idx] == ASN_TAG_RSA_PSS_SALTLEN)) { /* Salt length to use with padding. */ if (GetHeader(params, &tag, &idx, &length, sz, 0) < 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at saltlen_header"); ret = ASN_PARSE_E; } if (ret == 0) { @@ -7597,6 +7615,8 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, *saltLen = ret; ret = 0; } + else + WOLFSSL_MSG("DecodeRsaPssParams: fail at saltlen_value"); } } else { @@ -7604,10 +7624,13 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, *saltLen = 20; } } + + /* [3] trailerField */ if (ret == 0) { if ((idx < sz) && (params[idx] == ASN_TAG_RSA_PSS_TRAILER)) { - /* Unused - trialerField. */ + /* Unused - trailerField. */ if (GetHeader(params, &tag, &idx, &length, sz, 0) < 0) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at trailer_header"); ret = ASN_PARSE_E; } if (ret == 0) { @@ -7615,55 +7638,144 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, if (ret > 0) { ret = 0; } + else if (ret != 0) + WOLFSSL_MSG("DecodeRsaPssParams: fail at trailer_value"); } } } + if ((ret == 0) && (idx != sz)) { + WOLFSSL_MSG("DecodeRsaPssParams: fail at extra_data"); ret = ASN_PARSE_E; } return ret; -#else - DECL_ASNGETDATA(dataASN, rsaPssParamsASN_Length); - int ret = 0; - word16 sLen = 20; +} - if (params == NULL) { - ret = BAD_FUNC_ARG; +WOLFSSL_LOCAL int wc_DecodeRsaPssParams(const byte* params, word32 sz, + enum wc_HashType* hash, int* mgf, int* saltLen) +{ + return DecodeRsaPssParams(params, sz, hash, mgf, saltLen); +} + +/* Encode AlgorithmIdentifier for id-RSASSA-PSS with full RSASSA-PSS-params. + * hashOID: e.g. SHA256h; saltLen: e.g. 32; trailerField is encoded as 1. + * When out is NULL returns required length only. + * Returns encoded length on success, 0 on error. + * Note: saltLength is encoded as a single-byte INTEGER; values >255 would + * require multi-byte encoding (not implemented; CMS profiles use hash-length + * salts, e.g. 20-64 bytes). */ +WOLFSSL_LOCAL word32 wc_EncodeRsaPssAlgoId(int hashOID, int saltLen, byte* out, word32 outSz) +{ + word32 idx = 0; + word32 hashAlgSz, mgf1ParamSz, tag0Sz, tag1Sz, tag2Sz, tag3Sz; + word32 paramsSz, outerSz; + const byte* rsapssOid = sigRsaSsaPssOid; + word32 rsapssOidSz = sizeof(sigRsaSsaPssOid); + /* MGF1 OID 1.2.840.113549.1.1.8 */ + static const byte mgf1Oid[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x08 }; + byte tmpBuf[128]; + int setIntRet; + + if (saltLen < 0 || saltLen > 255) { + WOLFSSL_MSG("Salt length must be 0-255 for single-byte encoding"); + return 0; } - CALLOC_ASNGETDATA(dataASN, rsaPssParamsASN_Length, ret, NULL); - if (ret == 0) { - word32 inOutIdx = 0; - /* Default values. */ - *hash = WC_HASH_TYPE_SHA; - *mgf = WC_MGF1SHA1; + hashAlgSz = SetAlgoID(hashOID, out ? tmpBuf : NULL, oidHashType, 0); + if (hashAlgSz == 0) + return 0; + mgf1ParamSz = hashAlgSz; + tag0Sz = SetExplicit(0, hashAlgSz, NULL, 0) + hashAlgSz; + { + /* MGF AlgorithmIdentifier: SEQUENCE { OID id-mgf1, hash AlgoId } + * The hash AlgoId (from SetAlgoID) is the parameter of MGF1. */ + word32 mgf1OidLen = (word32)SetObjectId((int)sizeof(mgf1Oid), NULL) + (word32)sizeof(mgf1Oid); + word32 mgf1SeqContent = mgf1OidLen + mgf1ParamSz; + word32 mgf1SeqSz = SetSequence(mgf1SeqContent, NULL) + mgf1SeqContent; + tag1Sz = SetExplicit(1, mgf1SeqSz, NULL, 0) + mgf1SeqSz; + } + /* SetASNInt writes only tag+length (+ optional leading 0); value byte(s) appended by caller. */ + setIntRet = SetASNInt(1, (byte)(saltLen & 0xff), NULL); + if (setIntRet <= 0) + return 0; + { + word32 saltIntSz = (word32)setIntRet + 1; /* header + one value byte (two if high bit set) */ + tag2Sz = SetExplicit(2, saltIntSz, NULL, 0) + saltIntSz; + } + setIntRet = SetASNInt(1, 0x01, NULL); + if (setIntRet <= 0) + return 0; + { + word32 trailerIntSz = (word32)setIntRet + 1; + tag3Sz = SetExplicit(3, trailerIntSz, NULL, 0) + trailerIntSz; + } - /* Set OID type expected. */ - GetASN_OID(&dataASN[RSAPSSPARAMSASN_IDX_HASHOID], oidHashType); - GetASN_OID(&dataASN[RSAPSSPARAMSASN_IDX_MGFHOID], oidHashType); - /* Place the salt length into 16-bit var sLen. */ - GetASN_Int16Bit(&dataASN[RSAPSSPARAMSASN_IDX_SALTLENINT], &sLen); - /* Decode the algorithm identifier. */ - ret = GetASN_Items(rsaPssParamsASN, dataASN, rsaPssParamsASN_Length, 1, - params, &inOutIdx, sz); + paramsSz = tag0Sz + tag1Sz + tag2Sz + tag3Sz; + { + word32 idPart = (word32)SetObjectId((int)rsapssOidSz, NULL) + rsapssOidSz; + word32 seqPart = SetSequence(paramsSz, NULL) + paramsSz; + outerSz = SetSequence(idPart + seqPart, NULL) + idPart + seqPart; } - if ((ret == 0) && (dataASN[RSAPSSPARAMSASN_IDX_HASHOID].tag != 0)) { - word32 oid = dataASN[RSAPSSPARAMSASN_IDX_HASHOID].data.oid.sum; - ret = RsaPssHashOidToType(oid, hash); + + if (out == NULL) + return outerSz; + if (outSz < outerSz) + return 0; + + { + word32 idPart = (word32)SetObjectId((int)rsapssOidSz, NULL) + rsapssOidSz; + word32 seqPart = SetSequence(paramsSz, NULL) + paramsSz; + idx += SetSequence(idPart + seqPart, out + idx); } - if ((ret == 0) && (dataASN[RSAPSSPARAMSASN_IDX_MGFHOID].tag != 0)) { - word32 oid = dataASN[RSAPSSPARAMSASN_IDX_MGFHOID].data.oid.sum; - ret = RsaPssHashOidToMgf1(oid, mgf); + idx += (word32)SetObjectId((int)rsapssOidSz, out + idx); + XMEMCPY(out + idx, rsapssOid, rsapssOidSz); + idx += rsapssOidSz; + idx += SetSequence(paramsSz, out + idx); + + idx += SetExplicit(0, hashAlgSz, out + idx, 0); + XMEMCPY(out + idx, tmpBuf, hashAlgSz); + idx += hashAlgSz; + + { + /* [1] EXPLICIT { SEQUENCE { OID id-mgf1, hash AlgoId } } */ + word32 mgf1OidLen = (word32)SetObjectId((int)sizeof(mgf1Oid), NULL) + (word32)sizeof(mgf1Oid); + word32 mgf1SeqContent = mgf1OidLen + mgf1ParamSz; + word32 mgf1SeqSz = SetSequence(mgf1SeqContent, NULL) + mgf1SeqContent; + idx += SetExplicit(1, mgf1SeqSz, out + idx, 0); + idx += SetSequence(mgf1SeqContent, out + idx); + idx += (word32)SetObjectId((int)sizeof(mgf1Oid), out + idx); + XMEMCPY(out + idx, mgf1Oid, sizeof(mgf1Oid)); + idx += (word32)sizeof(mgf1Oid); + XMEMCPY(out + idx, tmpBuf, hashAlgSz); + idx += hashAlgSz; + } + + /* INTEGER saltLength (single byte; see function comment for >255 limitation). */ + setIntRet = SetASNInt(1, (byte)(saltLen & 0xff), tmpBuf); + if (setIntRet > 0) + tmpBuf[setIntRet] = (byte)(saltLen & 0xff); + { + word32 saltIntSz = (word32)setIntRet + 1; + idx += SetExplicit(2, saltIntSz, out + idx, 0); + XMEMCPY(out + idx, tmpBuf, saltIntSz); + idx += saltIntSz; } - if (ret == 0) { - *saltLen = sLen; + + /* INTEGER trailerField (1): same pattern. */ + setIntRet = SetASNInt(1, 0x01, tmpBuf); + if (setIntRet > 0) + tmpBuf[setIntRet] = 0x01; + { + word32 trailerIntSz = (word32)setIntRet + 1; + idx += SetExplicit(3, trailerIntSz, out + idx, 0); + XMEMCPY(out + idx, tmpBuf, trailerIntSz); + idx += trailerIntSz; } - FREE_ASNGETDATA(dataASN, NULL); - return ret; -#endif /* WOLFSSL_ASN_TEMPLATE */ + return idx; } + #endif /* WC_RSA_PSS */ #if defined(WOLFSSL_ASN_TEMPLATE) || (!defined(NO_CERTS) && \ @@ -11109,6 +11221,7 @@ int wc_RsaPublicKeyDecode_ex(const byte* input, word32* inOutIdx, word32 inSz, #else DECL_ASNGETDATA(dataASN, rsaPublicKeyASN_Length); int ret = 0; + word32 startIdx = (inOutIdx != NULL) ? *inOutIdx : 0; #ifdef WC_RSA_PSS word32 oid = RSAk; #endif @@ -11127,7 +11240,10 @@ int wc_RsaPublicKeyDecode_ex(const byte* input, word32* inOutIdx, word32 inSz, (int)(rsaPublicKeyASN_Length - RSAPUBLICKEYASN_IDX_PUBKEY_RSA_SEQ), 0, input, inOutIdx, inSz); if (ret != 0) { - /* Didn't work - try whole SubjectKeyInfo instead. */ + /* Didn't work - try whole SubjectKeyInfo instead. Reset index + * to caller's start since the previous attempt advanced it. */ + if (inOutIdx != NULL) + *inOutIdx = startIdx; #ifdef WC_RSA_PSS /* Could be RSA or RSA PSS key. */ GetASN_OID(&dataASN[RSAPUBLICKEYASN_IDX_ALGOID_OID], oidKeyType); @@ -17037,8 +17153,19 @@ static int GetSigAlg(DecodedCert* cert, word32* sigOid, word32 maxIdx) if (cert->srcIdx != endSeqIdx) { #ifdef WC_RSA_PSS if (*sigOid == CTC_RSASSAPSS) { - cert->sigParamsIndex = cert->srcIdx; - cert->sigParamsLength = endSeqIdx - cert->srcIdx; + /* cert->srcIdx is at start of parameters TLV (NULL or SEQUENCE) */ + word32 tmpIdx = cert->srcIdx; + byte tag; + int len; + +#ifdef DEBUG_WOLFSSL + WOLFSSL_MSG("Cert sigAlg is RSASSA-PSS; decoding params"); +#endif + if (GetHeader(cert->source, &tag, &tmpIdx, &len, endSeqIdx, 0) < 0) { + return ASN_PARSE_E; + } + cert->sigParamsIndex = cert->srcIdx; + cert->sigParamsLength = (word32)((tmpIdx - cert->srcIdx) + len); } else #endif @@ -24077,10 +24204,17 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, } } if (ret == 0) { - /* Store parameters for use in signature verification. */ - cert->sigParamsIndex = - dataASN[X509CERTASN_IDX_SIGALGO_PARAMS].offset; - cert->sigParamsLength = sigAlgParamsSz; + /* Store parameters for use in signature verification. + * Use full TLV length: tag (1) + length bytes + content. + * GetASNItem_Length can be content-only when buffer.data unset. */ + word32 off = dataASN[X509CERTASN_IDX_SIGALGO_PARAMS].offset; + word32 contentLen = + (word32)dataASN[X509CERTASN_IDX_SIGALGO_PARAMS].length; + cert->sigParamsIndex = off; + cert->sigParamsLength = (contentLen <= 127) + ? (2 + contentLen) + : GetASNItem_Length( + dataASN[X509CERTASN_IDX_SIGALGO_PARAMS], cert->source); } } #endif diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index 97d15a39371..635d78ef8a3 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -28,6 +28,23 @@ #ifndef NO_RSA #include #endif +#if defined(WC_RSA_PSS) && !defined(NO_RSA) + /* FIPS v1 / CAVP builds omit these from rsa.h; provide fallbacks */ + #ifndef RSA_PSS_SALT_LEN_DEFAULT + #define RSA_PSS_SALT_LEN_DEFAULT (-1) + #endif + /* MGF1 constants when not from rsa.h (e.g. FIPS v1 reduced header) */ + #ifndef WC_MGF1NONE + #define WC_MGF1NONE 0 + #define WC_MGF1SHA1 26 + #define WC_MGF1SHA224 4 + #define WC_MGF1SHA256 1 + #define WC_MGF1SHA384 2 + #define WC_MGF1SHA512 3 + #define WC_MGF1SHA512_224 5 + #define WC_MGF1SHA512_256 6 + #endif +#endif #ifdef HAVE_ECC #include #endif @@ -812,6 +829,11 @@ int wc_PKCS7_Init(wc_PKCS7* pkcs7, void* heap, int devId) isDynamic = pkcs7->isDynamic; XMEMSET(pkcs7, 0, sizeof(wc_PKCS7)); pkcs7->isDynamic = (isDynamic != 0); +#if defined(WC_RSA_PSS) && !defined(NO_RSA) + pkcs7->pssSaltLen = -1; + pkcs7->pssHashType = -1; + pkcs7->pssMgf = -1; +#endif #ifdef WOLFSSL_HEAP_TEST pkcs7->heap = (void*)WOLFSSL_HEAP_TEST; #else @@ -1019,6 +1041,11 @@ static int wc_PKCS7_CheckPublicKeyDer(wc_PKCS7* pkcs7, int keyOID, switch (keyOID) { #ifndef NO_RSA + #ifdef WC_RSA_PSS + case RSAPSSk: + /* RSA-PSS cert: public key is same RSA format as RSAk */ + FALL_THROUGH; + #endif case RSAk: ret = wc_InitRsaKey_ex(rsa, pkcs7->heap, pkcs7->devId); if (ret != 0) { @@ -1173,6 +1200,10 @@ int wc_PKCS7_InitWithCert(wc_PKCS7* pkcs7, byte* derCert, word32 derCertSz) XMEMCPY(pkcs7->publicKey, dCert->publicKey, dCert->pubKeySize); pkcs7->publicKeySz = dCert->pubKeySize; pkcs7->publicKeyOID = dCert->keyOID; + /* Do not derive publicKeyOID from cert signatureOID: the cert's + * signature is how the cert was signed by its issuer; the signer + * chooses digestEncryptionAlgorithm (e.g. RSASSA-PSS vs PKCS#1 v1.5) + * via API / pkcs7->publicKeyOID set by the application. */ XMEMCPY(pkcs7->issuerHash, dCert->issuerHash, KEYID_SIZE); pkcs7->issuer = dCert->issuerRaw; pkcs7->issuerSz = (word32)dCert->issuerRawLen; @@ -1533,7 +1564,11 @@ typedef struct ESD { byte issuerSKIDSeq[MAX_SEQ_SZ]; byte issuerSKID[MAX_OCTET_STR_SZ]; byte signerDigAlgoId[MAX_ALGO_SZ]; +#if defined(WC_RSA_PSS) + byte digEncAlgoId[128]; /* RSASSA-PSS needs full params */ +#else byte digEncAlgoId[MAX_ALGO_SZ]; +#endif byte signedAttribSet[MAX_SET_SZ]; EncodedAttrib signedAttribs[7]; byte signerDigest[MAX_OCTET_STR_SZ]; @@ -1945,6 +1980,130 @@ static int wc_PKCS7_EcdsaSign(wc_PKCS7* pkcs7, byte* in, word32 inSz, ESD* esd) #endif /* HAVE_ECC */ +#if defined(WC_RSA_PSS) && !defined(NO_RSA) +/* Map hash type to MGF1 identifier; local copy so PKCS7 does not depend on + * rsa.c when RSA is in a separate FIPS module (e.g. CAVP build). */ +static int pkcs7_hash2mgf(enum wc_HashType hType) +{ + switch (hType) { + case WC_HASH_TYPE_NONE: + return WC_MGF1NONE; + case WC_HASH_TYPE_SHA: +#ifndef NO_SHA + return WC_MGF1SHA1; +#else + break; +#endif + case WC_HASH_TYPE_SHA224: +#ifdef WOLFSSL_SHA224 + return WC_MGF1SHA224; +#else + break; +#endif + case WC_HASH_TYPE_SHA256: +#ifndef NO_SHA256 + return WC_MGF1SHA256; +#else + break; +#endif + case WC_HASH_TYPE_SHA384: +#ifdef WOLFSSL_SHA384 + return WC_MGF1SHA384; +#else + break; +#endif + case WC_HASH_TYPE_SHA512: +#ifdef WOLFSSL_SHA512 + return WC_MGF1SHA512; +#else + break; +#endif + /* MGF1 only supports SHA-1 and SHA-2; other hashes fall through to WC_MGF1NONE */ + case WC_HASH_TYPE_MD2: + case WC_HASH_TYPE_MD4: + case WC_HASH_TYPE_MD5: + case WC_HASH_TYPE_MD5_SHA: + case WC_HASH_TYPE_SHA3_224: + case WC_HASH_TYPE_SHA3_256: + case WC_HASH_TYPE_SHA3_384: + case WC_HASH_TYPE_SHA3_512: + case WC_HASH_TYPE_BLAKE2B: + case WC_HASH_TYPE_BLAKE2S: +#ifndef WOLFSSL_NOSHA512_224 + case WC_HASH_TYPE_SHA512_224: +#endif +#ifndef WOLFSSL_NOSHA512_256 + case WC_HASH_TYPE_SHA512_256: +#endif +#ifdef WOLFSSL_SHAKE128 + case WC_HASH_TYPE_SHAKE128: +#endif +#ifdef WOLFSSL_SHAKE256 + case WC_HASH_TYPE_SHAKE256: +#endif +#ifdef WOLFSSL_SM3 + case WC_HASH_TYPE_SM3: +#endif + default: + break; + } + return WC_MGF1NONE; +} + +/* returns size of signature put into esd->encContentDigest, negative on error. + * Signs the digest (contentAttribsDigest) with RSA-PSS padding, like ECDSA. */ +static int wc_PKCS7_RsaPssSign(wc_PKCS7* pkcs7, byte* digest, word32 digestSz, + ESD* esd) +{ + int ret; + word32 outSz; + WC_DECLARE_VAR(privKey, RsaKey, 1, 0); + + if (pkcs7 == NULL || pkcs7->rng == NULL || digest == NULL || esd == NULL) { + return BAD_FUNC_ARG; + } + + WC_ALLOC_VAR_EX(privKey, RsaKey, 1, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER, + return MEMORY_E); + + ret = wc_PKCS7_ImportRSA(pkcs7, privKey); + if (ret == 0) { + outSz = sizeof(esd->encContentDigest); +#ifdef WOLFSSL_ASYNC_CRYPT + do { + ret = wc_AsyncWait(ret, &privKey->asyncDev, + WC_ASYNC_FLAG_CALL_AGAIN); + if (ret >= 0) +#endif + { + /* Salt length policy: use hash digest length (RFC 4055 typical). + * RFC 3447 allows arbitrary salt lengths, but hash-length is the + * most interoperable choice and matches OpenSSL's default. + * Must agree with the saltLen encoded in + * SignerInfo.signatureAlgorithm params above. */ + int saltLen = wc_HashGetDigestSize(wc_OidGetHash(pkcs7->hashOID)); + if (saltLen < 0) { + ret = saltLen; + } else { + ret = wc_RsaPSS_Sign_ex(digest, digestSz, + esd->encContentDigest, outSz, + esd->hashType, pkcs7_hash2mgf(esd->hashType), + saltLen, privKey, pkcs7->rng); + } + } +#ifdef WOLFSSL_ASYNC_CRYPT + } while (ret == WC_NO_ERR_TRACE(WC_PENDING_E)); +#endif + /* wc_RsaPSS_Sign_ex returns signature length on success */ + } + + wc_FreeRsaKey(privKey); + WC_FREE_VAR_EX(privKey, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} +#endif /* WC_RSA_PSS && !NO_RSA */ + /* returns encContentDigestSz based on the signature set to be used */ static int wc_PKCS7_GetSignSize(wc_PKCS7* pkcs7) { @@ -1954,6 +2113,9 @@ static int wc_PKCS7_GetSignSize(wc_PKCS7* pkcs7) #ifndef NO_RSA case RSAk: + #ifdef WC_RSA_PSS + case RSAPSSk: + #endif { #ifndef WOLFSSL_SMALL_STACK RsaKey privKey[1]; @@ -2244,6 +2406,20 @@ static int wc_PKCS7_SignedDataGetEncAlgoId(wc_PKCS7* pkcs7, int* digEncAlgoId, } #endif /* HAVE_ECC */ +#ifdef WC_RSA_PSS + else if (pkcs7->publicKeyOID == RSAPSSk) { + algoType = oidSigType; + algoId = CTC_RSASSAPSS; + /* Hash/MGF/salt conveyed via PSS params in AlgorithmIdentifier */ + } +#endif +#ifndef WC_RSA_PSS + else if (pkcs7->publicKeyOID == RSAPSSk) { + WOLFSSL_MSG("RSA-PSS requested but WC_RSA_PSS not compiled in"); + return NOT_COMPILED_IN; + } +#endif + if (algoId == 0) { WOLFSSL_MSG("Invalid signature algorithm type"); return BAD_FUNC_ARG; @@ -2359,7 +2535,8 @@ static int wc_PKCS7_SignedDataBuildSignature(wc_PKCS7* pkcs7, { int ret = 0; #if defined(HAVE_ECC) || \ - (defined(HAVE_PKCS7_RSA_RAW_SIGN_CALLBACK) && !defined(NO_RSA)) + (defined(HAVE_PKCS7_RSA_RAW_SIGN_CALLBACK) && !defined(NO_RSA)) || \ + (defined(WC_RSA_PSS) && !defined(NO_RSA)) int hashSz = 0; #endif #if defined(HAVE_PKCS7_RSA_RAW_SIGN_CALLBACK) && !defined(NO_RSA) @@ -2384,7 +2561,8 @@ static int wc_PKCS7_SignedDataBuildSignature(wc_PKCS7* pkcs7, } #if defined(HAVE_ECC) || \ - (defined(HAVE_PKCS7_RSA_RAW_SIGN_CALLBACK) && !defined(NO_RSA)) + (defined(HAVE_PKCS7_RSA_RAW_SIGN_CALLBACK) && !defined(NO_RSA)) || \ + (defined(WC_RSA_PSS) && !defined(NO_RSA)) /* get digest size from hash type */ hashSz = wc_HashGetDigestSize(esd->hashType); if (hashSz < 0) { @@ -2447,6 +2625,14 @@ static int wc_PKCS7_SignedDataBuildSignature(wc_PKCS7* pkcs7, break; #endif +#if defined(WC_RSA_PSS) && !defined(NO_RSA) + case RSAPSSk: + /* RSA-PSS signs the digest directly (no DigestInfo), like ECDSA */ + ret = wc_PKCS7_RsaPssSign(pkcs7, esd->contentAttribsDigest, + (word32)hashSz, esd); + break; +#endif + default: WOLFSSL_MSG("Unsupported public key type"); ret = BAD_FUNC_ARG; @@ -2801,14 +2987,19 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, return BAD_FUNC_ARG; } - /* signature size varies with ECDSA, with a varying sign size the content - * hash must be known in order to create the surrounding ASN1 syntax - * properly before writing out the content and generating the hash on the - * fly and then creating the signature */ - if (hashBuf == NULL && pkcs7->publicKeyOID == ECDSAk) { + /* signature size varies with ECDSA; RSA-PSS signs digest directly like + * ECDSA. For both, content hash must be known to build ASN.1 before signing */ +#if defined(HAVE_ECC) || defined(WC_RSA_PSS) + if (hashBuf == NULL && + (pkcs7->publicKeyOID == ECDSAk +#ifdef WC_RSA_PSS + || pkcs7->publicKeyOID == RSAPSSk +#endif + )) { WOLFSSL_MSG("Pre-calculated content hash is needed in this case"); return BAD_FUNC_ARG; } +#endif #if defined(WOLFSSL_SM2) && defined(WOLFSSL_SM3) keyIdSize = wc_HashGetDigestSize(wc_HashTypeConvert(HashIdAlg( @@ -2965,8 +3156,31 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, idx = ret; goto out; } - esd->digEncAlgoIdSz = SetAlgoIDEx(digEncAlgoId, esd->digEncAlgoId, - digEncAlgoType, 0, pkcs7->hashParamsAbsent); +#if defined(WC_RSA_PSS) + if (digEncAlgoId == CTC_RSASSAPSS) { + /* Salt length policy: always encode as hash digest length. + * This is the common CMS/RFC 4055 profile and matches OpenSSL + * defaults. The decoder (pssSaltLen) handles arbitrary values + * from external blobs. A future pkcs7->pssSaltLen override for + * encode could be added here if custom salt lengths are needed. */ + int saltLen = wc_HashGetDigestSize(wc_OidGetHash(pkcs7->hashOID)); + if (saltLen < 0) { + idx = saltLen; + goto out; + } + esd->digEncAlgoIdSz = wc_EncodeRsaPssAlgoId(pkcs7->hashOID, + (int)(word32)saltLen, esd->digEncAlgoId, + (word32)sizeof(esd->digEncAlgoId)); + if (esd->digEncAlgoIdSz == 0) { + idx = ASN_PARSE_E; + goto out; + } + } else +#endif + { + esd->digEncAlgoIdSz = SetAlgoIDEx(digEncAlgoId, esd->digEncAlgoId, + digEncAlgoType, 0, pkcs7->hashParamsAbsent); + } signerInfoSz += esd->digEncAlgoIdSz; /* build up signed attributes, include contentType, signingTime, and @@ -3567,8 +3781,12 @@ int wc_PKCS7_EncodeSignedData(wc_PKCS7* pkcs7, byte* output, word32 outputSz) return BAD_FUNC_ARG; } - /* pre-calculate hash for ECC signatures */ - if (pkcs7->publicKeyOID == ECDSAk) { + /* pre-calculate content hash for ECDSA and RSA-PSS (both sign digest directly) */ + if (pkcs7->publicKeyOID == ECDSAk +#ifdef WC_RSA_PSS + || pkcs7->publicKeyOID == RSAPSSk +#endif + ) { int hashSz; enum wc_HashType hashType; byte hashBuf[WC_MAX_DIGEST_SIZE]; @@ -4175,6 +4393,169 @@ static int wc_PKCS7_RsaVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, return ret; } +#if defined(WC_RSA_PSS) +/* returns 0 when signature verifies, negative on error */ +static int wc_PKCS7_RsaPssVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, + byte* hash, word32 hashSz) +{ + int ret = 0, i; + word32 scratch = 0, verified = 0; + enum wc_HashType hashType; + int hashDigSz, mgf; +#ifdef WOLFSSL_SMALL_STACK + byte* digest; + RsaKey* key; + DecodedCert* dCert; +#else + /* wc_RsaPSS_Verify_ex output is PSS-encoded message (RSA_PSS_PAD_SZ + + * saltLen + 2*hLen bytes), which can exceed MAX_PKCS7_DIGEST_SZ. + * Use MAX_ENCRYPTED_KEY_SZ (512) to cover RSA keys up to 4096-bit. */ + byte digest[MAX_ENCRYPTED_KEY_SZ]; + RsaKey key[1]; + DecodedCert stack_dCert; + DecodedCert* dCert = &stack_dCert; +#endif + + if (pkcs7 == NULL || sig == NULL || hash == NULL) + return BAD_FUNC_ARG; + + /* Use SignerInfo.signatureAlgorithm params when decoded; else digestAlgo */ + if (pkcs7->pssHashType >= 0) + hashType = (enum wc_HashType)pkcs7->pssHashType; + else + hashType = wc_OidGetHash(pkcs7->hashOID); + hashDigSz = wc_HashGetDigestSize(hashType); + if (hashDigSz < 0) + return ASN_PARSE_E; /* invalid/unsupported hash from params/OID */ + if (hashSz != (word32)hashDigSz) + return ASN_PARSE_E; /* digest size mismatch with PSS hash params */ + mgf = (pkcs7->pssMgf >= 0) ? pkcs7->pssMgf : pkcs7_hash2mgf(hashType); + if (mgf == WC_MGF1NONE) + return ASN_PARSE_E; /* unsupported MGF from params or hash */ + +#ifdef WOLFSSL_SMALL_STACK + /* wc_RsaPSS_Verify_ex output is PSS-encoded message (RSA_PSS_PAD_SZ + + * saltLen + 2*hLen bytes), which can exceed MAX_PKCS7_DIGEST_SZ. + * Use MAX_ENCRYPTED_KEY_SZ (512) to cover RSA keys up to 4096-bit. */ + digest = (byte*)XMALLOC(MAX_ENCRYPTED_KEY_SZ, pkcs7->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (digest == NULL) + return MEMORY_E; + key = (RsaKey*)XMALLOC(sizeof(RsaKey), pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (key == NULL) { + XFREE(digest, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + return MEMORY_E; + } + dCert = (DecodedCert*)XMALLOC(sizeof(DecodedCert), pkcs7->heap, + DYNAMIC_TYPE_DCERT); + if (dCert == NULL) { + XFREE(digest, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(key, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + return MEMORY_E; + } +#endif + + XMEMSET(digest, 0, MAX_ENCRYPTED_KEY_SZ); + + for (i = 0; i < MAX_PKCS7_CERTS; i++) { + if (pkcs7->certSz[i] == 0) + continue; + + scratch = 0; /* decode from start of this cert's key each time */ + ret = wc_InitRsaKey_ex(key, pkcs7->heap, pkcs7->devId); + if (ret != 0) + break; + + InitDecodedCert(dCert, pkcs7->cert[i], pkcs7->certSz[i], pkcs7->heap); + ret = ParseCert(dCert, CA_TYPE, NO_VERIFY, 0); + if (ret < 0) { + FreeDecodedCert(dCert); + wc_FreeRsaKey(key); + continue; + } + + { + word32 pkSz = dCert->pubKeySize; + + /* RSAPSS certs may report oversized SPKI blobs */ + if (pkSz > (MAX_RSA_INT_SZ + MAX_RSA_E_SZ)) { + pkSz = (MAX_RSA_INT_SZ + MAX_RSA_E_SZ); + } + + if (wc_RsaPublicKeyDecode(dCert->publicKey, &scratch, key, + pkSz) < 0) { + FreeDecodedCert(dCert); + wc_FreeRsaKey(key); + continue; + } + } + + { + int saltLen = (pkcs7->pssSaltLen >= 0) ? pkcs7->pssSaltLen + : RSA_PSS_SALT_LEN_DEFAULT; + int verify; + + /* wc_RsaPSS_Verify_ex returns PSS encoded message length, not + * the recovered hash. Must then call wc_RsaPSS_CheckPadding_ex + * to verify the PSS padding against the expected hash. */ + verify = wc_RsaPSS_Verify_ex(sig, (word32)sigSz, digest, + MAX_ENCRYPTED_KEY_SZ, + hashType, mgf, saltLen, key); + if (verify > 0) { + /* wc_RsaPSS_CheckPadding_ex signature varies across + * FIPS / selftest versions; match the pattern in asn.c */ + #if (defined(HAVE_SELFTEST) && \ + (!defined(HAVE_SELFTEST_VERSION) || \ + (HAVE_SELFTEST_VERSION < 2))) || \ + (defined(HAVE_FIPS) && defined(HAVE_FIPS_VERSION) && \ + (HAVE_FIPS_VERSION < 2)) + ret = wc_RsaPSS_CheckPadding_ex(hash, hashSz, digest, + (word32)verify, hashType, + saltLen); + #elif (defined(HAVE_SELFTEST) && \ + (HAVE_SELFTEST_VERSION == 2)) || \ + (defined(HAVE_FIPS) && defined(HAVE_FIPS_VERSION) && \ + (HAVE_FIPS_VERSION == 2)) + ret = wc_RsaPSS_CheckPadding_ex(hash, hashSz, digest, + (word32)verify, hashType, + saltLen, 0); + #else + ret = wc_RsaPSS_CheckPadding_ex2(hash, hashSz, digest, + (word32)verify, hashType, + saltLen, + mp_count_bits(&key->n), + pkcs7->heap); + #endif + } + else { + ret = verify; /* propagate error */ + } + } + FreeDecodedCert(dCert); + wc_FreeRsaKey(key); + + if (ret == 0) { + verified = 1; + pkcs7->verifyCert = pkcs7->cert[i]; + pkcs7->verifyCertSz = pkcs7->certSz[i]; + break; + } + ret = 0; + } + + if (verified == 0) + ret = SIG_VERIFY_E; + +#ifdef WOLFSSL_SMALL_STACK + WC_FREE_VAR_EX(digest, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + WC_FREE_VAR_EX(key, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + WC_FREE_VAR_EX(dCert, pkcs7->heap, DYNAMIC_TYPE_DCERT); +#endif + + return ret; +} +#endif /* WC_RSA_PSS */ + #endif /* NO_RSA */ @@ -4695,6 +5076,14 @@ static int wc_PKCS7_SignedDataVerifySignature(wc_PKCS7* pkcs7, byte* sig, plainDigestSz); } break; + + #ifdef WC_RSA_PSS + /* RSA-PSS signs the raw hash (plainDigest), not DigestInfo */ + case RSAPSSk: + ret = wc_PKCS7_RsaPssVerify(pkcs7, sig, (int)sigSz, plainDigest, + plainDigestSz); + break; + #endif #endif #ifdef HAVE_ECC @@ -4741,6 +5130,13 @@ static int wc_PKCS7_SetPublicKeyOID(wc_PKCS7* pkcs7, int sigOID) pkcs7->publicKeyOID = RSAk; break; + #ifdef WC_RSA_PSS + /* CTC_RSASSAPSS and RSAPSSk are the same OID value */ + case CTC_RSASSAPSS: + pkcs7->publicKeyOID = RSAPSSk; + break; + #endif + /* if sigOID is already RSAk */ case RSAk: pkcs7->publicKeyOID = (word32)sigOID; @@ -5050,9 +5446,63 @@ static int wc_PKCS7_ParseSignerInfo(wc_PKCS7* pkcs7, byte* in, word32 inSz, idx += (word32)length; } - /* Get digestEncryptionAlgorithm - key type or signature type */ - if (ret == 0 && GetAlgoId(in, &idx, &sigOID, oidIgnoreType, inSz) < 0) { - ret = ASN_PARSE_E; + /* Get digestEncryptionAlgorithm (signatureAlgorithm) - parse manually + * so we can decode id-RSASSA-PSS parameters in all builds. */ +#if defined(WC_RSA_PSS) && !defined(NO_RSA) + pkcs7->pssSaltLen = -1; + pkcs7->pssHashType = -1; + pkcs7->pssMgf = -1; +#endif + if (ret == 0) { + int algoSeqLen = 0; + word32 algoContentStart = 0; + if (GetSequence(in, &idx, &algoSeqLen, (word32)inSz) < 0) { + ret = ASN_PARSE_E; + } + else { + algoContentStart = idx; /* first byte of AlgorithmIdentifier content */ + if (GetObjectId(in, &idx, &sigOID, oidSigType, inSz) < 0) { + ret = ASN_PARSE_E; + } + /* Only parse params when still inside the AlgorithmIdentifier; + * when optional params are absent, idx is already past the sequence. */ + else if (algoContentStart + (word32)algoSeqLen > idx) { + word32 paramsStart = idx; + byte paramTag; + int paramLen = 0; + if (GetASNTag(in, &idx, ¶mTag, inSz) != 0 || + GetLength(in, &idx, ¶mLen, inSz) < 0) { + ret = ASN_PARSE_E; + } + else { +#if defined(WC_RSA_PSS) && !defined(NO_RSA) + if ((word32)sigOID == (word32)CTC_RSASSAPSS && + paramTag == (ASN_SEQUENCE | ASN_CONSTRUCTED)) { + word32 tlvLen = (idx - paramsStart) + + (word32)paramLen; + enum wc_HashType pssHash = WC_HASH_TYPE_SHA; + int pssMgfVal = 0, pssSalt = 0; + if (paramsStart + tlvLen > (word32)inSz) { + return ASN_PARSE_E; + } + ret = wc_DecodeRsaPssParams(in + paramsStart, tlvLen, + &pssHash, &pssMgfVal, + &pssSalt); + if (ret == 0) { + pkcs7->pssSaltLen = pssSalt; + pkcs7->pssHashType = (int)pssHash; + pkcs7->pssMgf = pssMgfVal; + } + else { + WOLFSSL_MSG("RSASSA-PSS parameters invalid - failing parse"); + return ASN_PARSE_E; + } + } +#endif + idx += (word32)paramLen; + } + } + } } /* store public key type based on digestEncryptionAlgorithm */ diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index e1448c145a6..e185a25dcc4 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -2506,6 +2506,12 @@ WOLFSSL_LOCAL word32 SetSet(word32 len, byte* output); WOLFSSL_API word32 SetAlgoID(int algoOID, byte* output, int type, int curveSz); WOLFSSL_LOCAL word32 SetAlgoIDEx(int algoOID, byte* output, int type, int curveSz, byte absentParams); +#if defined(WC_RSA_PSS) && !defined(NO_RSA) +WOLFSSL_LOCAL word32 wc_EncodeRsaPssAlgoId(int hashOID, int saltLen, byte* out, + word32 outSz); +WOLFSSL_LOCAL int wc_DecodeRsaPssParams(const byte* params, word32 sz, + enum wc_HashType* hash, int* mgf, int* saltLen); +#endif WOLFSSL_LOCAL int SetMyVersion(word32 version, byte* output, int header); WOLFSSL_LOCAL int SetSerialNumber(const byte* sn, word32 snSz, byte* output, word32 outputSz, int maxSnSz); diff --git a/wolfssl/wolfcrypt/pkcs7.h b/wolfssl/wolfcrypt/pkcs7.h index 57bd83594d9..40a381a787d 100644 --- a/wolfssl/wolfcrypt/pkcs7.h +++ b/wolfssl/wolfcrypt/pkcs7.h @@ -388,6 +388,14 @@ struct wc_PKCS7 { CallbackEccSignRawDigest eccSignRawDigestCb; #endif +#if defined(WC_RSA_PSS) && !defined(NO_RSA) + /* Decoded from SignerInfo.signatureAlgorithm.parameters (id-RSASSA-PSS). + * Used on verify; -1 when not applicable. */ + int pssSaltLen; /* salt length from RSASSA-PSS-params; -1 if N/A */ + int pssHashType; /* wc_HashType from RSASSA-PSS-params; -1 if N/A */ + int pssMgf; /* MGF from RSASSA-PSS-params (e.g. WC_MGF1SHA256); -1 if N/A */ +#endif + /* !! NEW DATA MEMBERS MUST BE ADDED AT END !! */ }; From 1289ea8277ef877fd059cb88c6c5821b5fa3c962 Mon Sep 17 00:00:00 2001 From: Sameeh Jubran Date: Wed, 18 Feb 2026 12:46:54 +0200 Subject: [PATCH 2/2] fixes Signed-off-by: Sameeh Jubran --- doc/dox_comments/header_files/cryptocb.h | 13 +- tests/api/test_pkcs7.c | 9 +- wolfcrypt/src/asn.c | 230 +++++++++++++++++------ wolfcrypt/src/pkcs7.c | 213 ++++++++++----------- wolfssl/wolfcrypt/pkcs7.h | 9 +- 5 files changed, 283 insertions(+), 191 deletions(-) diff --git a/doc/dox_comments/header_files/cryptocb.h b/doc/dox_comments/header_files/cryptocb.h index aefd733996c..e236e1271f8 100644 --- a/doc/dox_comments/header_files/cryptocb.h +++ b/doc/dox_comments/header_files/cryptocb.h @@ -54,14 +54,13 @@ #endif #if defined(WC_RSA_PSS) && !defined(NO_RSA) if (info->pk.type == WC_PK_TYPE_RSA_PSS) { - /* RSA-PSS sign/verify (e.g. PKCS#7 SignedData, X.509). - * Uses info->pk.rsa (in/inLen = digest, out/outLen = signature, - * key, rng). With WOLF_CRYPTO_CB_RSA_PAD, info->pk.rsa.padding - * supplies hash and salt length. */ - ret = wc_RsaFunction(info->pk.rsa.in, info->pk.rsa.inLen, - info->pk.rsa.out, info->pk.rsa.outLen, info->pk.rsa.type, + // RSA-PSS sign/verify + ret = wc_RsaPSS_Sign_ex( + info->pk.rsa.in, info->pk.rsa.inLen, + info->pk.rsa.out, *info->pk.rsa.outLen, + WC_HASH_TYPE_SHA256, WC_MGF1SHA256, + RSA_PSS_SALT_LEN_DEFAULT, info->pk.rsa.key, info->pk.rsa.rng); - break; } #endif #ifdef HAVE_ECC diff --git a/tests/api/test_pkcs7.c b/tests/api/test_pkcs7.c index 2d6e3a23272..393017096c3 100644 --- a/tests/api/test_pkcs7.c +++ b/tests/api/test_pkcs7.c @@ -975,19 +975,20 @@ int test_wc_PKCS7_EncodeSignedData_RSA_PSS(void) ExpectIntEQ(wc_InitRng(&rng), 0); ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); - ExpectIntEQ(wc_PKCS7_Init(pkcs7, HEAP_HINT, INVALID_DEVID), 0); ExpectTrue((fp = XFOPEN("./certs/rsapss/client-rsapss.der", "rb")) != XBADFILE); - ExpectIntGT(certSz = (word32)XFREAD(cert, 1, sizeof(cert), fp), 0); if (fp != XBADFILE) { + ExpectIntGT(certSz = (word32)XFREAD(cert, 1, sizeof(cert), fp), 0); XFCLOSE(fp); fp = XBADFILE; } ExpectTrue((fp = XFOPEN("./certs/rsapss/client-rsapss-priv.der", "rb")) != XBADFILE); - ExpectIntGT(keySz = (word32)XFREAD(key, 1, sizeof(key), fp), 0); - if (fp != XBADFILE) + if (fp != XBADFILE) { + ExpectIntGT(keySz = (word32)XFREAD(key, 1, sizeof(key), fp), 0); XFCLOSE(fp); + fp = XBADFILE; + } ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, cert, certSz), 0); diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 597ce35d5d1..c2bfb848797 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -2539,7 +2539,7 @@ int GetASNHeader(const byte* input, byte tag, word32* inOutIdx, int* len, return GetASNHeader_ex(input, tag, inOutIdx, len, maxIdx, 1); } -#if !defined(WOLFSSL_ASN_TEMPLATE) || (defined(WC_RSA_PSS) && !defined(NO_RSA)) +#if !defined(WOLFSSL_ASN_TEMPLATE) static int GetHeader(const byte* input, byte* tag, word32* inOutIdx, int* len, word32 maxIdx, int check) { @@ -2896,9 +2896,7 @@ static int GetInteger7Bit(const byte* input, word32* inOutIdx, word32 maxIdx) #endif /* !NO_CERTS */ #endif /* !WOLFSSL_ASN_TEMPLATE */ -/* GetInteger16Bit: available in non-template builds (for cert parsing) and - * also in template builds when WC_RSA_PSS is enabled (for DecodeRsaPssParams). */ -#if !defined(WOLFSSL_ASN_TEMPLATE) || (defined(WC_RSA_PSS) && !defined(NO_RSA)) +#if !defined(WOLFSSL_ASN_TEMPLATE) #if ((defined(WC_RSA_PSS) && !defined(NO_RSA)) || !defined(NO_CERTS)) /* Get the DER/BER encoding of an ASN.1 INTEGER that has a value of no more than * 16 bits. @@ -2945,7 +2943,7 @@ static int GetInteger16Bit(const byte* input, word32* inOutIdx, word32 maxIdx) return n; } #endif /* WC_RSA_PSS || !NO_CERTS */ -#endif /* !WOLFSSL_ASN_TEMPLATE || WC_RSA_PSS */ +#endif /* !WOLFSSL_ASN_TEMPLATE */ #if !defined(NO_DSA) && !defined(NO_SHA) static const char sigSha1wDsaName[] = "SHAwDSA"; @@ -7458,66 +7456,146 @@ static int RsaPssHashOidToSigOid(word32 oid, word32* sigOid) } #endif -/* RSASSA-PSS-params EXPLICIT context tags (constructed, per X.680/X.690). - * These are the on-wire tag bytes for [0]..[3] in the SEQUENCE. */ +/* RSASSA-PSS-params EXPLICIT context tags (per X.680/X.690). + * The template engine strips ASN_CONSTRUCTED before matching, so the + * template tags omit it; the non-template tags include it for direct + * byte comparison. */ +#ifdef WOLFSSL_ASN_TEMPLATE +#define ASN_TAG_RSA_PSS_HASH (ASN_CONTEXT_SPECIFIC | 0) +#define ASN_TAG_RSA_PSS_MGF (ASN_CONTEXT_SPECIFIC | 1) +#define ASN_TAG_RSA_PSS_SALTLEN (ASN_CONTEXT_SPECIFIC | 2) +#define ASN_TAG_RSA_PSS_TRAILER (ASN_CONTEXT_SPECIFIC | 3) + +/* ASN.1 template for RSA PSS parameters. */ +static const ASNItem rsaPssParamsASN[] = { +/* SEQ */ { 0, ASN_SEQUENCE, 1, 1, 0 }, +/* HASH */ { 1, ASN_TAG_RSA_PSS_HASH, 1, 1, 1 }, +/* HASHSEQ */ { 2, ASN_SEQUENCE, 1, 1, 0 }, +/* HASHOID */ { 3, ASN_OBJECT_ID, 0, 0, 0 }, +/* HASHNULL */ { 3, ASN_TAG_NULL, 0, 0, 1 }, +/* MGF */ { 1, ASN_TAG_RSA_PSS_MGF, 1, 1, 1 }, +/* MGFSEQ */ { 2, ASN_SEQUENCE, 1, 1, 0 }, +/* MGFOID */ { 3, ASN_OBJECT_ID, 0, 0, 0 }, +/* MGFPARAM */ { 3, ASN_SEQUENCE, 1, 1, 0 }, +/* MGFHOID */ { 4, ASN_OBJECT_ID, 0, 0, 0 }, +/* MGFHNULL */ { 4, ASN_TAG_NULL, 0, 0, 1 }, +/* SALTLEN */ { 1, ASN_TAG_RSA_PSS_SALTLEN, 1, 1, 1 }, +/* SALTLENINT */ { 2, ASN_INTEGER, 0, 0, 0 }, +/* TRAILER */ { 1, ASN_TAG_RSA_PSS_TRAILER, 1, 1, 1 }, +/* TRAILERINT */ { 2, ASN_INTEGER, 0, 0, 0 }, +}; +enum { + RSAPSSPARAMSASN_IDX_SEQ = 0, + RSAPSSPARAMSASN_IDX_HASH, + RSAPSSPARAMSASN_IDX_HASHSEQ, + RSAPSSPARAMSASN_IDX_HASHOID, + RSAPSSPARAMSASN_IDX_HASHNULL, + RSAPSSPARAMSASN_IDX_MGF, + RSAPSSPARAMSASN_IDX_MGFSEQ, + RSAPSSPARAMSASN_IDX_MGFOID, + RSAPSSPARAMSASN_IDX_MGFPARAM, + RSAPSSPARAMSASN_IDX_MGFHOID, + RSAPSSPARAMSASN_IDX_MGFHNULL, + RSAPSSPARAMSASN_IDX_SALTLEN, + RSAPSSPARAMSASN_IDX_SALTLENINT, + RSAPSSPARAMSASN_IDX_TRAILER, + RSAPSSPARAMSASN_IDX_TRAILERINT +}; + +#define rsaPssParamsASN_Length (sizeof(rsaPssParamsASN) / sizeof(ASNItem)) +#else #define ASN_TAG_RSA_PSS_HASH (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 0) #define ASN_TAG_RSA_PSS_MGF (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 1) #define ASN_TAG_RSA_PSS_SALTLEN (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 2) #define ASN_TAG_RSA_PSS_TRAILER (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 3) +#endif /* Decode the RSA PSS parameters. * - * Uses manual (non-template) ASN.1 parsing for both WOLFSSL_ASN_TEMPLATE and - * non-template builds. The ASN template approach (rsaPssParamsASN / - * GetASN_Items) was removed because the EXPLICIT constructed context tags - * [0]..[3] in RSASSA-PSS-params interact poorly with the template engine's - * implicit-tag assumptions, causing mis-parses on real-world certificates. - * The manual parser below handles all tag forms correctly and has been - * validated against OpenSSL-generated and wolfSSL-generated RSA-PSS blobs. + * @param [in] params Buffer holding BER encoded RSA PSS parameters. + * @param [in] sz Size of data in buffer in bytes. + * @param [out] hash Hash algorithm to use on message. + * @param [out] mgf MGF algorithm to use with PSS padding. + * @param [out] saltLen Length of salt in PSS padding. + * @return BAD_FUNC_ARG when the params is NULL. + * @return ASN_PARSE_E when the decoding fails. + * @return 0 on success. */ static int DecodeRsaPssParams(const byte* params, word32 sz, enum wc_HashType* hash, int* mgf, int* saltLen) { - int ret = 0; - word32 idx = 0; - int len = 0; - word32 oid = 0; - byte tag; - int length; + if (params == NULL) + return BAD_FUNC_ARG; - /* params points to AlgorithmIdentifier.parameters TLV (NULL or SEQUENCE). */ - WOLFSSL_MSG_EX("DecodeRsaPssParams: enter, sz=%u", sz); - if (params == NULL) { - WOLFSSL_MSG("DecodeRsaPssParams: fail at params_null"); - ret = BAD_FUNC_ARG; - } - if ((ret == 0) && (sz == 0)) { - WOLFSSL_MSG("DecodeRsaPssParams: sz=0, returning defaults"); + /* Empty or NULL-tag parameters mean all defaults per RFC 4055 */ + if (sz == 0) { *hash = WC_HASH_TYPE_SHA; *mgf = WC_MGF1SHA1; *saltLen = 20; return 0; } - if (ret == 0 && params[0] == ASN_TAG_NULL) { - word32 tIdx = 0; - byte tTag; - int tLen; - if (GetHeader(params, &tTag, &tIdx, &tLen, sz, 0) >= 0 && tLen == 0) { + if (params[0] == ASN_TAG_NULL) { + if (sz >= 2 && params[1] == 0) { *hash = WC_HASH_TYPE_SHA; *mgf = WC_MGF1SHA1; *saltLen = 20; - return 0; /* defaults */ + return 0; } return ASN_PARSE_E; } - if ((ret == 0) && (params[0] != (ASN_SEQUENCE | ASN_CONSTRUCTED))) { - WOLFSSL_MSG("DecodeRsaPssParams: fail at sequence"); + if (params[0] != (ASN_SEQUENCE | ASN_CONSTRUCTED)) return ASN_PARSE_E; + +#ifdef WOLFSSL_ASN_TEMPLATE +{ + DECL_ASNGETDATA(dataASN, rsaPssParamsASN_Length); + int ret = 0; + word16 sLen = 20; + + /* Default values. */ + *hash = WC_HASH_TYPE_SHA; + *mgf = WC_MGF1SHA1; + + CALLOC_ASNGETDATA(dataASN, rsaPssParamsASN_Length, ret, NULL); + if (ret == 0) { + word32 inOutIdx = 0; + /* Set OID type expected. */ + GetASN_OID(&dataASN[RSAPSSPARAMSASN_IDX_HASHOID], oidHashType); + GetASN_OID(&dataASN[RSAPSSPARAMSASN_IDX_MGFHOID], oidHashType); + /* Place the salt length into 16-bit var sLen. */ + GetASN_Int16Bit(&dataASN[RSAPSSPARAMSASN_IDX_SALTLENINT], &sLen); + /* Decode the algorithm identifier. */ + ret = GetASN_Items(rsaPssParamsASN, dataASN, rsaPssParamsASN_Length, 1, + params, &inOutIdx, sz); + } + if ((ret == 0) && (dataASN[RSAPSSPARAMSASN_IDX_HASHOID].tag != 0)) { + word32 oid = dataASN[RSAPSSPARAMSASN_IDX_HASHOID].data.oid.sum; + ret = RsaPssHashOidToType(oid, hash); + } + if ((ret == 0) && (dataASN[RSAPSSPARAMSASN_IDX_MGFHOID].tag != 0)) { + word32 oid = dataASN[RSAPSSPARAMSASN_IDX_MGFHOID].data.oid.sum; + ret = RsaPssHashOidToMgf1(oid, mgf); + } + if (ret == 0) { + *saltLen = sLen; } + + FREE_ASNGETDATA(dataASN, NULL); + return ret; +} +#else /* !WOLFSSL_ASN_TEMPLATE */ +{ + int ret = 0; + word32 idx = 0; + int len = 0; + word32 oid = 0; + byte tag; + int length; + /* Decode RSASSA-PSS-params SEQUENCE content. */ - if ((ret == 0) && (GetSequence_ex(params, &idx, &len, sz, 1) < 0)) { + if (GetSequence_ex(params, &idx, &len, sz, 1) < 0) { WOLFSSL_MSG("DecodeRsaPssParams: fail at sequence"); - ret = ASN_PARSE_E; + return ASN_PARSE_E; } /* [0] hashAlgorithm */ @@ -7548,14 +7626,13 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, /* [1] maskGenAlgorithm -- AlgorithmIdentifier { OID id-mgf1, hash AlgoId } * Parse manually: read the MGF SEQUENCE + OID, then the hash AlgoId - * parameter, because GetAlgoId (in template builds) consumes the entire - * AlgorithmIdentifier including the hash parameter inside it. */ + * parameter, because GetAlgoId consumes the entire AlgorithmIdentifier + * including the hash parameter inside it. */ if (ret == 0) { if ((idx < sz) && (params[idx] == ASN_TAG_RSA_PSS_MGF)) { int mgfSeqLen = 0; + word32 mgfEnd; - WOLFSSL_MSG_EX("DecodeRsaPssParams: found MGF tag 0x%02x at idx %u", - params[idx], idx); if (GetHeader(params, &tag, &idx, &length, sz, 0) < 0) { WOLFSSL_MSG("DecodeRsaPssParams: fail at mgf_header"); ret = ASN_PARSE_E; @@ -7567,9 +7644,16 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, ret = ASN_PARSE_E; } } + /* Bound subsequent reads to the MGF SEQUENCE content */ + mgfEnd = idx + (word32)mgfSeqLen; + if (mgfEnd > sz) { + WOLFSSL_MSG("DecodeRsaPssParams: mgf_seq overflows buffer"); + ret = ASN_PARSE_E; + } /* Read MGF OID (id-mgf1) directly */ if (ret == 0) { - if (GetObjectId(params, &idx, &oid, oidIgnoreType, sz) < 0) { + if (GetObjectId(params, &idx, &oid, oidIgnoreType, + mgfEnd) < 0) { WOLFSSL_MSG("DecodeRsaPssParams: fail at mgf_oid"); ret = ASN_PARSE_E; } @@ -7580,7 +7664,7 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, } /* Read hash AlgorithmIdentifier (parameter of MGF1) */ if (ret == 0) { - ret = GetAlgoId(params, &idx, &oid, oidHashType, sz); + ret = GetAlgoId(params, &idx, &oid, oidHashType, mgfEnd); if (ret == 0) { ret = RsaPssHashOidToMgf1(oid, mgf); if (ret != 0) @@ -7589,14 +7673,13 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, else WOLFSSL_MSG("DecodeRsaPssParams: fail at mgf_hash_algo"); } + if ((ret == 0) && (idx != mgfEnd)) { + WOLFSSL_MSG("DecodeRsaPssParams: extra data in mgf_seq"); + ret = ASN_PARSE_E; + } } else { /* Default MGF/Hash algorithm. */ - if (idx < sz) { - WOLFSSL_MSG_EX("DecodeRsaPssParams: no MGF tag, got 0x%02x at" - " idx %u, using default MGF1-SHA1", - params[idx], idx); - } *mgf = WC_MGF1SHA1; } } @@ -7651,6 +7734,8 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, return ret; } +#endif /* WOLFSSL_ASN_TEMPLATE */ +} WOLFSSL_LOCAL int wc_DecodeRsaPssParams(const byte* params, word32 sz, enum wc_HashType* hash, int* mgf, int* saltLen) @@ -7665,6 +7750,11 @@ WOLFSSL_LOCAL int wc_DecodeRsaPssParams(const byte* params, word32 sz, * Note: saltLength is encoded as a single-byte INTEGER; values >255 would * require multi-byte encoding (not implemented; CMS profiles use hash-length * salts, e.g. 20-64 bytes). */ +/* Scratch buffer for building RSA-PSS AlgorithmIdentifier sub-encodings. + * Must hold the largest single sub-encoding (hash AlgoId, typically ~15 bytes) + * plus room for integer TLVs. 64 bytes is sufficient; 128 gives margin. */ +#define RSA_PSS_ALGOID_TMPBUF_SZ 128 + WOLFSSL_LOCAL word32 wc_EncodeRsaPssAlgoId(int hashOID, int saltLen, byte* out, word32 outSz) { word32 idx = 0; @@ -7674,17 +7764,29 @@ WOLFSSL_LOCAL word32 wc_EncodeRsaPssAlgoId(int hashOID, int saltLen, byte* out, word32 rsapssOidSz = sizeof(sigRsaSsaPssOid); /* MGF1 OID 1.2.840.113549.1.1.8 */ static const byte mgf1Oid[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x08 }; - byte tmpBuf[128]; int setIntRet; +#ifdef WOLFSSL_SMALL_STACK + byte* tmpBuf; +#else + byte tmpBuf[RSA_PSS_ALGOID_TMPBUF_SZ]; +#endif if (saltLen < 0 || saltLen > 255) { WOLFSSL_MSG("Salt length must be 0-255 for single-byte encoding"); return 0; } - hashAlgSz = SetAlgoID(hashOID, out ? tmpBuf : NULL, oidHashType, 0); - if (hashAlgSz == 0) +#ifdef WOLFSSL_SMALL_STACK + tmpBuf = (byte*)XMALLOC(RSA_PSS_ALGOID_TMPBUF_SZ, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (tmpBuf == NULL) return 0; +#endif + + hashAlgSz = SetAlgoID(hashOID, out ? tmpBuf : NULL, oidHashType, 0); + if (hashAlgSz == 0) { + idx = 0; goto pss_algoid_done; + } mgf1ParamSz = hashAlgSz; tag0Sz = SetExplicit(0, hashAlgSz, NULL, 0) + hashAlgSz; { @@ -7697,15 +7799,17 @@ WOLFSSL_LOCAL word32 wc_EncodeRsaPssAlgoId(int hashOID, int saltLen, byte* out, } /* SetASNInt writes only tag+length (+ optional leading 0); value byte(s) appended by caller. */ setIntRet = SetASNInt(1, (byte)(saltLen & 0xff), NULL); - if (setIntRet <= 0) - return 0; + if (setIntRet <= 0) { + idx = 0; goto pss_algoid_done; + } { word32 saltIntSz = (word32)setIntRet + 1; /* header + one value byte (two if high bit set) */ tag2Sz = SetExplicit(2, saltIntSz, NULL, 0) + saltIntSz; } setIntRet = SetASNInt(1, 0x01, NULL); - if (setIntRet <= 0) - return 0; + if (setIntRet <= 0) { + idx = 0; goto pss_algoid_done; + } { word32 trailerIntSz = (word32)setIntRet + 1; tag3Sz = SetExplicit(3, trailerIntSz, NULL, 0) + trailerIntSz; @@ -7718,10 +7822,12 @@ WOLFSSL_LOCAL word32 wc_EncodeRsaPssAlgoId(int hashOID, int saltLen, byte* out, outerSz = SetSequence(idPart + seqPart, NULL) + idPart + seqPart; } - if (out == NULL) - return outerSz; - if (outSz < outerSz) - return 0; + if (out == NULL) { + idx = outerSz; goto pss_algoid_done; + } + if (outSz < outerSz) { + idx = 0; goto pss_algoid_done; + } { word32 idPart = (word32)SetObjectId((int)rsapssOidSz, NULL) + rsapssOidSz; @@ -7773,6 +7879,10 @@ WOLFSSL_LOCAL word32 wc_EncodeRsaPssAlgoId(int hashOID, int saltLen, byte* out, idx += trailerIntSz; } +pss_algoid_done: +#ifdef WOLFSSL_SMALL_STACK + XFREE(tmpBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif return idx; } diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index 635d78ef8a3..468b6aa6da0 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -29,20 +29,28 @@ #include #endif #if defined(WC_RSA_PSS) && !defined(NO_RSA) - /* FIPS v1 / CAVP builds omit these from rsa.h; provide fallbacks */ - #ifndef RSA_PSS_SALT_LEN_DEFAULT - #define RSA_PSS_SALT_LEN_DEFAULT (-1) - #endif - /* MGF1 constants when not from rsa.h (e.g. FIPS v1 reduced header) */ - #ifndef WC_MGF1NONE - #define WC_MGF1NONE 0 - #define WC_MGF1SHA1 26 - #define WC_MGF1SHA224 4 - #define WC_MGF1SHA256 1 - #define WC_MGF1SHA384 2 - #define WC_MGF1SHA512 3 - #define WC_MGF1SHA512_224 5 - #define WC_MGF1SHA512_256 6 + /* Sentinel: PSS parameter not yet decoded from SignerInfo; verify path + * falls back to RSA_PSS_SALT_LEN_DEFAULT / digest algorithm defaults. */ + #define PKCS7_PSS_PARAM_UNSET (-1) + + /* FIPS v1 and early selftest builds may omit PSS constants from rsa.h */ + #if (defined(HAVE_FIPS) && \ + (!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION < 2))) || \ + (defined(HAVE_SELFTEST) && \ + (!defined(HAVE_SELFTEST_VERSION) || (HAVE_SELFTEST_VERSION < 2))) + #ifndef RSA_PSS_SALT_LEN_DEFAULT + #define RSA_PSS_SALT_LEN_DEFAULT (-1) + #endif + #ifndef WC_MGF1NONE + #define WC_MGF1NONE 0 + #define WC_MGF1SHA1 26 + #define WC_MGF1SHA224 4 + #define WC_MGF1SHA256 1 + #define WC_MGF1SHA384 2 + #define WC_MGF1SHA512 3 + #define WC_MGF1SHA512_224 5 + #define WC_MGF1SHA512_256 6 + #endif #endif #endif #ifdef HAVE_ECC @@ -830,9 +838,9 @@ int wc_PKCS7_Init(wc_PKCS7* pkcs7, void* heap, int devId) XMEMSET(pkcs7, 0, sizeof(wc_PKCS7)); pkcs7->isDynamic = (isDynamic != 0); #if defined(WC_RSA_PSS) && !defined(NO_RSA) - pkcs7->pssSaltLen = -1; - pkcs7->pssHashType = -1; - pkcs7->pssMgf = -1; + pkcs7->pssSaltLen = PKCS7_PSS_PARAM_UNSET; + pkcs7->pssHashType = PKCS7_PSS_PARAM_UNSET; + pkcs7->pssMgf = PKCS7_PSS_PARAM_UNSET; #endif #ifdef WOLFSSL_HEAP_TEST pkcs7->heap = (void*)WOLFSSL_HEAP_TEST; @@ -4289,8 +4297,7 @@ static int wc_PKCS7_RsaVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, byte digest[MAX_PKCS7_DIGEST_SZ]; #endif RsaKey key[1]; - DecodedCert stack_dCert; - DecodedCert* dCert = &stack_dCert; + DecodedCert dCert[1]; #endif if (pkcs7 == NULL || sig == NULL || hash == NULL) { @@ -4400,60 +4407,44 @@ static int wc_PKCS7_RsaPssVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, { int ret = 0, i; word32 scratch = 0, verified = 0; + word32 pkSz; + int saltLen, verify; enum wc_HashType hashType; int hashDigSz, mgf; -#ifdef WOLFSSL_SMALL_STACK - byte* digest; - RsaKey* key; - DecodedCert* dCert; -#else /* wc_RsaPSS_Verify_ex output is PSS-encoded message (RSA_PSS_PAD_SZ + * saltLen + 2*hLen bytes), which can exceed MAX_PKCS7_DIGEST_SZ. * Use MAX_ENCRYPTED_KEY_SZ (512) to cover RSA keys up to 4096-bit. */ - byte digest[MAX_ENCRYPTED_KEY_SZ]; - RsaKey key[1]; - DecodedCert stack_dCert; - DecodedCert* dCert = &stack_dCert; -#endif + WC_DECLARE_VAR(digest, byte, MAX_ENCRYPTED_KEY_SZ, 0); + WC_DECLARE_VAR(key, RsaKey, 1, 0); + WC_DECLARE_VAR(dCert, DecodedCert, 1, 0); if (pkcs7 == NULL || sig == NULL || hash == NULL) return BAD_FUNC_ARG; /* Use SignerInfo.signatureAlgorithm params when decoded; else digestAlgo */ - if (pkcs7->pssHashType >= 0) + if (pkcs7->pssHashType != PKCS7_PSS_PARAM_UNSET) hashType = (enum wc_HashType)pkcs7->pssHashType; else hashType = wc_OidGetHash(pkcs7->hashOID); hashDigSz = wc_HashGetDigestSize(hashType); if (hashDigSz < 0) - return ASN_PARSE_E; /* invalid/unsupported hash from params/OID */ + return ASN_PARSE_E; if (hashSz != (word32)hashDigSz) - return ASN_PARSE_E; /* digest size mismatch with PSS hash params */ - mgf = (pkcs7->pssMgf >= 0) ? pkcs7->pssMgf : pkcs7_hash2mgf(hashType); + return ASN_PARSE_E; + mgf = (pkcs7->pssMgf != PKCS7_PSS_PARAM_UNSET) + ? pkcs7->pssMgf : pkcs7_hash2mgf(hashType); if (mgf == WC_MGF1NONE) - return ASN_PARSE_E; /* unsupported MGF from params or hash */ + return ASN_PARSE_E; -#ifdef WOLFSSL_SMALL_STACK - /* wc_RsaPSS_Verify_ex output is PSS-encoded message (RSA_PSS_PAD_SZ + - * saltLen + 2*hLen bytes), which can exceed MAX_PKCS7_DIGEST_SZ. - * Use MAX_ENCRYPTED_KEY_SZ (512) to cover RSA keys up to 4096-bit. */ - digest = (byte*)XMALLOC(MAX_ENCRYPTED_KEY_SZ, pkcs7->heap, - DYNAMIC_TYPE_TMP_BUFFER); - if (digest == NULL) - return MEMORY_E; - key = (RsaKey*)XMALLOC(sizeof(RsaKey), pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); - if (key == NULL) { - XFREE(digest, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); - return MEMORY_E; - } - dCert = (DecodedCert*)XMALLOC(sizeof(DecodedCert), pkcs7->heap, - DYNAMIC_TYPE_DCERT); - if (dCert == NULL) { - XFREE(digest, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(key, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); - return MEMORY_E; - } -#endif + WC_ALLOC_VAR_EX(digest, byte, MAX_ENCRYPTED_KEY_SZ, pkcs7->heap, + DYNAMIC_TYPE_TMP_BUFFER, return MEMORY_E); + WC_ALLOC_VAR_EX(key, RsaKey, 1, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER, + { WC_FREE_VAR_EX(digest, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + return MEMORY_E; }); + WC_ALLOC_VAR_EX(dCert, DecodedCert, 1, pkcs7->heap, DYNAMIC_TYPE_DCERT, + { WC_FREE_VAR_EX(digest, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + WC_FREE_VAR_EX(key, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + return MEMORY_E; }); XMEMSET(digest, 0, MAX_ENCRYPTED_KEY_SZ); @@ -4461,7 +4452,7 @@ static int wc_PKCS7_RsaPssVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, if (pkcs7->certSz[i] == 0) continue; - scratch = 0; /* decode from start of this cert's key each time */ + scratch = 0; ret = wc_InitRsaKey_ex(key, pkcs7->heap, pkcs7->devId); if (ret != 0) break; @@ -4474,63 +4465,56 @@ static int wc_PKCS7_RsaPssVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, continue; } - { - word32 pkSz = dCert->pubKeySize; - - /* RSAPSS certs may report oversized SPKI blobs */ - if (pkSz > (MAX_RSA_INT_SZ + MAX_RSA_E_SZ)) { - pkSz = (MAX_RSA_INT_SZ + MAX_RSA_E_SZ); - } + pkSz = dCert->pubKeySize; + if (pkSz > (MAX_RSA_INT_SZ + MAX_RSA_E_SZ)) + pkSz = (MAX_RSA_INT_SZ + MAX_RSA_E_SZ); - if (wc_RsaPublicKeyDecode(dCert->publicKey, &scratch, key, - pkSz) < 0) { - FreeDecodedCert(dCert); - wc_FreeRsaKey(key); - continue; - } + if (wc_RsaPublicKeyDecode(dCert->publicKey, &scratch, key, + pkSz) < 0) { + FreeDecodedCert(dCert); + wc_FreeRsaKey(key); + continue; } - { - int saltLen = (pkcs7->pssSaltLen >= 0) ? pkcs7->pssSaltLen - : RSA_PSS_SALT_LEN_DEFAULT; - int verify; - - /* wc_RsaPSS_Verify_ex returns PSS encoded message length, not - * the recovered hash. Must then call wc_RsaPSS_CheckPadding_ex - * to verify the PSS padding against the expected hash. */ - verify = wc_RsaPSS_Verify_ex(sig, (word32)sigSz, digest, - MAX_ENCRYPTED_KEY_SZ, - hashType, mgf, saltLen, key); - if (verify > 0) { - /* wc_RsaPSS_CheckPadding_ex signature varies across - * FIPS / selftest versions; match the pattern in asn.c */ - #if (defined(HAVE_SELFTEST) && \ - (!defined(HAVE_SELFTEST_VERSION) || \ - (HAVE_SELFTEST_VERSION < 2))) || \ - (defined(HAVE_FIPS) && defined(HAVE_FIPS_VERSION) && \ - (HAVE_FIPS_VERSION < 2)) - ret = wc_RsaPSS_CheckPadding_ex(hash, hashSz, digest, - (word32)verify, hashType, - saltLen); - #elif (defined(HAVE_SELFTEST) && \ - (HAVE_SELFTEST_VERSION == 2)) || \ - (defined(HAVE_FIPS) && defined(HAVE_FIPS_VERSION) && \ - (HAVE_FIPS_VERSION == 2)) - ret = wc_RsaPSS_CheckPadding_ex(hash, hashSz, digest, - (word32)verify, hashType, - saltLen, 0); - #else - ret = wc_RsaPSS_CheckPadding_ex2(hash, hashSz, digest, - (word32)verify, hashType, - saltLen, - mp_count_bits(&key->n), - pkcs7->heap); - #endif - } - else { - ret = verify; /* propagate error */ - } + saltLen = (pkcs7->pssSaltLen != PKCS7_PSS_PARAM_UNSET) + ? pkcs7->pssSaltLen : RSA_PSS_SALT_LEN_DEFAULT; + + /* wc_RsaPSS_Verify_ex returns PSS encoded message length, not + * the recovered hash. Must then call wc_RsaPSS_CheckPadding_ex + * to verify the PSS padding against the expected hash. */ + verify = wc_RsaPSS_Verify_ex(sig, (word32)sigSz, digest, + MAX_ENCRYPTED_KEY_SZ, + hashType, mgf, saltLen, key); + if (verify > 0) { + /* wc_RsaPSS_CheckPadding_ex signature varies across + * FIPS / selftest versions; match the pattern in asn.c */ + #if (defined(HAVE_SELFTEST) && \ + (!defined(HAVE_SELFTEST_VERSION) || \ + (HAVE_SELFTEST_VERSION < 2))) || \ + (defined(HAVE_FIPS) && defined(HAVE_FIPS_VERSION) && \ + (HAVE_FIPS_VERSION < 2)) + ret = wc_RsaPSS_CheckPadding_ex(hash, hashSz, digest, + (word32)verify, hashType, + saltLen); + #elif (defined(HAVE_SELFTEST) && \ + (HAVE_SELFTEST_VERSION == 2)) || \ + (defined(HAVE_FIPS) && defined(HAVE_FIPS_VERSION) && \ + (HAVE_FIPS_VERSION == 2)) + ret = wc_RsaPSS_CheckPadding_ex(hash, hashSz, digest, + (word32)verify, hashType, + saltLen, 0); + #else + ret = wc_RsaPSS_CheckPadding_ex2(hash, hashSz, digest, + (word32)verify, hashType, + saltLen, + mp_count_bits(&key->n), + pkcs7->heap); + #endif + } + else { + ret = verify; } + FreeDecodedCert(dCert); wc_FreeRsaKey(key); @@ -4546,11 +4530,9 @@ static int wc_PKCS7_RsaPssVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, if (verified == 0) ret = SIG_VERIFY_E; -#ifdef WOLFSSL_SMALL_STACK WC_FREE_VAR_EX(digest, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); WC_FREE_VAR_EX(key, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); WC_FREE_VAR_EX(dCert, pkcs7->heap, DYNAMIC_TYPE_DCERT); -#endif return ret; } @@ -4575,8 +4557,7 @@ static int wc_PKCS7_EcdsaVerify(wc_PKCS7* pkcs7, byte* sig, int sigSz, #else byte digest[MAX_PKCS7_DIGEST_SZ]; ecc_key key[1]; - DecodedCert stack_dCert; - DecodedCert* dCert = &stack_dCert; + DecodedCert dCert[1]; #endif word32 idx = 0; @@ -5449,9 +5430,9 @@ static int wc_PKCS7_ParseSignerInfo(wc_PKCS7* pkcs7, byte* in, word32 inSz, /* Get digestEncryptionAlgorithm (signatureAlgorithm) - parse manually * so we can decode id-RSASSA-PSS parameters in all builds. */ #if defined(WC_RSA_PSS) && !defined(NO_RSA) - pkcs7->pssSaltLen = -1; - pkcs7->pssHashType = -1; - pkcs7->pssMgf = -1; + pkcs7->pssSaltLen = PKCS7_PSS_PARAM_UNSET; + pkcs7->pssHashType = PKCS7_PSS_PARAM_UNSET; + pkcs7->pssMgf = PKCS7_PSS_PARAM_UNSET; #endif if (ret == 0) { int algoSeqLen = 0; diff --git a/wolfssl/wolfcrypt/pkcs7.h b/wolfssl/wolfcrypt/pkcs7.h index 40a381a787d..8cf343e0eac 100644 --- a/wolfssl/wolfcrypt/pkcs7.h +++ b/wolfssl/wolfcrypt/pkcs7.h @@ -390,10 +390,11 @@ struct wc_PKCS7 { #if defined(WC_RSA_PSS) && !defined(NO_RSA) /* Decoded from SignerInfo.signatureAlgorithm.parameters (id-RSASSA-PSS). - * Used on verify; -1 when not applicable. */ - int pssSaltLen; /* salt length from RSASSA-PSS-params; -1 if N/A */ - int pssHashType; /* wc_HashType from RSASSA-PSS-params; -1 if N/A */ - int pssMgf; /* MGF from RSASSA-PSS-params (e.g. WC_MGF1SHA256); -1 if N/A */ + * PKCS7_PSS_PARAM_UNSET when not yet decoded; verify path falls back to + * RSA_PSS_SALT_LEN_DEFAULT / digest algorithm defaults. */ + int pssSaltLen; + int pssHashType; + int pssMgf; #endif /* !! NEW DATA MEMBERS MUST BE ADDED AT END !! */