|
3 | 3 | import java.io.ByteArrayInputStream; |
4 | 4 | import java.io.FileOutputStream; |
5 | 5 | import java.io.IOException; |
| 6 | +import java.io.OutputStream; |
6 | 7 | import java.io.StringReader; |
7 | 8 | import java.io.StringWriter; |
8 | 9 | import java.math.BigInteger; |
|
22 | 23 | import java.util.List; |
23 | 24 |
|
24 | 25 | import junit.framework.TestCase; |
| 26 | +import org.bouncycastle.asn1.ASN1Encoding; |
| 27 | +import org.bouncycastle.asn1.ASN1Sequence; |
| 28 | +import org.bouncycastle.asn1.DERSequence; |
25 | 29 | import org.bouncycastle.asn1.cms.ContentInfo; |
26 | 30 | import org.bouncycastle.asn1.iana.IANAObjectIdentifiers; |
27 | 31 | import org.bouncycastle.asn1.misc.MiscObjectIdentifiers; |
|
52 | 56 | import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; |
53 | 57 | import org.bouncycastle.openssl.jcajce.JcaPEMWriter; |
54 | 58 | import org.bouncycastle.operator.ContentSigner; |
| 59 | +import org.bouncycastle.operator.ContentVerifier; |
55 | 60 | import org.bouncycastle.operator.ContentVerifierProvider; |
56 | 61 | import org.bouncycastle.operator.DigestCalculatorProvider; |
57 | 62 | import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; |
@@ -327,6 +332,58 @@ public void testRSAAndECCompositeGen() |
327 | 332 | // doOutput("/tmp/comp_pub_1.pem", pubKeyStr); |
328 | 333 | } |
329 | 334 |
|
| 335 | + |
| 336 | + public void testCompositeSignatureStripping() |
| 337 | + throws Exception |
| 338 | + { |
| 339 | + // CVE-2026-5588 follow-up: the legacy id_alg_composite ContentVerifier |
| 340 | + // accepted a composite signature truncated to a single verifiable |
| 341 | + // component. The empty-sequence case was fixed; an under-length sequence |
| 342 | + // still passed. A composite signature MUST carry every component. |
| 343 | + KeyPairGenerator ecKpg = KeyPairGenerator.getInstance("EC", "BC"); |
| 344 | + ecKpg.initialize(new ECNamedCurveGenParameterSpec("P-256")); |
| 345 | + KeyPair ecKp = ecKpg.generateKeyPair(); |
| 346 | + |
| 347 | + KeyPairGenerator rsaKpg = KeyPairGenerator.getInstance("RSA", "BC"); |
| 348 | + rsaKpg.initialize(new RSAKeyGenParameterSpec(3072, RSAKeyGenParameterSpec.F4)); |
| 349 | + KeyPair rsaKp = rsaKpg.generateKeyPair(); |
| 350 | + |
| 351 | + // component 0 = ECDSA, component 1 = RSA |
| 352 | + CompositeAlgorithmSpec compAlgSpec = new CompositeAlgorithmSpec.Builder() |
| 353 | + .add("SHA256withECDSA") |
| 354 | + .add("SHA256withRSA") |
| 355 | + .build(); |
| 356 | + CompositePublicKey compPub = new CompositePublicKey(ecKp.getPublic(), rsaKp.getPublic()); |
| 357 | + CompositePrivateKey compPriv = new CompositePrivateKey(ecKp.getPrivate(), rsaKp.getPrivate()); |
| 358 | + |
| 359 | + ContentSigner sigGen = new JcaContentSignerBuilder("Composite", compAlgSpec).build(compPriv); |
| 360 | + |
| 361 | + X500Name name = new X500Name("CN=Composite Strip Test"); |
| 362 | + X509CertificateHolder certHldr = new JcaX509v3CertificateBuilder( |
| 363 | + name, BigInteger.valueOf(1), |
| 364 | + new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), |
| 365 | + name, compPub).build(sigGen); |
| 366 | + |
| 367 | + // the genuine 2-of-2 composite signature verifies |
| 368 | + assertTrue("genuine composite signature should verify", |
| 369 | + certHldr.isSignatureValid(new JcaContentVerifierProviderBuilder().build(compPub))); |
| 370 | + |
| 371 | + AlgorithmIdentifier sigAlgId = certHldr.getSignatureAlgorithm(); |
| 372 | + byte[] tbs = certHldr.toASN1Structure().getTBSCertificate().getEncoded(ASN1Encoding.DER); |
| 373 | + ASN1Sequence sigSeq = ASN1Sequence.getInstance(certHldr.getSignature()); |
| 374 | + assertTrue("expected a two-component composite signature", sigSeq.size() == 2); |
| 375 | + |
| 376 | + // strip the RSA component, leaving only the (genuine) ECDSA component |
| 377 | + byte[] strippedSig = new DERSequence(sigSeq.getObjectAt(0)).getEncoded(ASN1Encoding.DER); |
| 378 | + |
| 379 | + ContentVerifier cv = new JcaContentVerifierProviderBuilder().build(compPub).get(sigAlgId); |
| 380 | + OutputStream sOut = cv.getOutputStream(); |
| 381 | + sOut.write(tbs); |
| 382 | + sOut.close(); |
| 383 | + |
| 384 | + assertFalse("composite signature stripped of a component must not verify", cv.verify(strippedSig)); |
| 385 | + } |
| 386 | + |
330 | 387 | public void testRSAAndECCompositeSignedDataGen() |
331 | 388 | throws Exception |
332 | 389 | { |
|
0 commit comments