From 2e34433c52af53cd183649cd3787addad98a10af Mon Sep 17 00:00:00 2001 From: Hideki Miyazaki Date: Thu, 4 Jun 2026 10:32:27 +0900 Subject: [PATCH 1/3] enforce trailerField==1 in DecodeRsaPssParams --- tests/api/test_asn.c | 41 +++++++++++++++++++++++++++++++++++++++++ wolfcrypt/src/asn.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/tests/api/test_asn.c b/tests/api/test_asn.c index e9eab25bdf..e40ff8cf4a 100644 --- a/tests/api/test_asn.c +++ b/tests/api/test_asn.c @@ -1097,6 +1097,47 @@ int test_wc_DecodeRsaPssParams(void) &hash, &mgf, &saltLen), WC_NO_ERR_TRACE(ASN_PARSE_E)); } + /* --- Test 9: trailerField = 1 (trailerFieldBC) => valid in all modes --- */ + /* SEQUENCE { [3] CONSTRUCTED { INTEGER 1 } } = 30 05 a3 03 02 01 01 */ + { + static const byte trailerValid[] = { + 0x30, 0x05, 0xa3, 0x03, 0x02, 0x01, 0x01 + }; + hash = WC_HASH_TYPE_NONE; + mgf = 0; + saltLen = 0; + ExpectIntEQ(wc_DecodeRsaPssParams(trailerValid, + (word32)sizeof(trailerValid), &hash, &mgf, &saltLen), 0); + ExpectIntEQ((int)hash, (int)WC_HASH_TYPE_SHA); + ExpectIntEQ(mgf, WC_MGF1SHA1); + ExpectIntEQ(saltLen, 20); + } + +#ifndef WOLFSSL_NO_ASN_STRICT + /* --- Test 10: trailerField = 2 => ASN_PARSE_E (strict mode) --- */ + /* RFC 8017 A.2.3: trailerField SHALL be trailerFieldBC(1). */ + /* SEQUENCE { [3] CONSTRUCTED { INTEGER 2 } } = 30 05 a3 03 02 01 02 */ + { + static const byte trailerTwo[] = { + 0x30, 0x05, 0xa3, 0x03, 0x02, 0x01, 0x02 + }; + ExpectIntEQ(wc_DecodeRsaPssParams(trailerTwo, + (word32)sizeof(trailerTwo), &hash, &mgf, &saltLen), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + } + + /* --- Test 11: trailerField = 0 => ASN_PARSE_E (strict mode) --- */ + /* SEQUENCE { [3] CONSTRUCTED { INTEGER 0 } } = 30 05 a3 03 02 01 00 */ + { + static const byte trailerZero[] = { + 0x30, 0x05, 0xa3, 0x03, 0x02, 0x01, 0x00 + }; + ExpectIntEQ(wc_DecodeRsaPssParams(trailerZero, + (word32)sizeof(trailerZero), &hash, &mgf, &saltLen), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + } +#endif /* !WOLFSSL_NO_ASN_STRICT */ + #endif /* WC_RSA_PSS && !NO_RSA && !NO_ASN */ return EXPECT_RESULT(); } diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index bb48198a90..099921cc7e 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -8074,6 +8074,9 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, { DECL_ASNGETDATA(dataASN, rsaPssParamsASN_Length); int ret = 0; +#ifndef WOLFSSL_NO_ASN_STRICT + word16 trailerVal = 1; +#endif word16 sLen = 20; /* Default values. */ @@ -8089,6 +8092,10 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, GetASN_OID(&dataASN[RSAPSSPARAMSASN_IDX_MGFHOID], oidHashType); /* Place the salt length into 16-bit var sLen. */ GetASN_Int16Bit(&dataASN[RSAPSSPARAMSASN_IDX_SALTLENINT], &sLen); +#ifndef WOLFSSL_NO_ASN_STRICT + /* Capture trailerField value for RFC 8017 A.2.3 validation. */ + GetASN_Int16Bit(&dataASN[RSAPSSPARAMSASN_IDX_TRAILERINT], &trailerVal); +#endif /* Decode the algorithm identifier. */ ret = GetASN_Items(rsaPssParamsASN, dataASN, rsaPssParamsASN_Length, 1, params, &inOutIdx, sz); @@ -8105,6 +8112,15 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, word32 oid = dataASN[RSAPSSPARAMSASN_IDX_MGFHOID].data.oid.sum; ret = RsaPssHashOidToMgf1(oid, mgf); } +#ifndef WOLFSSL_NO_ASN_STRICT + /* RFC 8017 A.2.3: trailerField SHALL be trailerFieldBC(1). */ + if ((ret == 0) && (dataASN[RSAPSSPARAMSASN_IDX_TRAILERINT].tag != 0)) { + if (trailerVal != 1) { + WOLFSSL_MSG("DecodeRsaPssParams: trailerField must be 1"); + ret = ASN_PARSE_E; + } + } +#endif if (ret == 0) { *saltLen = sLen; } @@ -8251,12 +8267,24 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, } if (ret == 0) { ret = GetInteger16Bit(params, &idx, sz); +#ifndef WOLFSSL_NO_ASN_STRICT + /* RFC 8017 A.2.3: trailerField SHALL be trailerFieldBC(1). */ + if (ret == 1) { + ret = 0; + } + else { + WOLFSSL_MSG("DecodeRsaPssParams: trailerField must be 1"); + if (ret >= 0) + ret = ASN_PARSE_E; + } +#else if (ret > 0) { ret = 0; } else if (ret != 0) { WOLFSSL_MSG("DecodeRsaPssParams: fail at trailer_value"); } +#endif } } } From b06ced11664298cbe95eabfbd0700f6259473673 Mon Sep 17 00:00:00 2001 From: Hideki Miyazaki Date: Fri, 5 Jun 2026 12:20:09 +0900 Subject: [PATCH 2/3] Addressed Copilot comments --- wolfcrypt/src/asn.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 099921cc7e..1a85dc97b4 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -8272,10 +8272,12 @@ static int DecodeRsaPssParams(const byte* params, word32 sz, if (ret == 1) { ret = 0; } - else { + else if (ret >= 0) { WOLFSSL_MSG("DecodeRsaPssParams: trailerField must be 1"); - if (ret >= 0) - ret = ASN_PARSE_E; + ret = ASN_PARSE_E; + } + else { + WOLFSSL_MSG("DecodeRsaPssParams: fail at trailer_value"); } #else if (ret > 0) { From 7d74caac6d35c10b20e65a3d46ae89f31dfd77ab Mon Sep 17 00:00:00 2001 From: Hideki Miyazaki Date: Wed, 10 Jun 2026 07:02:46 +0900 Subject: [PATCH 3/3] Addressed review comments --- ChangeLog.md | 11 +++++++++++ tests/api/test_asn.c | 14 ++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index ef01745e95..d1ea4c31ff 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -2,6 +2,17 @@ ## Enhancements +* **Behavioral change (RSA-PSS trailerField enforcement)**: `DecodeRsaPssParams` + (and its public wrapper `wc_DecodeRsaPssParams`) now enforces RFC 8017 A.2.3, + which mandates `trailerField == trailerFieldBC(1)`. In the default build + (i.e., without `WOLFSSL_NO_ASN_STRICT`), any certificate or CMS/PKCS#7 + structure whose RSA-PSS parameters contain a `trailerField` value other than 1 + is now rejected with `ASN_PARSE_E`. Previously, any positive integer value was + silently accepted. This affects all call paths that decode RSA-PSS algorithm + parameters, including X.509 certificate parsing and PKCS#7 signature + verification. Users who need to interoperate with non-conformant peers can + define `WOLFSSL_NO_ASN_STRICT` to restore the previous permissive behavior. + * **BREAKING (FIPS 205 SLH-DSA)**: `wc_SlhDsaKey_SignHash`, `wc_SlhDsaKey_SignHashDeterministic`, `wc_SlhDsaKey_SignHashWithRandom`, and `wc_SlhDsaKey_VerifyHash` now take the **caller-pre-hashed message digest** diff --git a/tests/api/test_asn.c b/tests/api/test_asn.c index e40ff8cf4a..d6e5ce522b 100644 --- a/tests/api/test_asn.c +++ b/tests/api/test_asn.c @@ -1136,6 +1136,20 @@ int test_wc_DecodeRsaPssParams(void) (word32)sizeof(trailerZero), &hash, &mgf, &saltLen), WC_NO_ERR_TRACE(ASN_PARSE_E)); } + + /* --- Test 12: trailerField = 256 (multi-byte INTEGER) => ASN_PARSE_E --- + * Exercises the 2-byte integer branch in GetInteger16Bit (non-template) + * and the len==2 case of ASN_DATA_TYPE_WORD16 (template path). + * SEQUENCE { [3] CONSTRUCTED { INTEGER 256 } } = 30 06 a3 04 02 02 01 00 + */ + { + static const byte trailerMultiByte[] = { + 0x30, 0x06, 0xa3, 0x04, 0x02, 0x02, 0x01, 0x00 + }; + ExpectIntEQ(wc_DecodeRsaPssParams(trailerMultiByte, + (word32)sizeof(trailerMultiByte), &hash, &mgf, &saltLen), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + } #endif /* !WOLFSSL_NO_ASN_STRICT */ #endif /* WC_RSA_PSS && !NO_RSA && !NO_ASN */