Skip to content

Commit e0dce82

Browse files
committed
Merge branch 'main' of gitlab.cryptoworkshop.com:root/bc-java
2 parents 41478e3 + 83622d5 commit e0dce82

10 files changed

Lines changed: 97 additions & 36 deletions

File tree

‎docs/releasenotes.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ <h3>2.1.3 Additional Features and Functionality</h3>
195195
<li>SMAUG-T post-quantum key encapsulation mechanism (Module-Lizard / MLWE+MLWR), per the SMAUG-T v1.2.0 reference specification. Lightweight implementation in org.bouncycastle.pqc.crypto.smaugt covering all four parameter sets — smaugt_mode1, smaugt_mode3, smaugt_mode5 (NIST security categories 1/3/5) and smaugt_modet (TiMER, the bandwidth-optimised variant using the D2 encoding) — via SmaugTKeyPairGenerator, SmaugTKEMGenerator, SmaugTKEMExtractor and the matching SmaugTPublicKeyParameters / SmaugTPrivateKeyParameters. KAT-tested against the four PQCkemKAT_smaugt_*.rsp test vectors from the SMAUG-T reference distribution (100 cases per parameter set). JCE provider plumbing is registered through BCPQC: KeyPairGenerator.SmaugT, KeyFactory.SmaugT, KeyGenerator.SmaugT, Cipher.SmaugT (key wrap/unwrap), SmaugTParameterSpec (four constants + fromName lookup), SmaugTKey interface and BCSmaugTPublicKey / BCSmaugTPrivateKey. Four BC-arc OIDs in BCObjectIdentifiers (pqc_kem_smaugt.{1..4}) cover the SubjectPublicKeyInfo / PrivateKeyInfo wire form via the PublicKeyFactory / PrivateKeyFactory / SubjectPublicKeyInfoFactory / PrivateKeyInfoFactory converter plumbing, and BouncyCastleProvider.loadPQCKeys() registers a SmaugTKeyFactorySpi against each OID so the standard BC provider can decode SMAUG-T-bearing certificates and PKCS#8 keys without BCPQC in the lookup chain. The decode paths validate their input lengths up front: SmaugTPublicKeyParameters and SmaugTPrivateKeyParameters reject an encoding whose length does not match the parameter set, and SmaugTKEMExtractor rejects an encapsulation of the wrong length, so a malformed key or ciphertext arriving through PublicKeyFactory / PrivateKeyFactory fails with an IllegalArgumentException at decode time rather than an ArrayIndexOutOfBoundsException later during encapsulation or decapsulation; BCSmaugTPrivateKey.hashCode() derives from the parameter set rather than the secret private encoding.</li>
196196
<li>Server-side OCSP stapling with the org.bouncycastle.tls API is now documented and covered by an example. The protocol support has been in place for some time - AbstractTlsServer echoes a client's "status_request" (RFC 6066 sec. 8) or "status_request_v2" (RFC 6961 sec. 2.2) extension, and TlsServerProtocol / DTLSServerProtocol then send whatever TlsServer.getCertificateStatus() returns as a "certificate_status" message - but the callback's contract was not spelled out. Its javadoc, and that of AbstractTlsServer.allowCertificateStatus() / allowMultiCertStatus(), now describe when the callback is reached, how SecurityParameters.getStatusRequestVersion() selects between a single ocsp response and an ocsp_multi list ordered against the certificate chain, and that TLS 1.3 does not use this path (RFC 8446 sec. 4.4.2.1 carries the response in a per-CertificateEntry extension instead). A new misc-module example, org.bouncycastle.tls.examples.OCSPStaplingServerExample, runs both variants end to end. Note this is the low-level TLS API; for the BCJSSE provider see the following entry. See github issue #1157.</li>
197197
<li>Server-side OCSP stapling with the org.bouncycastle.tls API now also works in TLS 1.3, where it previously had no protocol support at all: RFC 8446 sec. 4.4.2.1 replaces the "certificate_status" handshake message with a "status_request" extension on the CertificateEntry carrying the certificate each response answers for, and TlsServerProtocol assembles those itself from the same TlsServer.getCertificateStatus() callback the earlier versions use, rather than leaving a TlsServer implementation to attach them to the Certificate its credentials supply - which was the only way to staple in TLS 1.3 and is not something the callback's shape suggested. The two shapes the callback may return are both accepted: a CertificateStatusType.ocsp status answers for the end-entity certificate, and an ocsp_multi status answers positionally, entry i of its list for certificate i of the chain, so a server with responses for the intermediates as well returns the ocsp_multi shape even though the status request version is 1 in TLS 1.3. An entry the server has itself given a "status_request" extension is left as it stands, so an implementation already doing this by hand is unaffected. A response too large for the entry to carry is dropped rather than stapled: a CertificateEntry's extensions are written with a 16-bit length, leaving room for 65527 bytes of response where the entry has no other extensions, and nothing bounds an OCSP response below that, so letting one through would cost the handshake rather than just the staple. The wire form of the extension is now created and read through TlsExtensionsUtils.createStatusRequestExtension13 / readStatusRequestExtension13, alongside the create/read pair every other extension has; the BCJSSE provider, which had been encoding it privately, goes through the same path. See github issue #1157.</li>
198-
<li>Client-side OCSP stapling with the org.bouncycastle.tls API now works in TLS 1.3, where TlsServerCertificate.getCertificateStatus() had always answered null: the value it returns is set from the "certificate_status" message, and RFC 8446 sec. 4.4.2.1 sends no such message, carrying each response in a "status_request" extension of the CertificateEntry holding the certificate it answers for instead. A low-level client was left to read those extensions itself, which only the BCJSSE provider did. The client now reads them as the Certificate message arrives - the exact inverse of what a BC server does when it staples - and hands them back through the new TlsServerCertificate.getCertificateStatusAt(int), one per certificate of getCertificate(), null where that certificate was left unstapled. This reads the same way whichever version was negotiated: up to TLS 1.2 the ocsp_multi list of a "certificate_status" message is positional in the same sense (RFC 6961 sec. 2.2), so a caller pairing responses with the certificates they answer for no longer has to know which version delivered them. getCertificateStatus() is unchanged up to TLS 1.2, where it remains the message as it arrived, and in TLS 1.3 answers for the end-entity certificate. Two consequences worth noting. TlsServerCertificate has gained a method, so an implementation of that interface outside BC will not compile until it adds one - BC constructs the only implementation itself, so a caller that merely receives one through TlsAuthentication.notifyServerCertificate is unaffected. And a client that asked for a staple now reads what arrives, so a server that mis-staples in TLS 1.3 - an extension carrying trailing bytes, the ocsp_multi form RFC 8446 does not admit there, or an unparseable body - fails the handshake with a decode_error alert where the bytes previously went unread; AbstractTlsClient.getCertificateStatusRequest() asks for a staple by default, so this applies unless a client overrides it to return null. A staple the client did not ask for is still ignored rather than made a handshake failure. The BCJSSE client, which took this strictness when it began reading TLS 1.3 staples, now shares the one implementation rather than carrying its own (github #1485).</li>
198+
<li>Client-side OCSP stapling with the org.bouncycastle.tls API now works in TLS 1.3, where TlsServerCertificate.getCertificateStatus() had always answered null: the value it returns is set from the "certificate_status" message, and RFC 8446 sec. 4.4.2.1 sends no such message, carrying each response in a "status_request" extension of the CertificateEntry holding the certificate it answers for instead. A low-level client was left to read those extensions itself, which only the BCJSSE provider did. The client now reads them as the Certificate message arrives - the exact inverse of what a BC server does when it staples - and hands them back through the new TlsServerCertificate.getCertificateStatusAt(int), one per certificate of getCertificate(), null where that certificate was left unstapled (and for an index that names no certificate of the chain). This reads the same way whichever version was negotiated: up to TLS 1.2 the ocsp_multi list of a "certificate_status" message is positional in the same sense (RFC 6961 sec. 2.2), so a caller pairing responses with the certificates they answer for no longer has to know which version delivered them. getCertificateStatus() is unchanged up to TLS 1.2, where it remains the message as it arrived, and in TLS 1.3 answers for the end-entity certificate. Two consequences worth noting. TlsServerCertificate has gained a method, so an implementation of that interface outside BC will not compile until it adds one - BC constructs the only implementation itself, so a caller that merely receives one through TlsAuthentication.notifyServerCertificate is unaffected. And a client that asked for a staple now reads what arrives, so a server that mis-staples in TLS 1.3 - an extension carrying trailing bytes, the ocsp_multi form RFC 8446 does not admit there, or an unparseable body - fails the handshake with a decode_error alert where the bytes previously went unread; AbstractTlsClient.getCertificateStatusRequest() asks for a staple by default, so this applies unless a client overrides it to return null. A staple the client did not ask for is still ignored rather than made a handshake failure. The BCJSSE client, which took this strictness when it began reading TLS 1.3 staples, now shares the one implementation rather than carrying its own (github #1485).</li>
199199
<li>org.bouncycastle.operator.DefaultAlgorithmNameFinder now names KEM algorithm OIDs, which it previously did not do for any KEM at all - getAlgorithmName() handed back the OID string unchanged. Added: ML-KEM (FIPS 203, the three NIST OIDs); and FrodoKEM and Classic McEliece on the ISO/IEC 18033-2 arc, which is what the BC provider registers them under, all eight and all sixteen parameter sets respectively. The coverage is restricted to the standardised assignments - BouncyCastle's own pre-standard arcs for these two families, including a Classic McEliece 348864 that ISO never published, are deliberately left unnamed as those parameters are to be phased out. Names follow the canonical form the matching lightweight parameter class uses - MLKEMParameters.getName(), FrodoKEMParameters.getName(), CMCEParameters.getName() - so they are also the names the BC provider registers the algorithms under. NTRU and HQC remain unnamed.</li>
200200
<li>org.bouncycastle.operator.KemAlgorithmIdentifierFinder and its DefaultKemAlgorithmIdentifierFinder implementation are the KEM counterpart of SignatureAlgorithmIdentifierFinder, turning a KEM algorithm name into the AlgorithmIdentifier that names it in a SubjectPublicKeyInfo, a CMS KEMRecipientInfo (RFC 9629) or a CMP KemCiphertextInfo - previously a caller had to assemble that identifier from the OID constants by hand, as BouncyCastle's own TLS and composite-KEM code still does through private switches. Names are matched without regard to case and cover the same set the name finder now names, so the two directions round-trip; an unrecognised name raises IllegalArgumentException rather than returning null, and hasAlgorithm(String) tests for support without catching, in the same way AlgorithmNameFinder.hasAlgorithmName does for the reverse direction. DefaultSignatureAlgorithmIdentifierFinder gains the same hasAlgorithm(String) method; it sits on the implementation rather than on SignatureAlgorithmIdentifierFinder, since adding a method to that long-published interface would break every implementation outside the library. Each KEM parameter set has its own OID, so every identifier returned has an absent parameters field - compare received identifiers with AlgorithmIdentifier.areEquivalent rather than equals, so a peer that spells the algorithm with an explicit NULL still matches. A new parent interface, org.bouncycastle.operator.AlgorithmIdentifierFinder, carries the shared find(String) contract, extended by SignatureAlgorithmIdentifierFinder, KemAlgorithmIdentifierFinder and DigestAlgorithmIdentifierFinder - the finders that behave alike, matching names without regard to case and throwing IllegalArgumentException for a name they do not recognise. MacAlgorithmIdentifierFinder stays outside the hierarchy, as it returns null for an unrecognised name instead. Existing implementations are unaffected, as the parent requires only the method its subinterfaces already declared. Two changes to DefaultDigestAlgorithmIdentifierFinder bring it into that contract. Its name lookup now throws IllegalArgumentException("Unknown digest algorithm requested: ...") where it used to return null, matching the signature and KEM finders; its other two overloads are untouched, so find(AlgorithmIdentifier) still returns null when it cannot derive a digest from a signature algorithm, which github #1767 relies on, and a name given in dotted OID form is still read as one. Callers that treated the null as an error are unaffected beyond the exception type - org.bouncycastle.est.HttpAuth, the only one in the library that branched on it, still reports an unrecognised HTTP Digest algorithm as an IOException - while JcaContentSignerBuilder and JcaAlgorithmParametersConverter, which dereferenced the result without checking, now name the offending algorithm instead of raising a bare NullPointerException. DefaultDigestAlgorithmIdentifierFinder also gains a hasAlgorithm(String), answering for exactly the names find(String) accepts - the digest name table or a name in dotted OID form - so the two cannot disagree. And name matching now folds case in all four finders: DefaultDigestAlgorithmIdentifierFinder was the one that did not, looking its digest names up verbatim, and it now upper cases the supplied name first. Every key in its table was already upper case, so that only widens what matches and cannot change a lookup that already succeeded; a name given in dotted OID form is still passed through unfolded.</li>
201201
<li>org.bouncycastle.jcajce.provider.util.SecurityExceptions gained an invalidAlgorithmParameterException(String, Throwable) factory, for the same reason as its siblings: java.security.InvalidAlgorithmParameterException only grew a cause-taking constructor in Java 5, so code that has to compile for the legacy Java 4 distributions attaches the cause through initCause instead. Use the factory rather than rolling that call at the throw site.</li>

‎tls/src/main/java/org/bouncycastle/jsse/provider/JsseUtils.java‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,21 @@ static List<byte[]> getStatusResponses(CertificateStatus certificateStatus) thro
618618
return null;
619619
}
620620

621+
static List<byte[]> getStatusResponses(TlsContext context, TlsServerCertificate serverCertificate)
622+
throws IOException
623+
{
624+
if (TlsUtils.isTLSv13(context))
625+
{
626+
/*
627+
* RFC 8446 4.4.2.1: TLS 1.3 has no CertificateStatus handshake message - the server's
628+
* OCSP information rides in an extension of each CertificateEntry.
629+
*/
630+
return getStatusResponses13(serverCertificate);
631+
}
632+
633+
return getStatusResponses(serverCertificate.getCertificateStatus());
634+
}
635+
621636
/**
622637
* The stapled responses from a TLS 1.3 Certificate message, where RFC 8446 sec. 4.4.2.1 carries
623638
* each one in a "status_request" extension of the CertificateEntry it belongs to rather than in a

‎tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsClient.java‎

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -379,19 +379,11 @@ public void notifyServerCertificate(TlsServerCertificate serverCertificate) thro
379379
String authType = JsseUtils.getAuthTypeServer(
380380
context.getSecurityParametersHandshake().getKeyExchangeAlgorithm());
381381

382-
if (TlsUtils.isTLSv13(context))
383-
{
384-
/*
385-
* RFC 8446 4.4.2.1: TLS 1.3 has no CertificateStatus handshake message - the
386-
* server's OCSP information rides in an extension of each CertificateEntry.
387-
*/
388-
jsseSecurityParameters.statusResponses = JsseUtils.getStatusResponses13(serverCertificate);
389-
}
390-
else
391-
{
392-
jsseSecurityParameters.statusResponses = JsseUtils.getStatusResponses(
393-
serverCertificate.getCertificateStatus());
394-
}
382+
/*
383+
* The status responses (if any) must be set before the checkServerTrusted call: the TrustManager
384+
* accesses them during chain validation via getStatusResponses on the handshake session.
385+
*/
386+
jsseSecurityParameters.statusResponses = JsseUtils.getStatusResponses(context, serverCertificate);
395387

396388
manager.checkServerTrusted(chain, authType);
397389
}

‎tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsServer.java‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,17 @@ public CertificateStatus getCertificateStatus() throws IOException
564564
/*
565565
* Identifying a certificate to a responder takes its issuer, and the chain supplies one for
566566
* every certificate but the last - so a chain of one (a server sending no issuers) has
567-
* nothing that can be asked about.
567+
* nothing that can be asked about. The RFC 6960 CertID needs the issuer's public key (for
568+
* issuerKeyHash), which the subject certificate does not carry, so the issuer DN alone is
569+
* not enough; see OcspStapleCache.createCertID. Two gaps follow: an end-entity certificate
570+
* issued directly by a trust anchor (a chain of one) gets no staple at all, even if marked
571+
* must-staple, and the last certificate sent gets none in the multi-staple shapes - both
572+
* permitted (RFC 6961 sec. 2.2, RFC 8446 sec. 4.4.2.1) and both shared with SunJSSE, whose
573+
* StatusResponseManager also requires a chain of 2 and pairs adjacent entries. A CertID is
574+
* fully determined by the subject certificate plus the issuing public key (invariant across
575+
* cross-signs, so no knowledge of the client's trust anchors is involved), meaning the gaps
576+
* could be closed by sourcing that key - or just its hash - from somewhere other than the
577+
* chain being sent; the wire format constrains nothing here.
568578
*/
569579
int chainLength = certificateMessage.getLength();
570580
if (chainLength < 2)
@@ -1060,6 +1070,14 @@ public void notifyClientCertificate(Certificate clientCertificate) throws IOExce
10601070
{
10611071
X509Certificate[] chain = JsseUtils.getX509CertificateChain(getCrypto(), clientCertificate);
10621072

1073+
/*
1074+
* TODO[tls13] Client stapling (RFC 8446 4.4.2.1: a client may staple OCSP responses to
1075+
* its CertificateEntry list when the CertificateRequest solicited them). If supported,
1076+
* any status responses must be set before the checkClientTrusted call: the TrustManager
1077+
* accesses them during chain validation via getStatusResponses on the handshake session
1078+
* (as ProvTlsClient.notifyServerCertificate already does for the server's staples).
1079+
*/
1080+
10631081
/*
10641082
* We never try to continue the handshake with an untrusted client certificate (although it could
10651083
* be an option if we only "want" client-auth, rather than "need".

‎tls/src/main/java/org/bouncycastle/tls/TlsServerCertificate.java‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ public interface TlsServerCertificate
3535
* response is held).
3636
*
3737
* @param index the index into {@link #getCertificate()} of the certificate in question.
38-
* @return the status answering for that certificate, or null.
39-
* @throws IndexOutOfBoundsException if <code>index</code> is not an index of
40-
* {@link #getCertificate()}.
38+
* @return the status answering for that certificate, or null - also where <code>index</code> is
39+
* not an index of {@link #getCertificate()}, since no certificate there has a status.
4140
*/
4241
CertificateStatus getCertificateStatusAt(int index);
4342
}

‎tls/src/main/java/org/bouncycastle/tls/TlsServerCertificateImpl.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ public CertificateStatus getCertificateStatus()
3535

3636
public CertificateStatus getCertificateStatusAt(int index)
3737
{
38+
if (index < 0 || index >= certificateStatuses.length)
39+
{
40+
return null;
41+
}
42+
3843
return certificateStatuses[index];
3944
}
4045
}

0 commit comments

Comments
 (0)