Skip to content

Commit 97fe077

Browse files
committed
Added CMS SignedData and PKIX certification path test coverage for the eighteen Composite ML-DSA parameter sets, which the CMS and cert-path signature algorithm finders already reached with no test exercising either, relates to github PR #2437.
1 parent d141bf4 commit 97fe077

3 files changed

Lines changed: 37 additions & 11 deletions

File tree

‎CONTRIBUTORS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ We also wish to acknowledge financial and collaborative support from [CISCO](htt
542542
- jmeeder \<https://github.com/jmeeder\> - reporting that RFC 4998 evidence-record generation rejected time stamps from an authority naming the digest with NULL parameters where BC names it with them absent, both of which RFC 5754 requires a receiver to accept (issue #2379).
543543
- rimuln \<https://github.com/rimuln\> - diagnosis and fix for PKCS12 getCertificateAlias returning the alias of an unrelated certificate, tracing it to the alias and certificate enumerations of the keystore's certs table diverging in order once keys() enumerated a copy (issue #2384, PR #2385).
544544
- Yu Bao \<yubao&#064;paypal.com\> - reporting an API gap, on behalf of the PayPal Cyber Security Team, that the high-level OpenPGP message API (OpenPGPMessageProcessor / OpenPGPMessageInputStream) gave a caller no way to bound how far a compressed data packet expands, where the low-level PGPCompressedData it wraps has carried a bounded getDataStream(long) overload all along, and that OpenPGPPolicy exposed no equivalent property to set. Suggesting a protocol whitelist for CRL Distribution Point fetching, which is now the org.bouncycastle.x509.CRLDP_protocols property, and suggesting that an OCSP response was read up to the length the responder declared for itself, now capped by org.bouncycastle.ocsp.max_response_size, and suggesting bounds on the OpenPGP ASCII armor headers, now capped by org.bouncycastle.openpgp.max_armor_header_length and org.bouncycastle.openpgp.max_armor_headers.
545-
- Arpan Sharma \<https://github.com/Arpan0995\> - initial audit of BCPQC provider consistency starting with HQC, which led to the exposure of a number of issues in the JCA provider service interfaces for other BCPQC algorithms. In-depth auditing of PQC signature algorithms in the provider leading to the correction of a number of JCA API compliance issues. Initial implementation of the guard that lets a signature context be set on the composite ML-DSA services before initSign / initVerify, the composite counterpart of the base-engine fix for issue #2396 (issue #2412). Audit of the LMS / HSS stateful private key decoder, establishing that the level count, the one-time index and the persisted tree cache were all accepted without validation (issue #2414); that issue was also the inspiration for the further investigation which found the HSS and XMSS^MT decoders accepting a private key whose declared index disagreed with the traversal state stored beside it, allowing a one-time key to be used twice. Audit of the high-level OpenPGP API (org.bouncycastle.openpgp, org.bouncycastle.openpgp.api) and the bcpg packet layer beneath it, spanning certificate evaluation, message verification, decryption and signature subpacket parsing, which found certificates being used without the certifications RFC 9580 requires of them, malformed but signed content reaching ordinary reader code as unchecked exceptions, a truncated message being read as a clean end of message, and unverified plaintext being released ahead of the integrity check (issues #2417, #2424, #2426). Audit of the recipient side of the CMS RFC 9629 KEMRecipientInfo path, which found three sender-controlled fields leaving methods declared to throw CMSException as unchecked exceptions and the kekLength never being confirmed against the wrap algorithm, and which led to the same translation being added to the neighbouring AuthEnvelopedData constructor and the two streaming parsers (issue #2422). Audit of RSA-PSS parameter handling in the provider, which found Signature.setParameter reporting a rejected trailer field as an unchecked exception and leaving the rejected spec half-applied, so that the parameters reported for a signature were not the ones it had been made with (issue #2421). Audit of the PKCS#12 key store entry-setting boundary, which found setKeyEntry reporting a certificate whose public key the provider cannot resolve as an unchecked exception rather than the KeyStoreException it declares, and which led to the same guard being applied to setCertificateEntry and to the RFC 9579 PBMAC1 store, and to the rejected entry no longer being left behind in the store (issue #2419). Further auditing of the verification and expiration paths of the high-level OpenPGP API, which found a configured algorithm policy not being applied to inline message verification, an expired primary key still offering its subkeys, and a data signature being reported valid past its own expiration time.
545+
- Arpan Sharma \<https://github.com/Arpan0995\> - initial audit of BCPQC provider consistency starting with HQC, which led to the exposure of a number of issues in the JCA provider service interfaces for other BCPQC algorithms. In-depth auditing of PQC signature algorithms in the provider leading to the correction of a number of JCA API compliance issues. Initial implementation of the guard that lets a signature context be set on the composite ML-DSA services before initSign / initVerify, the composite counterpart of the base-engine fix for issue #2396 (issue #2412). Audit of the LMS / HSS stateful private key decoder, establishing that the level count, the one-time index and the persisted tree cache were all accepted without validation (issue #2414); that issue was also the inspiration for the further investigation which found the HSS and XMSS^MT decoders accepting a private key whose declared index disagreed with the traversal state stored beside it, allowing a one-time key to be used twice. Audit of the high-level OpenPGP API (org.bouncycastle.openpgp, org.bouncycastle.openpgp.api) and the bcpg packet layer beneath it, spanning certificate evaluation, message verification, decryption and signature subpacket parsing, which found certificates being used without the certifications RFC 9580 requires of them, malformed but signed content reaching ordinary reader code as unchecked exceptions, a truncated message being read as a clean end of message, and unverified plaintext being released ahead of the integrity check (issues #2417, #2424, #2426). Audit of the recipient side of the CMS RFC 9629 KEMRecipientInfo path, which found three sender-controlled fields leaving methods declared to throw CMSException as unchecked exceptions and the kekLength never being confirmed against the wrap algorithm, and which led to the same translation being added to the neighbouring AuthEnvelopedData constructor and the two streaming parsers (issue #2422). Audit of RSA-PSS parameter handling in the provider, which found Signature.setParameter reporting a rejected trailer field as an unchecked exception and leaving the rejected spec half-applied, so that the parameters reported for a signature were not the ones it had been made with (issue #2421). Audit of the PKCS#12 key store entry-setting boundary, which found setKeyEntry reporting a certificate whose public key the provider cannot resolve as an unchecked exception rather than the KeyStoreException it declares, and which led to the same guard being applied to setCertificateEntry and to the RFC 9579 PBMAC1 store, and to the rejected entry no longer being left behind in the store (issue #2419). Further auditing of the verification and expiration paths of the high-level OpenPGP API, which found a configured algorithm policy not being applied to inline message verification, an expired primary key still offering its subkeys, and a data signature being reported valid past its own expiration time. CMS SignedData and PKIX certification path test coverage for the eighteen Composite ML-DSA parameter sets, which had been reachable through the CMS and cert-path signature algorithm finders with no test exercising either (PR #2437).
546546
- Flowdalic \<https://github.com/Flowdalic\> - initial implementation of an AnimalSniffer-based Android API-level compatibility check for the Gradle build (PR #336).
547547
- hannesa2 \<https://github.com/hannesa2\> - initial Dependabot configuration for the Gradle and GitHub Actions ecosystems (PR #883).
548548
- vladhuma \<https://github.com/vladhuma\> - initial implementation of server-side OCSP stapling for the BCJSSE provider, on behalf of Thales Group (PR #1740).

‎pkix/src/test/java/org/bouncycastle/cms/test/AllTests.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public static Test suite()
3232
suite.addTestSuite(CMCEEnvelopedDataTest.class);
3333
suite.addTestSuite(KEMUkmEnvelopedDataTest.class);
3434
suite.addTestSuite(CompositeKEMEnvelopedDataTest.class);
35+
suite.addTestSuite(CompositeMLDSASignedDataTest.class);
3536
suite.addTestSuite(KEMAuthEnvelopedDataTest.class);
3637
suite.addTest(NewAuthenticatedDataTest.suite());
3738
suite.addTest(NewAuthenticatedDataStreamTest.suite());

‎pkix/src/test/java/org/bouncycastle/cms/test/CompositeMLDSASignedDataTest.java‎

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
import java.util.ArrayList;
1717
import java.util.Collections;
1818
import java.util.Date;
19+
import java.util.HashMap;
1920
import java.util.HashSet;
2021
import java.util.List;
22+
import java.util.Map;
2123
import java.util.Set;
2224

2325
import junit.framework.TestCase;
@@ -41,6 +43,7 @@
4143
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
4244
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
4345
import org.bouncycastle.util.CollectionStore;
46+
import org.bouncycastle.util.Strings;
4447

4548
/**
4649
* CMS SignedData round-trip and PKIX CertPath validation tests for the eighteen Composite ML-DSA
@@ -49,12 +52,16 @@
4952
* ML-KEM EnvelopedData path, but the Composite ML-DSA OIDs had no CMS SignedData or PKIX CertPath
5053
* (chain) coverage even though they are wired through DefaultCMSSignatureAlgorithmNameGenerator and
5154
* the DefaultSignatureAlgorithmIdentifierFinder used on the CMS and cert-path paths.
55+
* <p>
56+
* Two key pairs per parameter set are generated once and shared by both test methods. The RSA half
57+
* of the larger sets costs seconds to generate on a modern JVM and far more on the genuine JRE 5 the
58+
* jdk15to18 suite runs against, so a key is never generated where an existing one will do.
5259
*/
5360
public class CompositeMLDSASignedDataTest
5461
extends TestCase
5562
{
5663
private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
57-
private static final byte[] DATA = "the composite ML-DSA cat sat on the CMS mat".getBytes();
64+
private static final byte[] DATA = Strings.toByteArray("the composite ML-DSA cat sat on the CMS mat");
5865

5966
private static final String[] NAMES = {
6067
"MLDSA44-ECDSA-P256-SHA256", "MLDSA44-Ed25519-SHA512", "MLDSA44-RSA2048-PKCS15-SHA256", "MLDSA44-RSA2048-PSS-SHA256",
@@ -76,6 +83,8 @@ public class CompositeMLDSASignedDataTest
7683
IANAObjectIdentifiers.id_MLDSA87_RSA3072_PSS_SHA512, IANAObjectIdentifiers.id_MLDSA87_RSA4096_PSS_SHA512
7784
};
7885

86+
private static final Map keyPairs = new HashMap();
87+
7988
public void setUp()
8089
{
8190
if (Security.getProvider(BC) == null)
@@ -84,6 +93,21 @@ public void setUp()
8493
}
8594
}
8695

96+
private static KeyPair keyPair(String name, int index)
97+
throws Exception
98+
{
99+
String cacheKey = name + "#" + index;
100+
KeyPair kp = (KeyPair)keyPairs.get(cacheKey);
101+
102+
if (kp == null)
103+
{
104+
kp = KeyPairGenerator.getInstance(name, BC).generateKeyPair();
105+
keyPairs.put(cacheKey, kp);
106+
}
107+
108+
return kp;
109+
}
110+
87111
private static X509Certificate buildCert(X500Name issuer, PrivateKey issuerKey, String sigName,
88112
X500Name subject, PublicKey subjectKey, boolean ca, long serial)
89113
throws Exception
@@ -104,7 +128,7 @@ public void testCompositeMLDSASignedData()
104128
{
105129
for (int i = 0; i != NAMES.length; i++)
106130
{
107-
KeyPair kp = KeyPairGenerator.getInstance(NAMES[i], BC).generateKeyPair();
131+
KeyPair kp = keyPair(NAMES[i], 0);
108132
X500Name dn = new X500Name("CN=" + NAMES[i]);
109133
X509Certificate cert = buildCert(dn, kp.getPrivate(), NAMES[i], dn, kp.getPublic(), true, 1);
110134

@@ -128,7 +152,7 @@ public void testCompositeMLDSASignedData()
128152
signerInfo.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
129153

130154
// negative control: the same signature must not verify against a different composite key
131-
KeyPair other = KeyPairGenerator.getInstance(NAMES[i], BC).generateKeyPair();
155+
KeyPair other = keyPair(NAMES[i], 1);
132156
X509Certificate otherCert = buildCert(dn, other.getPrivate(), NAMES[i], dn, other.getPublic(), true, 9);
133157
assertFalse(NAMES[i] + ": composite signature verified against the wrong key",
134158
signerInfo.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(otherCert)));
@@ -139,13 +163,15 @@ public void testCompositeMLDSACertPath()
139163
throws Exception
140164
{
141165
CertificateFactory cf = CertificateFactory.getInstance("X.509", BC);
166+
CertPathValidator validator = CertPathValidator.getInstance("PKIX", BC);
167+
142168
for (int i = 0; i != NAMES.length; i++)
143169
{
144-
KeyPair caKp = KeyPairGenerator.getInstance(NAMES[i], BC).generateKeyPair();
170+
KeyPair caKp = keyPair(NAMES[i], 0);
145171
X500Name caDn = new X500Name("CN=Composite CA " + NAMES[i]);
146172
X509Certificate caCert = buildCert(caDn, caKp.getPrivate(), NAMES[i], caDn, caKp.getPublic(), true, 1);
147173

148-
KeyPair eeKp = KeyPairGenerator.getInstance(NAMES[i], BC).generateKeyPair();
174+
KeyPair eeKp = keyPair(NAMES[i], 1);
149175
X509Certificate eeCert = buildCert(caDn, caKp.getPrivate(), NAMES[i],
150176
new X500Name("CN=Composite EE " + NAMES[i]), eeKp.getPublic(), false, 2);
151177

@@ -155,16 +181,15 @@ public void testCompositeMLDSACertPath()
155181
CertPath cp = cf.generateCertPath(Collections.singletonList(eeCert));
156182
PKIXParameters params = new PKIXParameters(trust);
157183
params.setRevocationEnabled(false);
158-
CertPathValidator.getInstance("PKIX", BC).validate(cp, params);
184+
validator.validate(cp, params);
159185

160-
// negative control: an EE certificate signed by a different CA key must not validate
161-
KeyPair rogueKp = KeyPairGenerator.getInstance(NAMES[i], BC).generateKeyPair();
162-
X509Certificate rogueEe = buildCert(caDn, rogueKp.getPrivate(), NAMES[i],
186+
// negative control: an EE certificate signed by a key outside the anchor must not validate
187+
X509Certificate rogueEe = buildCert(caDn, eeKp.getPrivate(), NAMES[i],
163188
new X500Name("CN=Rogue EE " + NAMES[i]), eeKp.getPublic(), false, 3);
164189
CertPath badCp = cf.generateCertPath(Collections.singletonList(rogueEe));
165190
try
166191
{
167-
CertPathValidator.getInstance("PKIX", BC).validate(badCp, params);
192+
validator.validate(badCp, params);
168193
fail(NAMES[i] + ": CertPath validated an EE signed by a key outside the anchor");
169194
}
170195
catch (CertPathValidatorException expected)

0 commit comments

Comments
 (0)