PKCS#7, standardized as Cryptographic Message Syntax (CMS) in RFC 5652, is a common format for signing structured data. Signed firmware updates, signed configuration packages, and certificate-based authentication workflows all rely on PKCS#7 SignedData to ensure integrity and authenticity.wolfSSL now supports RSA-PSS (RSASSA-PSS) signatures in PKCS#7 SignedData, for both generation and verification. This lets applications move from the legacy RSA PKCS#1 v1.5 signature scheme to the probabilistic RSA-PSS construction recommended by NIST and required by FIPS 186-5 for new RSA signature deployments.
Why RSA-PSS?
RSA-PSS is defined in PKCS#1 v2.x (RFC 8017). Unlike the deterministic PKCS#1 v1.5 signature padding, PSS introduces randomness through a salt value during signature generation. This probabilistic construction provides a tighter security reduction to the RSA problem and improved resistance to certain padding-oracle style attacks.Many compliance frameworks (FIPS 186-5, CABForum, Common Criteria profiles) now prefer or require RSA-PSS over PKCS#1 v1.5 for new deployments.
What Changes Inside CMS
Supporting RSA-PSS inside a CMS SignedData structure is not a drop-in replacement for PKCS#1 v1.5. The key differences:SignerInfo.signatureAlgorithm must carry the OID id- RSASSA-PSS (1.2.840.113549.1.1.10) instead of sha256WithRSAEncryption or similar. Unlike PKCS#1 v1.5 signature OIDs, id-RSASSA-PSS requires explicit RSASSA-PSS-params in the AlgorithmIdentifier, as defined in RFC 4055:
RSASSA-PSS-params ::= SEQUENCE {
hashAlgorithm [0] HashAlgorithm DEFAULT sha1,
maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1,
saltLength [2] INTEGER DEFAULT 20,
trailerField [3] TrailerField DEFAULT trailerFieldBC
}
The signature covers a raw hash, not a DigestInfo. With PKCS#1 v1.5, RSA signs a DER-
encoded DigestInfo structure wrapping the hash. With PSS, the raw hash digest is signed
directly (the same behavior as ECDSA). This affects both the signing and verification code
paths.
Build Configuration
RSA-PSS PKCS#7 requires two features to be enabled at build time. With autotools:
./configure --enable-rsapss --enable-pkcs7
With user_settings.h:
#define WC_RSA_PSS #define HAVE_PKCS7
Note: WC_RSA_PSS is automatically enabled when TLS 1.3 is enabled (–enable-tls13),
since TLS 1.3 mandates RSA-PSS for RSA certificate verify messages.All RSA-PSS
PKCS#7 code is guarded by:
#if defined(WC_RSA_PSS) && !defined(NO_RSA)
If WC_RSA_PSS is not defined, the RSA-PSS code paths are compiled out entirely, and any
incoming PKCS#7 blob with id-RSASSA-PSS will fail to parse.
How PSS Parameters Are Derived
On the encode path, wolfSSL derives all PSS parameters from a single setting: pkcs7→hashOID. There is no separate API to set the salt length, MGF hash, or trailer field independently. The policy is:
| Derived Parameter | Value |
|---|---|
| PSS hash algorithm | Same as hashOID |
| MGF algorithm | MGF1 |
| MGF1 hash | Same as hashOID |
| Salt length | Equal to hash digest length |
| Trailer field | 0xBC (standard) |
| hashOID | PSS Hash | MGF1 Hash | Salt Length |
|---|---|---|---|
| SHA256h | SHA-256 | SHA-256 | 32 bytes |
| SHA384h | SHA-384 | SHA-384 | 48 bytes |
| SHA512h | SHA-512 | SHA-512 | 64 bytes |
This profile (salt length = hash length, MGF1 hash = signature hash) is the most common CMS/RFC 4055 configuration and matches OpenSSL's default behavior. Custom salt lengths on the encode path are not currently supported; the code has a placeholder for a future pkcs7->pssSaltLen override if needed.On the decode/verify path, wolfSSL parses whatever RSASSA-PSS-params are present in the incoming SignerInfo. If parameters are absent or empty, the RFC 4055 defaults apply: SHA-1, MGF1-SHA1, salt length 20. This is important for interoperability — blobs from systems that omit PSS params will be verified using SHA-1 defaults, not SHA-256. After successful verification, the parsed parameters are available in the PKCS7 struct:
pkcs7→pssParamsPresent /* 1 if PSS params were decoded */
pkcs7→pssHashType /* enum wc_HashType */
pkcs7→pssMgf /* e.g. WC_MGF1SHA256 */
pkcs7→pssSaltLen /* e.g. 32 */
Generating RSA-PSS Certificates
Before using RSA-PSS with PKCS#7, you need an RSA-PSS key pair and certificate. Using
OpenSSL:
# Generate a 2048-bit RSA-PSS private key constrained to SHA-256 openssl genpkey -algorithm RSA-PSS \ -pkeyopt rsa_keygen_bits:2048 \ -pkeyopt rsa_pss_keygen_md:sha256 \ -pkeyopt rsa_pss_keygen_mgf1_md:sha256 \ -pkeyopt rsa_pss_keygen_saltlen:32 \ -out client-rsapss-priv.pem # Export DER private key (for wolfSSL) openssl pkey -in client-rsapss-priv.pem -outform DER \ -out client-rsapss-priv.der # Generate self-signed certificate openssl req -new -x509 -key client-rsapss-priv.pem \ -days 3650 -out client-rsapss.pem \ -subj "/C=US/ST=Montana/L=Bozeman/O=wolfSSL/CN=Client-RSAPSS" # Export DER certificate (for wolfSSL) openssl x509 -in client-rsapss.pem -outform DER \ -out client-rsapss.der
wolfSSL ships pre-generated RSA-PSS test certificates in certs/rsapss/ (2048-bit with SHA-
256, and 3072-bit with SHA-384).
Example: Signing and Verifying PKCS#7 SignedData with RSA-PSS
Below is a complete example showing the encode and verify round-trip.
#include <wolfssl/options.h>
#include <wolfssl/wolfcrypt/pkcs7.h>
#include <wolfssl/wolfcrypt/asn_public.h>
int rsapss_pkcs7_signeddata_example(
const byte* cert, word32 certSz,
const byte* privKey, word32 privKeySz,
const byte* content, word32 contentSz)
{
int ret;
PKCS7* pkcs7 = NULL;
WC_RNG rng;
byte output[4096];
word32 outputSz = sizeof(output);
int signedSz;
ret = wc_InitRng(&rng);
if (ret != 0) return ret;
pkcs7 = wc_PKCS7_New(NULL, INVALID_DEVID);
if (pkcs7 == NULL) { wc_FreeRng(&rng); return MEMORY_E; }
/* --- ENCODE --- */
ret = wc_PKCS7_InitWithCert(pkcs7, cert, certSz);
if (ret != 0) goto cleanup;
pkcs7->publicKeyOID = RSAPSSk; /* use id-RSASSA-PSS */
pkcs7->content = (byte*)content;
pkcs7->contentSz = contentSz;
pkcs7->contentOID = DATA;
pkcs7->hashOID = SHA256h; /* controls PSS hash, MGF, salt */
pkcs7->encryptOID = RSAk;
pkcs7->privateKey = (byte*)privKey;
pkcs7->privateKeySz = privKeySz;
pkcs7->rng = &rng; /* required: PSS is probabilistic */
pkcs7->signedAttribs = NULL;
pkcs7->signedAttribsSz = 0;
signedSz = wc_PKCS7_EncodeSignedData(pkcs7, output, outputSz);
if (signedSz <= 0) { ret = signedSz; goto cleanup; }
/* --- VERIFY --- */
/* Re-init for verification (resets internal state) */
wc_PKCS7_Free(pkcs7);
pkcs7 = wc_PKCS7_New(NULL, INVALID_DEVID);
if (pkcs7 == NULL) { ret = MEMORY_E; goto cleanup; }
ret = wc_PKCS7_InitWithCert(pkcs7, cert, certSz);
if (ret != 0) goto cleanup;
ret = wc_PKCS7_VerifySignedData(pkcs7, output, (word32)signedSz);
if (ret != 0) goto cleanup;
/* After verify, PSS params are available for inspection */
if (pkcs7->pssParamsPresent) {
printf("PSS hash type: %d\n", pkcs7->pssHashType);
printf("PSS MGF: %d\n", pkcs7->pssMgf);
printf("PSS salt len: %d\n", pkcs7->pssSaltLen);
}
cleanup:
wc_PKCS7_Free(pkcs7);
wc_FreeRng(&rng);
return ret;
}
Key points about the API usage:
- publicKeyOID = RSAPSSk selects the RSA-PSS code path. Without this, standard PKCS#1 v1.5 is used. (RSAPSSk is the key-type constant; CTC_RSASSAPSS is the equivalent signature-algorithm OID constant — both resolve to 654, but RSAPSSk is the correct choice for publicKeyOID.)
- hashOID = SHA256h is the single knob that controls the PSS hash algorithm, MGF1hash, and salt length.
- rng must be set. PSS signing requires a random number generator for salt generation. If rng is NULL, wc_PKCS7_RsaPssSign returns BAD_FUNC_ARG.
- Content is set via struct fields, not as arguments to wc_PKCS7_EncodeSignedData.
The function signature is wc_PKCS7_EncodeSignedData(pkcs7, output, outputSz). - Verification is automatic. When wc_PKCS7_VerifySignedData encounters id-RSASSA-PSS in the Signer Info, it calls wc_DecodeRsaPssParams to extract the PSS parameters, then dispatches to wc_RsaPSS_Verify_ex /wc_RsaPSS_CheckPadding_ex2 internally. No caller-side configuration is needed for verification.
Verifying with OpenSSL (Interoperability)
To confirm that a wolfSSL-generated PKCS#7 SignedData blob is valid CMS, you can verify
it with OpenSSL:
# Write the DER output from wolfSSL to a file, then: openssl cms -verify \ -in wolfssl_signed.der -inform DER \ -CAfile client-rsapss.pem \ -out /dev/null Similarly, to verify an OpenSSL-generated RSA-PSS CMS blob with wolfSSL: # Generate with OpenSSL openssl cms -sign \ -in plaintext.txt \ -signer client-rsapss.pem \ -inkey client-rsapss-priv.pem \ -keyopt rsa_padding_mode:pss \ -keyopt rsa_pss_saltlen:32 \ -outform DER -out signed.der -nodetach
Then load signed.der into wolfSSL and call wc_PKCS7_VerifySignedData. The parser will decode the RSASSA-PSS-params from the SignerInfo and verify accordingly.
What the ASN.1 Looks Like
A wolfSSL-generated RSA-PSS SignedData encodes the SignerInfo signatureAlgorithm as:
SEQUENCE {
OBJECT IDENTIFIER 1.2.840.113549.1.1.10 (id-RSASSA-PSS)
SEQUENCE { -- RSASSA-PSS-params
[0] { -- hashAlgorithm
SEQUENCE {
OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.1 (sha-256)
NULL
}
}
[1] { -- maskGenAlgorithm
SEQUENCE {
OBJECT IDENTIFIER 1.2.840.113549.1.1.8 (id-mgf1)
SEQUENCE {
OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.1 (sha-256)
NULL
}
}
}
[2] { -- saltLength
INTEGER 32
}
[3] { -- trailerField
INTEGER 1
}
}
}
wolfSSL always encodes the full parameter set, including the trailerField, even though it is the default value. This is the maximally explicit encoding and avoids interoperability issues with implementations that don't handle default-value omission correctly.You can inspect this yourself with:
openssl asn1parse -in wolfssl_signed.der -inform DER
Standards
The implementation follows:
- RFC 5652 (CMS) — SignedData structure, SignerInfo encoding
- RFC 4055 (RSASSA-PSS AlgorithmIdentifier) — PSS parameter encoding in AlgorithmIdentifier; default values (SHA-1/MGF1-SHA1/salt=20) when params are absent
- RFC 8017 (PKCS#1 v2.2) — RSA-PSS signature generation and verification
primitives
Specific conformance notes:
- The digestAlgorithm field in SignerInfo is set to the hash algorithm (e.g. SHA-256). Only the signatureAlgorithm field uses id-RSASSA-PSS. This matches the CMS convention where digestAlgorithm identifies the hash, and signatureAlgorithm identifies the signature scheme.
- wolfSSL encodes all four RSASSA-PSS-params fields explicitly. On decode, absent params default to the RFC 4055 values (SHA-1, MGF1-SHA1, salt length 20).
Current Limitations
- Encode-path salt length is not user-configurable. It is always set to the hash digest length. The decode path handles arbitrary salt lengths from external blobs.
- SHA-224 is supported in pkcs7_hash2mgf but not listed in the commonly tested configurations. SHA-256/384/512 are the primary targets.
- No SHA-3 support for MGF1. The MGF1 mapping only covers SHA-1 and SHA-2 family hashes. SHA-3 hash types fall through to WC_MGF1NONE and will be rejected.
Conclusion
RSA-PSS support for PKCS#7 SignedData lets wolfSSL applications adopt the signature scheme recommended by current NIST and FIPS standards while staying within existing CMS-based workflows. The API change is minimal — set publicKeyOID = RSAPSSk and hashOID to the desired hash, and the implementation handles the PSS parameter encoding, salt generation, and verification logic internally.For questions or integration help, contact support@wolfssl.com or visit the wolfSSL GitHub.
If you have questions about any of the above, please contact us at facts@wolfssl.com or call us at +1 425 245 8247.
Download wolfSSL Now

