Skip to content

Commit 23f3b26

Browse files
committed
Merge branch 'main' of gitlab.cryptoworkshop.com:root/bc-java
2 parents 9b85b76 + f42e970 commit 23f3b26

6 files changed

Lines changed: 223 additions & 144 deletions

File tree

‎docs/releasenotes.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ Date: 2026, TBD
8383
- TLS: A TLS 1.3 client aborted the handshake with an illegal_parameter alert when the server sent the cached_info or record_size_limit extension in EncryptedExtensions, or the server_name extension in a CertificateRequest, since these were missing from the table of extensions permitted in each TLS 1.3 message.
8484
- TLS: The extensions of the CertificateEntry list in a TLS 1.3 Certificate message were not checked against what the receiving endpoint had asked for, although RFC 8446 sec. 4.4.2 requires those in a server's Certificate to correspond to extensions in the ClientHello and those in a client's Certificate to extensions in the CertificateRequest: a client ignored an OCSP response the server stapled without being asked, and a server accepted whatever a client attached. An unrequested extension in any entry now aborts the handshake with an unsupported_extension alert, as RFC 8446 sec. 4.2 requires. A TLS 1.3 server also now rejects a client Certificate whose certificate_request_context differs from that of its CertificateRequest, with an illegal_parameter alert.
8585

86+
- CRL.isRevoked() on the provider's CRLs threw ClassCastException for a certificate whose type is "X.509" but which is not a java.security.cert.X509Certificate, although the method checks only the type and goes on to handle exactly that case: it cast the certificate to X509Certificate for its serial number before looking at any entry, so the branch that reads the issuer from the certificate's encoding could never be reached. Such a certificate now has both its serial number and its issuer read from its encoding, in the provider's X509CRL and in the legacy org.bouncycastle.jce.provider.X509CRLObject alike.
87+
8688
### 2.1.3 Additional Features and Functionality
8789

8890
- The CRMF certificate request message controls now include the RFC 4211 sec. 6.6 protocolEncrKey control, which names the key a CA is to encrypt its response with: org.bouncycastle.cert.crmf.ProtocolEncrKeyControl carries the SubjectPublicKeyInfo the control is defined to take, and CertificateRequestMessage.getControl() recognises id-regCtrl-protocolEncrKey alongside the regToken, authenticator and pkiArchiveOptions controls it already returned (github PR #2443).

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

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import java.security.cert.CertPathValidator;
1414
import java.security.cert.CertPathValidatorException;
1515
import java.security.cert.CertStore;
16+
import java.security.cert.Certificate;
17+
import java.security.cert.CertificateEncodingException;
1618
import java.security.cert.CertificateFactory;
1719
import java.security.cert.CollectionCertStoreParameters;
1820
import java.security.cert.PKIXParameters;
@@ -30,6 +32,7 @@
3032
import org.bouncycastle.asn1.x500.X500Name;
3133
import org.bouncycastle.asn1.x509.BasicConstraints;
3234
import org.bouncycastle.asn1.x509.CRLReason;
35+
import org.bouncycastle.asn1.x509.CertificateList;
3336
import org.bouncycastle.asn1.x509.Extension;
3437
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
3538
import org.bouncycastle.asn1.x509.GeneralName;
@@ -121,14 +124,82 @@ public void performTest()
121124

122125
/**
123126
* CRL.isRevoked() stopped at the first entry carrying the serial number, and reported the
124-
* certificate as not revoked when that entry was another issuer's.
127+
* certificate as not revoked when that entry was another issuer's. The same checks run with the
128+
* certificates presented as a Certificate of type "X.509" that is not an X509Certificate, which
129+
* isRevoked() cast to one for its serial number, and against the legacy provider CRL class.
125130
*/
126131
private void isRevoked(X509Certificate ee, X509Certificate other, X509CRL caFirst, X509CRL otherFirst, X509CRL otherOnly)
132+
throws Exception
133+
{
134+
Certificate opaqueEe = new OpaqueCertificate(ee);
135+
Certificate opaqueOther = new OpaqueCertificate(other);
136+
137+
isRevoked("", ee, other, caFirst, otherFirst, otherOnly);
138+
isRevoked(" [not an X509Certificate]", opaqueEe, opaqueOther, caFirst, otherFirst, otherOnly);
139+
140+
X509CRL legacyCaFirst = legacyCrl(caFirst);
141+
X509CRL legacyOtherFirst = legacyCrl(otherFirst);
142+
X509CRL legacyOtherOnly = legacyCrl(otherOnly);
143+
144+
isRevoked(" [legacy CRL]", ee, other, legacyCaFirst, legacyOtherFirst, legacyOtherOnly);
145+
isRevoked(" [legacy CRL, not an X509Certificate]", opaqueEe, opaqueOther, legacyCaFirst, legacyOtherFirst, legacyOtherOnly);
146+
}
147+
148+
private void isRevoked(String label, Certificate ee, Certificate other, X509CRL caFirst, X509CRL otherFirst, X509CRL otherOnly)
149+
{
150+
isTrue("revoked certificate not reported by isRevoked() (own entry first)" + label, caFirst.isRevoked(ee));
151+
isTrue("revoked certificate not reported by isRevoked() (other issuer's entry first)" + label, otherFirst.isRevoked(ee));
152+
isTrue("other issuer's revoked certificate not reported by isRevoked()" + label, otherFirst.isRevoked(other));
153+
isTrue("another issuer's entry reported by isRevoked()" + label, !otherOnly.isRevoked(ee));
154+
}
155+
156+
private static X509CRL legacyCrl(X509CRL crl)
157+
throws Exception
127158
{
128-
isTrue("revoked certificate not reported by isRevoked() (own entry first)", caFirst.isRevoked(ee));
129-
isTrue("revoked certificate not reported by isRevoked() (other issuer's entry first)", otherFirst.isRevoked(ee));
130-
isTrue("other issuer's revoked certificate not reported by isRevoked()", otherFirst.isRevoked(other));
131-
isTrue("another issuer's entry reported by isRevoked()", !otherOnly.isRevoked(ee));
159+
return new org.bouncycastle.jce.provider.X509CRLObject(CertificateList.getInstance(crl.getEncoded()));
160+
}
161+
162+
/**
163+
* A certificate of type "X.509" that is not a java.security.cert.X509Certificate, as another
164+
* provider's CertificateFactory may return.
165+
*/
166+
private static class OpaqueCertificate
167+
extends Certificate
168+
{
169+
private final X509Certificate cert;
170+
171+
OpaqueCertificate(X509Certificate cert)
172+
{
173+
super("X.509");
174+
175+
this.cert = cert;
176+
}
177+
178+
public byte[] getEncoded()
179+
throws CertificateEncodingException
180+
{
181+
return cert.getEncoded();
182+
}
183+
184+
public void verify(PublicKey key)
185+
{
186+
throw new UnsupportedOperationException();
187+
}
188+
189+
public void verify(PublicKey key, String sigProvider)
190+
{
191+
throw new UnsupportedOperationException();
192+
}
193+
194+
public String toString()
195+
{
196+
return "OpaqueCertificate: " + cert.getSubjectX500Principal();
197+
}
198+
199+
public PublicKey getPublicKey()
200+
{
201+
return cert.getPublicKey();
202+
}
132203
}
133204

134205
private static X509CRL jdkCrl(X509CRL crl)

‎prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLEntryObject.java‎

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,29 +49,21 @@ protected X509CRLEntryObject(TBSCertList.CRLEntry c)
4949
}
5050

5151
/**
52-
* Constructor for CRLEntries of indirect CRLs. If <code>isIndirect</code>
53-
* is <code>false</code> {@link #getCertificateIssuer()} will always
54-
* return <code>null</code>, <code>previousCertificateIssuer</code> is
55-
* ignored. If this <code>isIndirect</code> is specified and this CRLEntry
56-
* has no certificate issuer CRL entry extension
57-
* <code>previousCertificateIssuer</code> is returned by
58-
* {@link #getCertificateIssuer()}.
59-
*
52+
* Constructor for a CRLEntry whose certificate issuer the caller has already worked out with
53+
* {@link #loadCertificateIssuer}, which it has to do anyway to track the issuer from one entry
54+
* to the next.
55+
*
6056
* @param c
6157
* TBSCertList.CRLEntry object.
62-
* @param isIndirect
63-
* <code>true</code> if the corresponding CRL is a indirect
64-
* CRL.
65-
* @param previousCertificateIssuer
66-
* Certificate issuer of the previous CRLEntry.
58+
* @param certificateIssuer
59+
* the certificate issuer of this CRLEntry, <code>null</code> for the CRL issuer.
6760
*/
6861
protected X509CRLEntryObject(
6962
TBSCertList.CRLEntry c,
70-
boolean isIndirect,
71-
X500Name previousCertificateIssuer)
63+
X500Name certificateIssuer)
7264
{
7365
this.c = c;
74-
this.certificateIssuer = loadCertificateIssuer(c, isIndirect, previousCertificateIssuer);
66+
this.certificateIssuer = certificateIssuer;
7567
}
7668

7769
/**

‎prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLImpl.java‎

Lines changed: 63 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -419,39 +419,21 @@ public Date getNextUpdate()
419419
return null == nextUpdate ? null : nextUpdate.getDate();
420420
}
421421

422-
private Set loadCRLEntries()
423-
{
424-
Set entrySet = new HashSet();
425-
Enumeration certs = c.getRevokedCertificateEnumeration();
426-
427-
X500Name previousCertificateIssuer = null; // the issuer
428-
while (certs.hasMoreElements())
429-
{
430-
TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
431-
X509CRLEntryObject crlEntry = new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
432-
entrySet.add(crlEntry);
433-
previousCertificateIssuer = X509CRLEntryObject.loadCertificateIssuer(entry, isIndirect, previousCertificateIssuer);
434-
}
435-
436-
return entrySet;
437-
}
438-
439422
// returns the first entry with the serial number whatever its issuer - the cert path validators rely on that.
440423
public X509CRLEntry getRevokedCertificate(BigInteger serialNumber)
441424
{
442425
Enumeration certs = c.getRevokedCertificateEnumeration();
443426

444-
X500Name previousCertificateIssuer = null; // the issuer
427+
X500Name certificateIssuer = null; // null means the CRL issuer
445428
while (certs.hasMoreElements())
446429
{
447430
TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
431+
certificateIssuer = X509CRLEntryObject.loadCertificateIssuer(entry, isIndirect, certificateIssuer);
448432

449433
if (entry.getUserCertificate().hasValue(serialNumber))
450434
{
451-
return new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
435+
return new X509CRLEntryObject(entry, certificateIssuer);
452436
}
453-
454-
previousCertificateIssuer = X509CRLEntryObject.loadCertificateIssuer(entry, isIndirect, previousCertificateIssuer);
455437
}
456438

457439
return null;
@@ -475,34 +457,42 @@ public X509CRLEntry getRevokedCertificate(X509Certificate certificate)
475457

476458
Enumeration certs = c.getRevokedCertificateEnumeration();
477459

478-
X500Name previousCertificateIssuer = null; // the issuer
460+
X500Name certificateIssuer = null; // null means the CRL issuer
479461
while (certs.hasMoreElements())
480462
{
481463
TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
482-
X500Name certificateIssuer = X509CRLEntryObject.loadCertificateIssuer(entry, isIndirect, previousCertificateIssuer);
464+
certificateIssuer = X509CRLEntryObject.loadCertificateIssuer(entry, isIndirect, certificateIssuer);
483465

484-
if (entry.getUserCertificate().hasValue(serialNumber)
485-
&& issuer.equals(certificateIssuer == null ? c.getIssuer() : certificateIssuer))
466+
// a serial number is only unique within its issuer, so another issuer's entry is not this one.
467+
if (entry.getUserCertificate().hasValue(serialNumber) &&
468+
issuer.equals(certificateIssuer == null ? c.getIssuer() : certificateIssuer))
486469
{
487-
return new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
470+
return new X509CRLEntryObject(entry, certificateIssuer);
488471
}
489-
490-
previousCertificateIssuer = certificateIssuer;
491472
}
492473

493474
return null;
494475
}
495476

496477
public Set getRevokedCertificates()
497478
{
498-
Set entrySet = loadCRLEntries();
479+
Enumeration certs = c.getRevokedCertificateEnumeration();
480+
if (!certs.hasMoreElements())
481+
{
482+
return null;
483+
}
499484

500-
if (!entrySet.isEmpty())
485+
Set entrySet = new HashSet();
486+
X500Name certificateIssuer = null; // null means the CRL issuer
487+
do
501488
{
502-
return Collections.unmodifiableSet(entrySet);
489+
TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
490+
certificateIssuer = X509CRLEntryObject.loadCertificateIssuer(entry, isIndirect, certificateIssuer);
491+
entrySet.add(new X509CRLEntryObject(entry, certificateIssuer));
503492
}
493+
while (certs.hasMoreElements());
504494

505-
return null;
495+
return Collections.unmodifiableSet(entrySet);
506496
}
507497

508498
public byte[] getTBSCertList()
@@ -664,51 +654,59 @@ public boolean isRevoked(Certificate cert)
664654
throw new IllegalArgumentException("X.509 CRL used with non X.509 Cert");
665655
}
666656

667-
Enumeration certs = c.getRevokedCertificateEnumeration();
668-
669-
X500Name caName = c.getIssuer();
657+
Enumeration revokedCerts = c.getRevokedCertificateEnumeration();
670658

671-
if (certs.hasMoreElements())
659+
if (revokedCerts.hasMoreElements())
672660
{
673-
BigInteger serial = ((X509Certificate)cert).getSerialNumber();
661+
BigInteger serial;
662+
X500Name issuer;
674663

675-
while (certs.hasMoreElements())
664+
if (cert instanceof X509Certificate)
676665
{
677-
TBSCertList.CRLEntry entry = TBSCertList.CRLEntry.getInstance(certs.nextElement());
666+
X509Certificate x509Cert = (X509Certificate)cert;
678667

679-
caName = X509CRLEntryObject.loadCertificateIssuer(entry, isIndirect, caName);
680-
if (caName == null)
668+
serial = x509Cert.getSerialNumber();
669+
issuer = X500Name.getInstance(x509Cert.getIssuerX500Principal().getEncoded());
670+
}
671+
else
672+
{
673+
// an "X.509" type does not make it an X509Certificate, so both come from the encoding.
674+
org.bouncycastle.asn1.x509.Certificate certStruct;
675+
try
681676
{
682-
caName = c.getIssuer(); // an entry with no certificate issuer of its own is the CRL issuer's
677+
certStruct = org.bouncycastle.asn1.x509.Certificate.getInstance(cert.getEncoded());
683678
}
684-
685-
if (entry.getUserCertificate().hasValue(serial))
679+
catch (CertificateEncodingException e)
686680
{
687-
X500Name issuer;
681+
throw Exceptions.illegalArgumentException("Cannot process certificate", e);
682+
}
688683

689-
if (cert instanceof X509Certificate)
690-
{
691-
issuer = X500Name.getInstance(((X509Certificate)cert).getIssuerX500Principal().getEncoded());
692-
}
693-
else
694-
{
695-
try
696-
{
697-
issuer = org.bouncycastle.asn1.x509.Certificate.getInstance(cert.getEncoded()).getIssuer();
698-
}
699-
catch (CertificateEncodingException e)
700-
{
701-
throw Exceptions.illegalArgumentException("Cannot process certificate", e);
702-
}
703-
}
684+
serial = certStruct.getSerialNumber().getValue();
685+
issuer = certStruct.getIssuer();
686+
}
704687

705-
// a serial number is only unique within its issuer, so another issuer's entry is not this one.
706-
if (caName.equals(issuer))
707-
{
708-
return true;
709-
}
688+
boolean issuedByCRLIssuer = issuer.equals(c.getIssuer());
689+
if (!isIndirect && !issuedByCRLIssuer)
690+
{
691+
return false;
692+
}
693+
694+
X500Name certificateIssuer = null; // null means the CRL issuer
695+
696+
do
697+
{
698+
TBSCertList.CRLEntry entry = TBSCertList.CRLEntry.getInstance(revokedCerts.nextElement());
699+
700+
certificateIssuer = X509CRLEntryObject.loadCertificateIssuer(entry, isIndirect, certificateIssuer);
701+
702+
// a serial number is only unique within its issuer, so another issuer's entry is not this one.
703+
if (entry.getUserCertificate().hasValue(serial) &&
704+
(certificateIssuer == null ? issuedByCRLIssuer : issuer.equals(certificateIssuer)))
705+
{
706+
return true;
710707
}
711708
}
709+
while (revokedCerts.hasMoreElements());
712710
}
713711

714712
return false;

‎prov/src/main/java/org/bouncycastle/jce/provider/X509CRLEntryObject.java‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,26 @@ public X509CRLEntryObject(
6767
TBSCertList.CRLEntry c,
6868
boolean isIndirect,
6969
X500Name previousCertificateIssuer)
70+
{
71+
this(c, loadCertificateIssuer(c, isIndirect, previousCertificateIssuer));
72+
}
73+
74+
/**
75+
* Constructor for a CRLEntry whose certificate issuer the caller has already worked out with
76+
* {@link #loadCertificateIssuer}, which it has to do anyway to track the issuer from one entry
77+
* to the next.
78+
*
79+
* @param c
80+
* TBSCertList.CRLEntry object.
81+
* @param certificateIssuer
82+
* the certificate issuer of this CRLEntry, <code>null</code> for the CRL issuer.
83+
*/
84+
X509CRLEntryObject(
85+
TBSCertList.CRLEntry c,
86+
X500Name certificateIssuer)
7087
{
7188
this.c = c;
72-
this.certificateIssuer = loadCertificateIssuer(c, isIndirect, previousCertificateIssuer);
89+
this.certificateIssuer = certificateIssuer;
7390
}
7491

7592
/**

0 commit comments

Comments
 (0)