Skip to content

Commit d3cf026

Browse files
committed
Remove creation of legacy generic composite (id_alg_composite) signatures from JcaContentSignerBuilder, deprecating CompositeAlgorithmSpec; verification of existing legacy composite signatures is unaffected but planned for removal next release.
1 parent 46677df commit d3cf026

7 files changed

Lines changed: 110 additions & 814 deletions

File tree

‎docs/releasenotes.html‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ <h3>2.1.4 Additional Notes</h3>
134134
<li>As flagged in the 1.85 release notes, the deprecated FrodoKEM implementation under org.bouncycastle.pqc.crypto.frodo has been removed, together with its BCPQC provider support: the Frodo KeyFactory / KeyPairGenerator / KeyGenerator / Cipher registrations, org.bouncycastle.pqc.jcajce.spec.FrodoParameterSpec and org.bouncycastle.pqc.jcajce.interfaces.FrodoKey. Use the ISO/IEC 18033-2 FrodoKEM added in 1.85 under org.bouncycastle.crypto instead (crypto.kems.FrodoKEMGenerator / FrodoKEMExtractor and crypto.params.FrodoKEM*), which is registered in the BC provider. Note that keys carrying the superseded BCObjectIdentifiers.frodokem* object identifiers no longer decode through either provider, as the key-info converters for them are gone with the implementation.</li>
135135
<li>The deprecated XWingPrivateKeyParameters constructor taking org.bouncycastle.pqc.crypto.mlkem key parameters has been removed, as flagged when the ML-KEM classes were promoted to org.bouncycastle.crypto in 1.85; use the constructor taking org.bouncycastle.crypto.params.MLKEMPrivateKeyParameters / MLKEMPublicKeyParameters instead. X-Wing itself is otherwise unchanged and no longer references the deprecated package.</li>
136136
<li>ML-DSA signature verification (org.bouncycastle.crypto.signers.MLDSASigner / HashMLDSASigner, and the corresponding JCA Signature.MLDSA* / Signature.HashMLDSA* algorithms) returns false uniformly for a cryptographically wrong signature and for one that is structurally malformed per FIPS 204 Algorithm 8 (wrong length, an out-of-order or duplicate hint index, or a hint weight exceeding the parameter set's omega) - the two cases are not distinguished, and neither ever raises SignatureException for a decode failure. This differs from some other providers, including the JDK's own SUN ML-DSA implementation, which throws SignatureException for a decode failure and reserves false for a well-formed-but-wrong signature; code relying on Signature.verify() to distinguish "malformed" from "wrong" cannot rely on that distinction against the BC provider. This is documented behaviour, not a defect (github #2367).</li>
137+
<li>org.bouncycastle.operator.jcajce.JcaContentSignerBuilder no longer creates new legacy generic composite (id_alg_composite) signatures: the constructor's org.bouncycastle.jcajce.spec.CompositeAlgorithmSpec handling and the CompositePrivateKey short-circuit that drove it have been removed, and CompositeAlgorithmSpec is deprecated accordingly. Creating a modern fixed-algorithm Composite ML-DSA signature (e.g. via a CompositePrivateKey whose algorithm is one of the IANAObjectIdentifiers.id_MLDSA* OIDs) is unaffected. Verifying an existing legacy composite signature through org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder still works in this release but is planned for removal in the next one - callers still relying on the legacy format for verification should migrate to a modern fixed-algorithm composite key.</li>
137138
</ul>
138139

139140
<a id="r1rv85"><h3>2.2.1 Version</h3></a>

‎pkix/src/main/java/org/bouncycastle/operator/jcajce/JcaContentSignerBuilder.java‎

Lines changed: 0 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.bouncycastle.operator.jcajce;
22

3-
import java.io.IOException;
43
import java.io.OutputStream;
54
import java.security.GeneralSecurityException;
65
import java.security.PrivateKey;
@@ -14,28 +13,19 @@
1413
import java.security.spec.MGF1ParameterSpec;
1514
import java.security.spec.PSSParameterSpec;
1615
import java.util.HashSet;
17-
import java.util.List;
1816
import java.util.Set;
1917

20-
import org.bouncycastle.asn1.ASN1EncodableVector;
2118
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
22-
import org.bouncycastle.asn1.ASN1Encoding;
2319
import org.bouncycastle.asn1.ASN1Integer;
24-
import org.bouncycastle.asn1.ASN1Sequence;
25-
import org.bouncycastle.asn1.DERBitString;
2620
import org.bouncycastle.asn1.DERNull;
27-
import org.bouncycastle.asn1.DERSequence;
28-
import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
2921
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
3022
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
3123
import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
3224
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
3325
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
3426
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
3527
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
36-
import org.bouncycastle.jcajce.CompositePrivateKey;
3728
import org.bouncycastle.jcajce.io.OutputStreamFactory;
38-
import org.bouncycastle.jcajce.spec.CompositeAlgorithmSpec;
3929
import org.bouncycastle.operator.ContentSigner;
4030
import org.bouncycastle.operator.FixedLengthContentSigner;
4131
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
@@ -44,11 +34,9 @@
4434
import org.bouncycastle.operator.ExtendedContentSigner;
4535
import org.bouncycastle.operator.OperatorCreationException;
4636
import org.bouncycastle.operator.RuntimeOperatorException;
47-
import org.bouncycastle.operator.SignatureAlgorithmIdentifierFinder;
4837
import org.bouncycastle.pqc.crypto.lms.LMSigParameters;
4938
import org.bouncycastle.util.Pack;
5039
import org.bouncycastle.util.Strings;
51-
import org.bouncycastle.util.io.TeeOutputStream;
5240

5341
/**
5442
* General builder class for ContentSigner operators based on the JCA.
@@ -163,14 +151,6 @@ public JcaContentSignerBuilder(String signatureAlgorithm, AlgorithmParameterSpec
163151
this.sigAlgId = new AlgorithmIdentifier(
164152
PKCSObjectIdentifiers.id_RSASSA_PSS, createPSSParams(pssSpec));
165153
}
166-
else if (sigParamSpec instanceof CompositeAlgorithmSpec)
167-
{
168-
CompositeAlgorithmSpec compSpec = (CompositeAlgorithmSpec)sigParamSpec;
169-
170-
this.sigAlgSpec = compSpec;
171-
this.sigAlgId = new AlgorithmIdentifier(
172-
MiscObjectIdentifiers.id_alg_composite, createCompParams(compSpec));
173-
}
174154
else
175155
{
176156
throw new IllegalArgumentException("unknown sigParamSpec: "
@@ -202,12 +182,6 @@ public JcaContentSignerBuilder setSecureRandom(SecureRandom random)
202182
public ContentSigner build(PrivateKey privateKey)
203183
throws OperatorCreationException
204184
{
205-
//Use this legacy method only for composite private keys (they have that identifier)
206-
if (privateKey instanceof CompositePrivateKey && ((CompositePrivateKey)privateKey).getAlgorithmIdentifier().getAlgorithm().equals(MiscObjectIdentifiers.id_composite_key))
207-
{
208-
return buildComposite((CompositePrivateKey)privateKey);
209-
}
210-
211185
try
212186
{
213187
if (sigAlgSpec == null)
@@ -375,81 +349,6 @@ private AlgorithmIdentifier getSigAlgId(PrivateKey privateKey)
375349
}
376350
}
377351

378-
private ContentSigner buildComposite(CompositePrivateKey privateKey)
379-
throws OperatorCreationException
380-
{
381-
try
382-
{
383-
List<PrivateKey> privateKeys = privateKey.getPrivateKeys();
384-
final ASN1Sequence sigAlgIds = ASN1Sequence.getInstance(sigAlgId.getParameters());
385-
final Signature[] sigs = new Signature[sigAlgIds.size()];
386-
387-
for (int i = 0; i != sigAlgIds.size(); i++)
388-
{
389-
sigs[i] = helper.createSignature(AlgorithmIdentifier.getInstance(sigAlgIds.getObjectAt(i)));
390-
391-
if (random != null)
392-
{
393-
sigs[i].initSign(privateKeys.get(i), random);
394-
}
395-
else
396-
{
397-
sigs[i].initSign(privateKeys.get(i));
398-
}
399-
}
400-
401-
OutputStream sStream = OutputStreamFactory.createStream(sigs[0]);
402-
for (int i = 1; i != sigs.length; i++)
403-
{
404-
sStream = new TeeOutputStream(sStream, OutputStreamFactory.createStream(sigs[i]));
405-
}
406-
407-
final OutputStream sigStream = sStream;
408-
409-
return new ContentSigner()
410-
{
411-
OutputStream stream = sigStream;
412-
413-
public AlgorithmIdentifier getAlgorithmIdentifier()
414-
{
415-
return sigAlgId;
416-
}
417-
418-
public OutputStream getOutputStream()
419-
{
420-
return stream;
421-
}
422-
423-
public byte[] getSignature()
424-
{
425-
try
426-
{
427-
ASN1EncodableVector sigV = new ASN1EncodableVector();
428-
429-
for (int i = 0; i != sigs.length; i++)
430-
{
431-
sigV.add(new DERBitString(sigs[i].sign()));
432-
}
433-
434-
return new DERSequence(sigV).getEncoded(ASN1Encoding.DER);
435-
}
436-
catch (IOException e)
437-
{
438-
throw new RuntimeOperatorException("exception encoding signature: " + e.getMessage(), e);
439-
}
440-
catch (SignatureException e)
441-
{
442-
throw new RuntimeOperatorException("exception obtaining signature: " + e.getMessage(), e);
443-
}
444-
}
445-
};
446-
}
447-
catch (GeneralSecurityException e)
448-
{
449-
throw new OperatorCreationException("cannot create signer: " + e.getMessage(), e);
450-
}
451-
}
452-
453352
private static RSASSAPSSparams createPSSParams(PSSParameterSpec pssSpec)
454353
{
455354
DigestAlgorithmIdentifierFinder digFinder = new DefaultDigestAlgorithmIdentifierFinder();
@@ -470,32 +369,4 @@ private static RSASSAPSSparams createPSSParams(PSSParameterSpec pssSpec)
470369
ASN1Integer.valueOf(pssSpec.getSaltLength()),
471370
ASN1Integer.valueOf(pssSpec.getTrailerField()));
472371
}
473-
474-
private static ASN1Sequence createCompParams(CompositeAlgorithmSpec compSpec)
475-
{
476-
SignatureAlgorithmIdentifierFinder algFinder = new DefaultSignatureAlgorithmIdentifierFinder();
477-
ASN1EncodableVector v = new ASN1EncodableVector();
478-
479-
List<String> algorithmNames = compSpec.getAlgorithmNames();
480-
List<AlgorithmParameterSpec> algorithmSpecs = compSpec.getParameterSpecs();
481-
482-
for (int i = 0; i != algorithmNames.size(); i++)
483-
{
484-
AlgorithmParameterSpec sigSpec = algorithmSpecs.get(i);
485-
if (sigSpec == null)
486-
{
487-
v.add(algFinder.find(algorithmNames.get(i)));
488-
}
489-
else if (sigSpec instanceof PSSParameterSpec)
490-
{
491-
v.add(new AlgorithmIdentifier(PKCSObjectIdentifiers.id_RSASSA_PSS, createPSSParams((PSSParameterSpec)sigSpec)));
492-
}
493-
else
494-
{
495-
throw new IllegalArgumentException("unrecognized parameterSpec");
496-
}
497-
}
498-
499-
return new DERSequence(v);
500-
}
501372
}

‎pkix/src/main/jdk1.4/org/bouncycastle/operator/jcajce/JcaContentSignerBuilder.java‎

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,19 @@
1111
import java.security.spec.AlgorithmParameterSpec;
1212
import java.security.spec.PSSParameterSpec;
1313
import java.util.HashSet;
14-
import java.util.List;
1514
import java.util.Set;
1615

17-
import org.bouncycastle.asn1.ASN1EncodableVector;
1816
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
1917
import org.bouncycastle.asn1.ASN1Encoding;
2018
import org.bouncycastle.asn1.ASN1Integer;
21-
import org.bouncycastle.asn1.ASN1Sequence;
2219
import org.bouncycastle.asn1.DERNull;
23-
import org.bouncycastle.asn1.DERSequence;
24-
import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
2520
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
2621
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
2722
import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
2823
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
2924
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
3025
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
3126
import org.bouncycastle.jcajce.io.OutputStreamFactory;
32-
import org.bouncycastle.jcajce.spec.CompositeAlgorithmSpec;
3327
import org.bouncycastle.jcajce.util.DefaultJcaJceHelper;
3428
import org.bouncycastle.jcajce.util.NamedJcaJceHelper;
3529
import org.bouncycastle.jcajce.util.ProviderJcaJceHelper;
@@ -41,7 +35,6 @@
4135
import org.bouncycastle.operator.ExtendedContentSigner;
4236
import org.bouncycastle.operator.OperatorCreationException;
4337
import org.bouncycastle.operator.RuntimeOperatorException;
44-
import org.bouncycastle.operator.SignatureAlgorithmIdentifierFinder;
4538
import org.bouncycastle.util.Strings;
4639

4740
/**
@@ -123,14 +116,6 @@ public JcaContentSignerBuilder(String signatureAlgorithm, AlgorithmParameterSpec
123116
this.sigAlgId = new AlgorithmIdentifier(
124117
PKCSObjectIdentifiers.id_RSASSA_PSS, createPSSParams(signatureAlgorithm, pssSpec));
125118
}
126-
else if (sigParamSpec instanceof CompositeAlgorithmSpec)
127-
{
128-
CompositeAlgorithmSpec compSpec = (CompositeAlgorithmSpec)sigParamSpec;
129-
130-
this.sigAlgSpec = compSpec;
131-
this.sigAlgId = new AlgorithmIdentifier(
132-
MiscObjectIdentifiers.id_alg_composite, createCompParams(compSpec));
133-
}
134119
else
135120
{
136121
throw new IllegalArgumentException("unknown sigParamSpec: "
@@ -159,12 +144,6 @@ public JcaContentSignerBuilder setSecureRandom(SecureRandom random)
159144
return this;
160145
}
161146

162-
// NOTE: base's genuine-CompositePrivateKey short-circuit (buildComposite(), driving one
163-
// Signature per component key) is dropped here - org.bouncycastle.jcajce.CompositePrivateKey's
164-
// jdk1.4 overlay has no getAlgorithmIdentifier() (composite JCE support is excluded from the
165-
// jdk1.4 distribution's asymmetric.compositesignatures/compositekem packages), so no real
166-
// CompositePrivateKey ever reaches build() here. The ASN.1-only CompositeAlgorithmSpec handling
167-
// in the constructor above is unrelated and stays, matching the pre-existing overlay shape.
168147
public ContentSigner build(PrivateKey privateKey)
169148
throws OperatorCreationException
170149
{
@@ -360,33 +339,4 @@ private static RSASSAPSSparams createPSSParams(String signatureAlgorithm, PSSPar
360339
ASN1Integer.valueOf(pssSpec.getSaltLength()),
361340
RSASSAPSSparams.DEFAULT_TRAILER_FIELD);
362341
}
363-
364-
private static ASN1Sequence createCompParams(CompositeAlgorithmSpec compSpec)
365-
{
366-
SignatureAlgorithmIdentifierFinder algFinder = new DefaultSignatureAlgorithmIdentifierFinder();
367-
ASN1EncodableVector v = new ASN1EncodableVector();
368-
369-
List<String> algorithmNames = compSpec.getAlgorithmNames();
370-
List<AlgorithmParameterSpec> algorithmSpecs = compSpec.getParameterSpecs();
371-
372-
for (int i = 0; i != algorithmNames.size(); i++)
373-
{
374-
AlgorithmParameterSpec sigSpec = (AlgorithmParameterSpec)algorithmSpecs.get(i);
375-
if (sigSpec == null)
376-
{
377-
v.add(algFinder.find((String)algorithmNames.get(i)));
378-
}
379-
else if (sigSpec instanceof PSSParameterSpec)
380-
{
381-
v.add(new AlgorithmIdentifier(PKCSObjectIdentifiers.id_RSASSA_PSS,
382-
createPSSParams((String)algorithmNames.get(i), (PSSParameterSpec)sigSpec)));
383-
}
384-
else
385-
{
386-
throw new IllegalArgumentException("unrecognized parameterSpec");
387-
}
388-
}
389-
390-
return new DERSequence(v);
391-
}
392342
}

‎pkix/src/test/java/org/bouncycastle/cert/test/CertTest.java‎

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
import java.io.IOException;
66
import java.io.ObjectInputStream;
77
import java.io.ObjectOutputStream;
8+
import java.io.OutputStream;
89
import java.io.Serializable;
910
import java.io.UnsupportedEncodingException;
1011
import java.math.BigInteger;
12+
import java.security.GeneralSecurityException;
1113
import java.security.InvalidKeyException;
1214
import java.security.KeyFactory;
1315
import java.security.KeyPair;
@@ -51,6 +53,7 @@
5153
import org.bouncycastle.asn1.ASN1Enumerated;
5254
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
5355
import org.bouncycastle.asn1.ASN1OctetString;
56+
import org.bouncycastle.asn1.ASN1Encoding;
5457
import org.bouncycastle.asn1.ASN1Primitive;
5558
import org.bouncycastle.asn1.DERBitString;
5659
import org.bouncycastle.asn1.DERNull;
@@ -107,7 +110,6 @@
107110
import org.bouncycastle.asn1.iana.IANAObjectIdentifiers;
108111
import org.bouncycastle.jcajce.CompositePrivateKey;
109112
import org.bouncycastle.jcajce.CompositePublicKey;
110-
import org.bouncycastle.jcajce.spec.CompositeAlgorithmSpec;
111113
import org.bouncycastle.jcajce.spec.MLDSAParameterSpec;
112114
import org.bouncycastle.jcajce.spec.SLHDSAParameterSpec;
113115
import org.bouncycastle.jce.X509KeyUsage;
@@ -4248,14 +4250,55 @@ public void checkCreationComposite()
42484250
//
42494251
// create the certificate - version 3
42504252
//
4251-
CompositeAlgorithmSpec compAlgSpec = new CompositeAlgorithmSpec.Builder()
4252-
.add("SHA256withECDSA")
4253-
.add("LMS")
4254-
.build();
42554253
CompositePublicKey compPub = new CompositePublicKey(ecPub, lmsPub);
42564254
CompositePrivateKey compPrivKey = new CompositePrivateKey(ecPriv, lmsPriv);
42574255

4258-
ContentSigner sigGen = new JcaContentSignerBuilder("Composite", compAlgSpec).setProvider(BC).build(compPrivKey);
4256+
// Legacy composite signature *creation* via JcaContentSignerBuilder has since been
4257+
// removed (verification is unaffected), so the genuine 2-of-2 signature here is built
4258+
// directly from two raw java.security.Signature instances rather than through the builder.
4259+
final DefaultSignatureAlgorithmIdentifierFinder compSigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();
4260+
final AlgorithmIdentifier compSigAlgId = new AlgorithmIdentifier(
4261+
MiscObjectIdentifiers.id_alg_composite,
4262+
new DERSequence(compSigAlgFinder.find("SHA256withECDSA"), compSigAlgFinder.find("LMS")));
4263+
final Signature compEcSig = Signature.getInstance("SHA256withECDSA", BC);
4264+
compEcSig.initSign(ecPriv);
4265+
final Signature compLmsSig = Signature.getInstance("LMS", "BCPQC");
4266+
compLmsSig.initSign(lmsPriv);
4267+
4268+
ContentSigner sigGen = new ContentSigner()
4269+
{
4270+
private final ByteArrayOutputStream bOut = new ByteArrayOutputStream();
4271+
4272+
public AlgorithmIdentifier getAlgorithmIdentifier()
4273+
{
4274+
return compSigAlgId;
4275+
}
4276+
4277+
public OutputStream getOutputStream()
4278+
{
4279+
return bOut;
4280+
}
4281+
4282+
public byte[] getSignature()
4283+
{
4284+
try
4285+
{
4286+
byte[] tbs = bOut.toByteArray();
4287+
compEcSig.update(tbs);
4288+
compLmsSig.update(tbs);
4289+
return new DERSequence(new DERBitString(compEcSig.sign()), new DERBitString(compLmsSig.sign()))
4290+
.getEncoded(ASN1Encoding.DER);
4291+
}
4292+
catch (GeneralSecurityException e)
4293+
{
4294+
throw new RuntimeException(e);
4295+
}
4296+
catch (IOException e)
4297+
{
4298+
throw new RuntimeException(e);
4299+
}
4300+
}
4301+
};
42594302

42604303
X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
42614304
issuer,
@@ -4400,10 +4443,6 @@ private void checkCompositeCertificateVerify()
44004443
//
44014444
// create the certificate - version 3
44024445
//
4403-
CompositeAlgorithmSpec compAlgSpec = new CompositeAlgorithmSpec.Builder()
4404-
.add("SHA256withECDSA")
4405-
.add("LMS")
4406-
.build();
44074446
CompositePublicKey compPub = new CompositePublicKey(ecPub, lmsPub);
44084447
CompositePrivateKey compPrivKey = new CompositePrivateKey(ecPriv, lmsPriv);
44094448

0 commit comments

Comments
 (0)