Skip to content

Commit 4304044

Browse files
committed
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 <sameeh@wolfssl.com>
1 parent 63b9d13 commit 4304044

File tree

15 files changed

+1029
-76
lines changed

15 files changed

+1029
-76
lines changed

.github/workflows/os-check.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ jobs:
5353
'--enable-opensslall --enable-opensslextra CPPFLAGS=-DWC_RNG_SEED_CB',
5454
'--enable-opensslall --enable-opensslextra
5555
CPPFLAGS=''-DWC_RNG_SEED_CB -DWOLFSSL_NO_GETPID'' ',
56+
# PKCS#7 with RSA-PSS (CMS RSASSA-PSS signers)
57+
'--enable-pkcs7 CPPFLAGS=-DWC_RSA_PSS',
5658
'--enable-opensslextra CPPFLAGS=''-DWOLFSSL_NO_CA_NAMES'' ',
5759
'--enable-opensslextra=x509small',
5860
'CPPFLAGS=''-DWOLFSSL_EXTRA'' ',

doc/dox_comments/header_files/cryptocb.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@
5252
}
5353
}
5454
#endif
55+
#if defined(WC_RSA_PSS) && !defined(NO_RSA)
56+
if (info->pk.type == WC_PK_TYPE_RSA_PSS) {
57+
// RSA-PSS sign/verify
58+
ret = wc_RsaPSS_Sign_ex(
59+
info->pk.rsa.in, info->pk.rsa.inLen,
60+
info->pk.rsa.out, *info->pk.rsa.outLen,
61+
WC_HASH_TYPE_SHA256, WC_MGF1SHA256,
62+
RSA_PSS_SALT_LEN_DEFAULT,
63+
info->pk.rsa.key, info->pk.rsa.rng);
64+
}
65+
#endif
5566
#ifdef HAVE_ECC
5667
if (info->pk.type == WC_PK_TYPE_ECDSA_SIGN) {
5768
// ECDSA

doc/dox_comments/header_files/doxygen_pages.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
<li>\ref MD5</li>
5252
<li>\ref Password</li>
5353
<li>\ref PKCS7</li>
54+
<li>\ref PKCS7_RSA_PSS</li>
5455
<li>\ref PKCS11</li>
5556
<li>\ref Poly1305</li>
5657
<li>\ref RIPEMD</li>
@@ -97,4 +98,13 @@
9798
\sa wc_CryptoCb_AesSetKey
9899
\sa \ref Crypto Callbacks
99100
*/
101+
/*!
102+
\page PKCS7_RSA_PSS PKCS#7 RSA-PSS (CMS)
103+
PKCS#7 SignedData supports RSA-PSS signers (CMS RSASSA-PSS). When WC_RSA_PSS
104+
is defined, use wc_PKCS7_InitWithCert with a signer certificate that has
105+
RSA-PSS (id-RSASSA-PSS) and set hashOID and optional rng; encode produces
106+
full RSASSA-PSS-params (hashAlgorithm, mgfAlgorithm, saltLength,
107+
trailerField). Verify accepts NULL, empty, or absent parameters with
108+
RFC defaults. See \ref PKCS7 for the main API.
109+
*/
100110

doc/dox_comments/header_files/pkcs7.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ int wc_PKCS7_EncodeData(wc_PKCS7* pkcs7, byte* output,
147147
148148
\brief This function builds the PKCS7 signed data content type, encoding
149149
the PKCS7 structure into a buffer containing a parsable PKCS7
150-
signed data packet.
150+
signed data packet. For RSA-PSS signers (WC_RSA_PSS), see \ref PKCS7_RSA_PSS.
151151
152152
\return Success On successfully encoding the PKCS7 data into the buffer,
153153
returns the index parsed up to in the PKCS7 structure. This index also

examples/configs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Example wolfSSL configuration file templates for use when autoconf is not availa
2323
* `user_settings_openssl_compat.h`: OpenSSL compatibility layer for drop-in replacement. Enables OPENSSL_ALL and related APIs.
2424
* `user_settings_baremetal.h`: Bare metal configuration. No filesystem, static memory only, minimal footprint.
2525
* `user_settings_rsa_only.h`: RSA-only configuration (no ECC). For legacy systems requiring RSA cipher suites.
26-
* `user_settings_pkcs7.h`: PKCS#7/CMS configuration for signing and encryption. S/MIME, firmware signing.
26+
* `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.
2727
* `user_settings_ca.h`: Certificate Authority / PKI operations. Certificate generation, signing, CRL, OCSP.
2828
* `user_settings_wolfboot_keytools.h`: wolfBoot key generation and signing tool. Supports ECC, RSA, ED25519, ED448, and post-quantum (ML-DSA/Dilithium, LMS, XMSS).
2929
* `user_settings_wolfssh.h`: Minimum options for building wolfSSH. See comment at top for ./configure used to generate.

examples/configs/user_settings_pkcs7.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ extern "C" {
115115
#undef NO_RSA
116116
#define WOLFSSL_KEY_GEN
117117
#define WC_RSA_NO_PADDING
118+
#define WC_RSA_PSS /* RSA-PSS SignedData (id-RSASSA-PSS); see PKCS7_RSA_PSS */
118119
#else
119120
#define NO_RSA
120121
#endif

tests/api/test_asn.c

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <tests/api/test_asn.h>
2525

2626
#include <wolfssl/wolfcrypt/asn.h>
27+
#include <wolfssl/wolfcrypt/rsa.h>
2728

2829
#if defined(WC_ENABLE_ASYM_KEY_EXPORT) && defined(HAVE_ED25519)
2930
static int test_SetAsymKeyDer_once(byte* privKey, word32 privKeySz, byte* pubKey,
@@ -787,3 +788,135 @@ int test_wolfssl_local_MatchBaseName(void)
787788

788789
return EXPECT_RESULT();
789790
}
791+
792+
/*
793+
* Testing wc_DecodeRsaPssParams with known DER byte arrays.
794+
* Exercises both WOLFSSL_ASN_TEMPLATE and non-template paths.
795+
*/
796+
int test_wc_DecodeRsaPssParams(void)
797+
{
798+
EXPECT_DECLS;
799+
#if defined(WC_RSA_PSS) && !defined(NO_RSA) && !defined(NO_ASN)
800+
enum wc_HashType hash;
801+
int mgf;
802+
int saltLen;
803+
804+
/* SHA-256 / MGF1-SHA-256 / saltLen=32 */
805+
static const byte pssParamsSha256[] = {
806+
0x30, 0x34,
807+
0xA0, 0x0F,
808+
0x30, 0x0D,
809+
0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03,
810+
0x04, 0x02, 0x01,
811+
0x05, 0x00,
812+
0xA1, 0x1C,
813+
0x30, 0x1A,
814+
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D,
815+
0x01, 0x01, 0x08,
816+
0x30, 0x0D,
817+
0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03,
818+
0x04, 0x02, 0x01,
819+
0x05, 0x00,
820+
0xA2, 0x03,
821+
0x02, 0x01, 0x20,
822+
};
823+
824+
/* Hash-only: SHA-256 hash, defaults for MGF and salt */
825+
static const byte pssParamsHashOnly[] = {
826+
0x30, 0x11,
827+
0xA0, 0x0F,
828+
0x30, 0x0D,
829+
0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03,
830+
0x04, 0x02, 0x01,
831+
0x05, 0x00,
832+
};
833+
834+
/* Salt-only: default hash/mgf, saltLen=48 */
835+
static const byte pssParamsSaltOnly[] = {
836+
0x30, 0x05,
837+
0xA2, 0x03,
838+
0x02, 0x01, 0x30,
839+
};
840+
841+
/* NULL tag (05 00) means all defaults */
842+
static const byte pssParamsNull[] = { 0x05, 0x00 };
843+
844+
/* Empty SEQUENCE means all non-default fields omitted => defaults */
845+
static const byte pssParamsEmptySeq[] = { 0x30, 0x00 };
846+
847+
/* --- Test 1: sz=0 => all defaults --- */
848+
hash = WC_HASH_TYPE_NONE;
849+
mgf = 0;
850+
saltLen = 0;
851+
ExpectIntEQ(wc_DecodeRsaPssParams((const byte*)"", 0,
852+
&hash, &mgf, &saltLen), 0);
853+
ExpectIntEQ((int)hash, (int)WC_HASH_TYPE_SHA);
854+
ExpectIntEQ(mgf, WC_MGF1SHA1);
855+
ExpectIntEQ(saltLen, 20);
856+
857+
/* --- Test 2: NULL tag => all defaults --- */
858+
hash = WC_HASH_TYPE_NONE;
859+
mgf = 0;
860+
saltLen = 0;
861+
ExpectIntEQ(wc_DecodeRsaPssParams(pssParamsNull,
862+
(word32)sizeof(pssParamsNull), &hash, &mgf, &saltLen), 0);
863+
ExpectIntEQ((int)hash, (int)WC_HASH_TYPE_SHA);
864+
ExpectIntEQ(mgf, WC_MGF1SHA1);
865+
ExpectIntEQ(saltLen, 20);
866+
867+
/* --- Test 3: Empty SEQUENCE => all defaults --- */
868+
hash = WC_HASH_TYPE_NONE;
869+
mgf = 0;
870+
saltLen = 0;
871+
ExpectIntEQ(wc_DecodeRsaPssParams(pssParamsEmptySeq,
872+
(word32)sizeof(pssParamsEmptySeq), &hash, &mgf, &saltLen), 0);
873+
ExpectIntEQ((int)hash, (int)WC_HASH_TYPE_SHA);
874+
ExpectIntEQ(mgf, WC_MGF1SHA1);
875+
ExpectIntEQ(saltLen, 20);
876+
877+
#ifndef NO_SHA256
878+
/* --- Test 4: SHA-256 / MGF1-SHA-256 / salt=32 --- */
879+
hash = WC_HASH_TYPE_NONE;
880+
mgf = 0;
881+
saltLen = 0;
882+
ExpectIntEQ(wc_DecodeRsaPssParams(pssParamsSha256,
883+
(word32)sizeof(pssParamsSha256), &hash, &mgf, &saltLen), 0);
884+
ExpectIntEQ((int)hash, (int)WC_HASH_TYPE_SHA256);
885+
ExpectIntEQ(mgf, WC_MGF1SHA256);
886+
ExpectIntEQ(saltLen, 32);
887+
888+
/* --- Test 5: Hash only => SHA-256, default MGF/salt --- */
889+
hash = WC_HASH_TYPE_NONE;
890+
mgf = 0;
891+
saltLen = 0;
892+
ExpectIntEQ(wc_DecodeRsaPssParams(pssParamsHashOnly,
893+
(word32)sizeof(pssParamsHashOnly), &hash, &mgf, &saltLen), 0);
894+
ExpectIntEQ((int)hash, (int)WC_HASH_TYPE_SHA256);
895+
ExpectIntEQ(mgf, WC_MGF1SHA1);
896+
ExpectIntEQ(saltLen, 20);
897+
#endif
898+
899+
/* --- Test 6: Salt only => default hash/MGF, salt=48 --- */
900+
hash = WC_HASH_TYPE_NONE;
901+
mgf = 0;
902+
saltLen = 0;
903+
ExpectIntEQ(wc_DecodeRsaPssParams(pssParamsSaltOnly,
904+
(word32)sizeof(pssParamsSaltOnly), &hash, &mgf, &saltLen), 0);
905+
ExpectIntEQ((int)hash, (int)WC_HASH_TYPE_SHA);
906+
ExpectIntEQ(mgf, WC_MGF1SHA1);
907+
ExpectIntEQ(saltLen, 48);
908+
909+
/* --- Test 7: NULL pointer → BAD_FUNC_ARG --- */
910+
ExpectIntEQ(wc_DecodeRsaPssParams(NULL, 10, &hash, &mgf, &saltLen),
911+
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
912+
913+
/* --- Test 8: Bad leading tag => ASN_PARSE_E --- */
914+
{
915+
static const byte badTag[] = { 0x01, 0x00 };
916+
ExpectIntEQ(wc_DecodeRsaPssParams(badTag, (word32)sizeof(badTag),
917+
&hash, &mgf, &saltLen), WC_NO_ERR_TRACE(ASN_PARSE_E));
918+
}
919+
920+
#endif /* WC_RSA_PSS && !NO_RSA && !NO_ASN */
921+
return EXPECT_RESULT();
922+
}

tests/api/test_asn.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ int test_SetAsymKeyDer(void);
2828
int test_GetSetShortInt(void);
2929
int test_wc_IndexSequenceOf(void);
3030
int test_wolfssl_local_MatchBaseName(void);
31+
int test_wc_DecodeRsaPssParams(void);
3132

3233
#define TEST_ASN_DECLS \
3334
TEST_DECL_GROUP("asn", test_SetAsymKeyDer), \
3435
TEST_DECL_GROUP("asn", test_GetSetShortInt), \
3536
TEST_DECL_GROUP("asn", test_wc_IndexSequenceOf), \
36-
TEST_DECL_GROUP("asn", test_wolfssl_local_MatchBaseName)
37+
TEST_DECL_GROUP("asn", test_wolfssl_local_MatchBaseName), \
38+
TEST_DECL_GROUP("asn", test_wc_DecodeRsaPssParams)
3739

3840
#endif /* WOLFCRYPT_TEST_ASN_H */

tests/api/test_pkcs7.c

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,98 @@ int test_wc_PKCS7_EncodeSignedData(void)
947947
} /* END test_wc_PKCS7_EncodeSignedData */
948948

949949

950+
/*
951+
* Testing wc_PKCS7_EncodeSignedData() with RSA-PSS signer certificate.
952+
* Uses certs/rsapss/client-rsapss.der and client-rsapss-priv.der.
953+
* Requires both encode and round-trip verify to succeed.
954+
*/
955+
#if defined(HAVE_PKCS7) && defined(WC_RSA_PSS) && !defined(NO_RSA) && \
956+
!defined(NO_FILESYSTEM) && !defined(NO_SHA256)
957+
int test_wc_PKCS7_EncodeSignedData_RSA_PSS(void)
958+
{
959+
EXPECT_DECLS;
960+
PKCS7* pkcs7 = NULL;
961+
WC_RNG rng;
962+
byte output[FOURK_BUF];
963+
byte cert[FOURK_BUF];
964+
byte key[FOURK_BUF];
965+
word32 outputSz = (word32)sizeof(output);
966+
word32 certSz = 0;
967+
word32 keySz = 0;
968+
XFILE fp = XBADFILE;
969+
byte data[] = "Test data for RSA-PSS SignedData.";
970+
971+
XMEMSET(&rng, 0, sizeof(WC_RNG));
972+
XMEMSET(output, 0, outputSz);
973+
XMEMSET(cert, 0, sizeof(cert));
974+
XMEMSET(key, 0, sizeof(key));
975+
976+
ExpectIntEQ(wc_InitRng(&rng), 0);
977+
ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId));
978+
979+
ExpectTrue((fp = XFOPEN("./certs/rsapss/client-rsapss.der", "rb")) != XBADFILE);
980+
if (fp != XBADFILE) {
981+
ExpectIntGT(certSz = (word32)XFREAD(cert, 1, sizeof(cert), fp), 0);
982+
XFCLOSE(fp);
983+
fp = XBADFILE;
984+
}
985+
986+
ExpectTrue((fp = XFOPEN("./certs/rsapss/client-rsapss-priv.der", "rb")) != XBADFILE);
987+
if (fp != XBADFILE) {
988+
ExpectIntGT(keySz = (word32)XFREAD(key, 1, sizeof(key), fp), 0);
989+
XFCLOSE(fp);
990+
fp = XBADFILE;
991+
}
992+
993+
ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, cert, certSz), 0);
994+
995+
if (pkcs7 != NULL) {
996+
/* Force RSA-PSS so SignerInfo uses id-RSASSA-PSS (cert may use RSA
997+
* in subjectPublicKeyInfo). WC_RSA_PSS is guaranteed by outer guard. */
998+
pkcs7->publicKeyOID = RSAPSSk;
999+
1000+
pkcs7->content = data;
1001+
pkcs7->contentSz = (word32)sizeof(data);
1002+
pkcs7->contentOID = DATA;
1003+
pkcs7->hashOID = SHA256h;
1004+
pkcs7->encryptOID = RSAk;
1005+
pkcs7->privateKey = key;
1006+
pkcs7->privateKeySz = keySz;
1007+
pkcs7->rng = &rng;
1008+
pkcs7->signedAttribs = NULL;
1009+
pkcs7->signedAttribsSz = 0;
1010+
}
1011+
1012+
/* EncodeSignedData with RSA-PSS cert: require encode and verify success */
1013+
{
1014+
int outLen = wc_PKCS7_EncodeSignedData(pkcs7, output, outputSz);
1015+
ExpectIntGT(outLen, 0);
1016+
if (outLen > 0) {
1017+
int verifyRet = wc_PKCS7_VerifySignedData(pkcs7, output,
1018+
(word32)outLen);
1019+
ExpectIntEQ(verifyRet, 0);
1020+
1021+
if (pkcs7 != NULL) {
1022+
/* Verify decoded RSASSA-PSS parameters match what we
1023+
* encoded:
1024+
* hashAlgorithm = SHA-256
1025+
* maskGenAlgorithm = MGF1-SHA-256
1026+
* saltLength = 32 (== SHA-256 digest length) */
1027+
ExpectIntEQ(pkcs7->pssHashType, (int)WC_HASH_TYPE_SHA256);
1028+
ExpectIntEQ(pkcs7->pssMgf, WC_MGF1SHA256);
1029+
ExpectIntEQ(pkcs7->pssSaltLen, 32);
1030+
}
1031+
}
1032+
}
1033+
1034+
wc_PKCS7_Free(pkcs7);
1035+
DoExpectIntEQ(wc_FreeRng(&rng), 0);
1036+
1037+
return EXPECT_RESULT();
1038+
} /* END test_wc_PKCS7_EncodeSignedData_RSA_PSS */
1039+
#endif
1040+
1041+
9501042
/*
9511043
* Testing wc_PKCS7_EncodeSignedData_ex() and wc_PKCS7_VerifySignedData_ex()
9521044
*/

tests/api/test_pkcs7.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ int test_wc_PKCS7_Init(void);
2929
int test_wc_PKCS7_InitWithCert(void);
3030
int test_wc_PKCS7_EncodeData(void);
3131
int test_wc_PKCS7_EncodeSignedData(void);
32+
#if defined(HAVE_PKCS7) && defined(WC_RSA_PSS) && !defined(NO_RSA) && \
33+
!defined(NO_FILESYSTEM) && !defined(NO_SHA256)
34+
int test_wc_PKCS7_EncodeSignedData_RSA_PSS(void);
35+
#endif
3236
int test_wc_PKCS7_EncodeSignedData_ex(void);
3337
int test_wc_PKCS7_VerifySignedData_RSA(void);
3438
int test_wc_PKCS7_VerifySignedData_ECC(void);
@@ -55,10 +59,19 @@ int test_wc_PKCS7_VerifySignedData_PKCS7ContentSeq(void);
5559
TEST_DECL_GROUP("pkcs7", test_wc_PKCS7_New), \
5660
TEST_DECL_GROUP("pkcs7", test_wc_PKCS7_Init)
5761

62+
#if defined(HAVE_PKCS7) && defined(WC_RSA_PSS) && !defined(NO_RSA) && \
63+
!defined(NO_FILESYSTEM) && !defined(NO_SHA256)
64+
#define TEST_PKCS7_RSA_PSS_SD_DECL \
65+
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeSignedData_RSA_PSS),
66+
#else
67+
#define TEST_PKCS7_RSA_PSS_SD_DECL
68+
#endif
69+
5870
#define TEST_PKCS7_SIGNED_DATA_DECLS \
5971
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_InitWithCert), \
6072
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeData), \
6173
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeSignedData), \
74+
TEST_PKCS7_RSA_PSS_SD_DECL \
6275
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeSignedData_ex), \
6376
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_RSA), \
6477
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_ECC), \

0 commit comments

Comments
 (0)