@@ -5884,3 +5884,218 @@ int test_wc_PKCS7_VerifySignedData_TruncCertSetTag(void)
58845884 return EXPECT_RESULT ();
58855885}
58865886
5887+ #if defined(HAVE_PKCS7 ) && !defined(NO_RSA ) && !defined(NO_SHA256 ) && \
5888+ defined(USE_CERT_BUFFERS_2048 )
5889+ /*
5890+ * Encode a minimal RSA SignedData (SHA-256) with the requested DigestInfo
5891+ * AlgorithmIdentifier parameter encoding. When signedAttribs is zero the
5892+ * signature covers the content DigestInfo directly; when non-zero it covers
5893+ * the signed-attributes DigestInfo with a deterministic attribute set
5894+ * (contentType + messageDigest, no signingTime). Either way the signed data is
5895+ * deterministic, so two encodes that differ only in hashParamsAbsent sign the
5896+ * same hash - only the DigestInfo NULL parameters differ. Returns the encoded
5897+ * size (> 0) on success, negative on failure.
5898+ */
5899+ static int pkcs7_sign_digest_params (byte * cert , word32 certSz ,
5900+ byte * key , word32 keySz ,
5901+ byte hashParamsAbsent , byte signedAttribs ,
5902+ byte * out , word32 outSz )
5903+ {
5904+ PKCS7 * pkcs7 = NULL ;
5905+ WC_RNG rng ;
5906+ byte data [] = "wolfSSL PKCS#7 DigestInfo params regression content" ;
5907+ int ret ;
5908+
5909+ XMEMSET (& rng , 0 , sizeof (rng ));
5910+ ret = wc_InitRng (& rng );
5911+ if (ret != 0 )
5912+ return ret ;
5913+
5914+ pkcs7 = wc_PKCS7_New (HEAP_HINT , testDevId );
5915+ if (pkcs7 == NULL ) {
5916+ wc_FreeRng (& rng );
5917+ return MEMORY_E ;
5918+ }
5919+
5920+ ret = wc_PKCS7_Init (pkcs7 , HEAP_HINT , INVALID_DEVID );
5921+ if (ret == 0 )
5922+ ret = wc_PKCS7_InitWithCert (pkcs7 , cert , certSz );
5923+ if (ret == 0 ) {
5924+ if (signedAttribs ) {
5925+ /* Deterministic attributes only (no signingTime) so two encodes
5926+ * stay byte-identical and the cross-signature splice stays valid. */
5927+ pkcs7 -> defaultSignedAttribs = WOLFSSL_CONTENT_TYPE_ATTRIBUTE |
5928+ WOLFSSL_MESSAGE_DIGEST_ATTRIBUTE ;
5929+ }
5930+ else {
5931+ ret = wc_PKCS7_NoDefaultSignedAttribs (pkcs7 );
5932+ }
5933+ }
5934+ if (ret == 0 ) {
5935+ pkcs7 -> content = data ;
5936+ pkcs7 -> contentSz = (word32 )sizeof (data ) - 1 ;
5937+ pkcs7 -> privateKey = key ;
5938+ pkcs7 -> privateKeySz = keySz ;
5939+ pkcs7 -> encryptOID = RSAk ;
5940+ pkcs7 -> hashOID = SHA256h ;
5941+ pkcs7 -> rng = & rng ;
5942+ pkcs7 -> hashParamsAbsent = (hashParamsAbsent != 0 ) ? 1 : 0 ;
5943+
5944+ ret = wc_PKCS7_EncodeSignedData (pkcs7 , out , outSz );
5945+ }
5946+
5947+ wc_PKCS7_Free (pkcs7 );
5948+ wc_FreeRng (& rng );
5949+ return ret ;
5950+ }
5951+
5952+ /*
5953+ * Build a SignedData whose SignerInfo digestAlgorithm parameter encoding does
5954+ * NOT match the encoding of the DigestInfo covered by the RSA signature. The
5955+ * same content is signed twice - once NULL-absent, once NULL-present - and the
5956+ * signature from one encode is spliced over the other message. RSA signatures
5957+ * are fixed length, so this is a same-length byte substitution needing no
5958+ * re-encoding. signerInfoAbsent selects the produced message's SignerInfo
5959+ * digestAlgorithm encoding; the spliced signature then carries the opposite
5960+ * encoding. Returns the message size (> 0) on success, negative on failure.
5961+ */
5962+ static int pkcs7_build_digestparam_mismatch (byte * cert , word32 certSz ,
5963+ byte * key , word32 keySz ,
5964+ byte signedAttribs ,
5965+ byte signerInfoAbsent ,
5966+ byte * out , word32 outSz )
5967+ {
5968+ byte other [FOURK_BUF ];
5969+ int keepSz , otherSz ;
5970+ /* RSA-2048 signature is 256 bytes, wrapped as OCTET STRING 04 82 01 00. */
5971+ const int rsaSigSz = 256 ;
5972+
5973+ /* message that keeps the requested SignerInfo digestAlgorithm encoding */
5974+ keepSz = pkcs7_sign_digest_params (cert , certSz , key , keySz , signerInfoAbsent ,
5975+ signedAttribs , out , outSz );
5976+ if (keepSz <= 0 )
5977+ return keepSz ;
5978+
5979+ /* message whose signature covers the opposite DigestInfo encoding */
5980+ XMEMSET (other , 0 , sizeof (other ));
5981+ otherSz = pkcs7_sign_digest_params (cert , certSz , key , keySz ,
5982+ (byte )!signerInfoAbsent , signedAttribs ,
5983+ other , (word32 )sizeof (other ));
5984+ if (otherSz <= 0 )
5985+ return otherSz ;
5986+
5987+ /* both messages must end with the 256-byte signature OCTET STRING */
5988+ if (keepSz <= rsaSigSz + 4 || otherSz <= rsaSigSz + 4 )
5989+ return -1 ;
5990+ if (out [keepSz - rsaSigSz - 4 ] != 0x04 ||
5991+ other [otherSz - rsaSigSz - 4 ] != 0x04 ) {
5992+ return -1 ;
5993+ }
5994+
5995+ /* splice the opposite-encoding signature over the kept message */
5996+ XMEMCPY (out + keepSz - rsaSigSz , other + otherSz - rsaSigSz ,
5997+ (size_t )rsaSigSz );
5998+ return keepSz ;
5999+ }
6000+ #endif
6001+
6002+ /*
6003+ * Regression test for the DigestInfo AlgorithmIdentifier parameter mismatch.
6004+ *
6005+ * The parameter encoding (NULL present vs absent) of the SignerInfo
6006+ * digestAlgorithm - a CMS field, RFC 5652/5754 - is independent of the
6007+ * parameter encoding of the AlgorithmIdentifier inside the PKCS#1 v1.5
6008+ * DigestInfo that the RSA signature actually covers (RFC 8017). wolfSSL must
6009+ * not couple them. Go's crypto/rsa (micromdm/scep and other Go CMS/SCEP
6010+ * stacks) omits the NULL in the SignerInfo digestAlgorithm while signing a
6011+ * NULL-present DigestInfo; before the fix wc_PKCS7_VerifySignedData() returned
6012+ * SIG_VERIFY_E on such a (cryptographically valid) message.
6013+ *
6014+ * The mismatch is produced here entirely from wolfSSL's own signer, so no
6015+ * externally captured message is embedded (see pkcs7_build_digestparam_
6016+ * mismatch). The fix is symmetric, so both directions are exercised, over both
6017+ * the attribute-free and signed-attribute signing paths.
6018+ */
6019+ int test_wc_PKCS7_VerifySignedData_NoDigestParams (void )
6020+ {
6021+ EXPECT_DECLS ;
6022+ #if defined(HAVE_PKCS7 ) && !defined(NO_RSA ) && !defined(NO_SHA256 ) && \
6023+ defined(USE_CERT_BUFFERS_2048 )
6024+ PKCS7 * pkcs7 = NULL ;
6025+ byte cert [sizeof (client_cert_der_2048 )];
6026+ byte key [sizeof (client_key_der_2048 )];
6027+ word32 certSz = (word32 )sizeof (cert );
6028+ word32 keySz = (word32 )sizeof (key );
6029+ byte msg [FOURK_BUF ];
6030+ int msgSz = 0 ;
6031+
6032+ XMEMCPY (cert , client_cert_der_2048 , certSz );
6033+ XMEMCPY (key , client_key_der_2048 , keySz );
6034+
6035+ /* Direction A (Go/micromdm), no signed attributes: SignerInfo
6036+ * digestAlgorithm NULL-absent, signature over a NULL-present DigestInfo. */
6037+ XMEMSET (msg , 0 , sizeof (msg ));
6038+ ExpectIntGT (msgSz = pkcs7_build_digestparam_mismatch (cert , certSz , key ,
6039+ keySz , 0 , 1 , msg , (word32 )sizeof (msg )), 0 );
6040+ ExpectNotNull (pkcs7 = wc_PKCS7_New (HEAP_HINT , testDevId ));
6041+ ExpectIntEQ (wc_PKCS7_Init (pkcs7 , HEAP_HINT , INVALID_DEVID ), 0 );
6042+ ExpectIntEQ (wc_PKCS7_InitWithCert (pkcs7 , NULL , 0 ), 0 );
6043+ ExpectIntEQ (wc_PKCS7_VerifySignedData (pkcs7 , msg , (word32 )msgSz ), 0 );
6044+ wc_PKCS7_Free (pkcs7 );
6045+ pkcs7 = NULL ;
6046+
6047+ /* Direction B (reverse), no signed attributes: SignerInfo digestAlgorithm
6048+ * NULL-present, signature over a NULL-absent DigestInfo. Exercises the
6049+ * other branch of the symmetric (!hashParamsAbsent) retry. */
6050+ XMEMSET (msg , 0 , sizeof (msg ));
6051+ ExpectIntGT (msgSz = pkcs7_build_digestparam_mismatch (cert , certSz , key ,
6052+ keySz , 0 , 0 , msg , (word32 )sizeof (msg )), 0 );
6053+ ExpectNotNull (pkcs7 = wc_PKCS7_New (HEAP_HINT , testDevId ));
6054+ ExpectIntEQ (wc_PKCS7_Init (pkcs7 , HEAP_HINT , INVALID_DEVID ), 0 );
6055+ ExpectIntEQ (wc_PKCS7_InitWithCert (pkcs7 , NULL , 0 ), 0 );
6056+ ExpectIntEQ (wc_PKCS7_VerifySignedData (pkcs7 , msg , (word32 )msgSz ), 0 );
6057+ wc_PKCS7_Free (pkcs7 );
6058+ pkcs7 = NULL ;
6059+
6060+ /* Direction A with signed attributes: same mismatch over the signed-
6061+ * attributes path, exercising the flipped rebuild's attribute-hashing
6062+ * branch. */
6063+ XMEMSET (msg , 0 , sizeof (msg ));
6064+ ExpectIntGT (msgSz = pkcs7_build_digestparam_mismatch (cert , certSz , key ,
6065+ keySz , 1 , 1 , msg , (word32 )sizeof (msg )), 0 );
6066+ ExpectNotNull (pkcs7 = wc_PKCS7_New (HEAP_HINT , testDevId ));
6067+ ExpectIntEQ (wc_PKCS7_Init (pkcs7 , HEAP_HINT , INVALID_DEVID ), 0 );
6068+ ExpectIntEQ (wc_PKCS7_InitWithCert (pkcs7 , NULL , 0 ), 0 );
6069+ ExpectIntEQ (wc_PKCS7_VerifySignedData (pkcs7 , msg , (word32 )msgSz ), 0 );
6070+ wc_PKCS7_Free (pkcs7 );
6071+ pkcs7 = NULL ;
6072+
6073+ /* Direction B with signed attributes: completes the 2x2 matrix (both flip
6074+ * directions over both the attribute-free and signed-attribute paths). */
6075+ XMEMSET (msg , 0 , sizeof (msg ));
6076+ ExpectIntGT (msgSz = pkcs7_build_digestparam_mismatch (cert , certSz , key ,
6077+ keySz , 1 , 0 , msg , (word32 )sizeof (msg )), 0 );
6078+ ExpectNotNull (pkcs7 = wc_PKCS7_New (HEAP_HINT , testDevId ));
6079+ ExpectIntEQ (wc_PKCS7_Init (pkcs7 , HEAP_HINT , INVALID_DEVID ), 0 );
6080+ ExpectIntEQ (wc_PKCS7_InitWithCert (pkcs7 , NULL , 0 ), 0 );
6081+ ExpectIntEQ (wc_PKCS7_VerifySignedData (pkcs7 , msg , (word32 )msgSz ), 0 );
6082+ wc_PKCS7_Free (pkcs7 );
6083+ pkcs7 = NULL ;
6084+
6085+ /* Negative control: a corrupted signature must still fail. The fix adds a
6086+ * third acceptance attempt (the flipped DigestInfo parameter encoding), so
6087+ * assert the added leniency did not become over-broad - a signature that
6088+ * matches under neither encoding must return non-zero. Reuse the last
6089+ * built message and flip the final signature byte. Guarded on msgSz > 0 so
6090+ * a failed build above is never passed to the (word32) size cast. */
6091+ if (msgSz > 0 ) {
6092+ msg [msgSz - 1 ] ^= 0xFF ;
6093+ ExpectNotNull (pkcs7 = wc_PKCS7_New (HEAP_HINT , testDevId ));
6094+ ExpectIntEQ (wc_PKCS7_Init (pkcs7 , HEAP_HINT , INVALID_DEVID ), 0 );
6095+ ExpectIntEQ (wc_PKCS7_InitWithCert (pkcs7 , NULL , 0 ), 0 );
6096+ ExpectIntNE (wc_PKCS7_VerifySignedData (pkcs7 , msg , (word32 )msgSz ), 0 );
6097+ wc_PKCS7_Free (pkcs7 );
6098+ }
6099+ #endif /* HAVE_PKCS7 && !NO_RSA && !NO_SHA256 && USE_CERT_BUFFERS_2048 */
6100+ return EXPECT_RESULT ();
6101+ }
0 commit comments