Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/releasenotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Date: 2026, TBD
- The ML-KEM KeyGenerator (KEMGenerateSpec/KEMExtractSpec), Cipher (wrap/unwrap), javax.crypto.KEM and KeyFactory.translateKey services accepted only BC's own ML-KEM key objects, and the KeyGenerator failed with a ClassCastException at generateKey() rather than at init, so an ML-KEM key from another provider could not be used with BC even though its standard encoding was one BC reads. This broke BCJSSE handshakes over the ML-KEM and hybrid groups whenever another provider ahead of BC decoded the peer's key or generated the ephemeral key pair. A foreign key is now converted from its X.509 or PKCS#8 encoding, with the usual parameter-set checks, and an unusable one is rejected at init (github #2466).
- The raw JCA provider bounded the PBKDF2 iteration count taken from an encoding (org.bouncycastle.pbe.max_iteration_count, default 10,000,000) but not the counts of the legacy PBES1 (PKCS#5 scheme 1) and PKCS#12 PBE families beside it. Their AlgorithmParameters (PKCS12PBE and its OID aliases, PBKDF1) accepted any count, narrowing one beyond the int range with intValue() so that 2^32 arrived as 0, and every Cipher, Mac and SecretKeyFactory derivation ran with whatever count it was given - including a count decoded by another provider's AlgorithmParameters, as when javax.crypto.EncryptedPrivateKeyInfo.getKeySpec() decrypts a PKCS#12 PBE-protected key with BC. As these schemes carry the count in unauthenticated parameters and derive before anything can be checked, a supplied blob could hold a derivation for tens of minutes. The parameter parse now rejects a negative, beyond-int or over-limit count, and the derivations reject a negative or over-limit count, under the same property as PBKDF2. The PKCS#12 key store derives through the same code, so a org.bouncycastle.pkcs12.max_it_count raised above 10,000,000 now needs org.bouncycastle.pbe.max_iteration_count raised with it.
- The light-weight CryptoProWrapEngine (RFC 4357 sec. 6.3) diversified the key encryption key in the caller's own array, so after init the KeyParameter it was given held the diversified key, and initialising again with the same parameters - to unwrap what had just been wrapped, say - diversified it a second time and used a different key. It also failed with a NullPointerException when given no S-box, although init has a branch for that case. It now diversifies a copy, and given no S-box uses the GOST 28147 engine's default S-box for the diversification, the one the wrap itself then uses. The provider's GOST 28147 key wrap ciphers were unaffected, as they always supply an S-box and build a new KeyParameter on every init.
- The OCSPResp(byte[]) and OCSPReq(byte[]) constructors reported a malformed encoding only when the ASN.1 decode raised IllegalArgumentException, ClassCastException or ASN1Exception, so a top-level SEQUENCE that is empty or truncated - which makes OCSPResponse/OCSPRequest read seq.getObjectAt(0) out of bounds - escaped the declared IOException as an ArrayIndexOutOfBoundsException. Both parsers now surface any RuntimeException from the decode as a CertIOException, as X509CertificateHolder and X509CRLHolder already do, so an OCSP client parsing a response and an OCSP responder parsing a request reject malformed input through the contract rather than crashing.

### 2.1.3 Additional Features and Functionality

Expand Down
12 changes: 6 additions & 6 deletions pkix/src/main/java/org/bouncycastle/cert/ocsp/OCSPReq.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,16 @@ private OCSPReq(
}
this.extensions = req.getTbsRequest().getRequestExtensions();
}
catch (IllegalArgumentException e)
{
throw new CertIOException("malformed request: " + e.getMessage(), e);
}
catch (ClassCastException e)
catch (ASN1Exception e)
{
throw new CertIOException("malformed request: " + e.getMessage(), e);
}
catch (ASN1Exception e)
catch (RuntimeException e)
{
// any RuntimeException here (ClassCastException, IllegalArgumentException,
// IndexOutOfBoundsException from a truncated SEQUENCE, NullPointerException, ...)
// means the bytes were malformed; surface it as the declared IOException rather
// than letting it escape the contract, as X509CertificateHolder already does.
throw new CertIOException("malformed request: " + e.getMessage(), e);
}
}
Expand Down
12 changes: 6 additions & 6 deletions pkix/src/main/java/org/bouncycastle/cert/ocsp/OCSPResp.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,16 @@ private OCSPResp(
{
this.resp = OCSPResponse.getInstance(aIn.readObject());
}
catch (IllegalArgumentException e)
{
throw new CertIOException("malformed response: " + e.getMessage(), e);
}
catch (ClassCastException e)
catch (ASN1Exception e)
{
throw new CertIOException("malformed response: " + e.getMessage(), e);
}
catch (ASN1Exception e)
catch (RuntimeException e)
{
// any RuntimeException here (ClassCastException, IllegalArgumentException,
// IndexOutOfBoundsException from a truncated SEQUENCE, NullPointerException, ...)
// means the bytes were malformed; surface it as the declared IOException rather
// than letting it escape the contract, as X509CertificateHolder already does.
throw new CertIOException("malformed response: " + e.getMessage(), e);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public void testOCSP()
{
Security.addProvider(new BouncyCastleProvider());

org.bouncycastle.util.test.Test[] tests = new org.bouncycastle.util.test.Test[] { new OCSPTest(), new OCSPExceptionalSignatureRejectionTest(), new PKIXRevocationTest(), new PKIXOcspRevocationCheckerTest() };
org.bouncycastle.util.test.Test[] tests = new org.bouncycastle.util.test.Test[] { new OCSPTest(), new OCSPExceptionalSignatureRejectionTest(), new OCSPMalformedInputTest(), new PKIXRevocationTest(), new PKIXOcspRevocationCheckerTest() };

for (int i = 0; i != tests.length; i++)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package org.bouncycastle.cert.ocsp.test;

import java.io.IOException;

import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.OCSPRespBuilder;
import org.bouncycastle.util.test.SimpleTest;

/**
* A malformed encoding handed to the OCSPResp(byte[]) / OCSPReq(byte[]) parsers must be reported
* through the declared IOException, not as an unchecked exception. A top-level SEQUENCE that is
* empty or truncated makes OCSPResponse/OCSPRequest read seq.getObjectAt(0) out of bounds, and the
* parsers only caught IllegalArgumentException/ClassCastException/ASN1Exception, so the
* ArrayIndexOutOfBoundsException escaped the throws IOException contract (same shape already
* handled by X509CertificateHolder / X509CRLHolder).
*/
public class OCSPMalformedInputTest
extends SimpleTest
{
public String getName()
{
return "OCSPMalformedInputTest";
}

public void performTest()
throws Exception
{
// empty SEQUENCE, one-element SEQUENCE holding a truncated inner SEQUENCE, and a
// non-SEQUENCE top-level element.
byte[][] malformed = new byte[][]
{
new byte[]{ 0x30, 0x00 },
new byte[]{ 0x30, 0x03, 0x30, 0x01, 0x00 },
new byte[]{ 0x02, 0x01, 0x00 },
};

for (int i = 0; i != malformed.length; i++)
{
checkResp(malformed[i]);
checkReq(malformed[i]);
}

// well-formed input still parses without change.
byte[] validResp = new OCSPRespBuilder().build(OCSPRespBuilder.SUCCESSFUL, null).getEncoded();
new OCSPResp(validResp);

byte[] validReq = new OCSPReqBuilder().build().getEncoded();
new OCSPReq(validReq);
}

private void checkResp(byte[] encoding)
{
boolean reported = false;
try
{
new OCSPResp(encoding);
}
catch (IOException e)
{
reported = true;
}
catch (Exception e)
{
fail("OCSPResp threw " + e.getClass().getName() + " rather than IOException", e);
}

if (!reported)
{
fail("malformed OCSP response accepted");
}
}

private void checkReq(byte[] encoding)
{
boolean reported = false;
try
{
new OCSPReq(encoding);
}
catch (IOException e)
{
reported = true;
}
catch (Exception e)
{
fail("OCSPReq threw " + e.getClass().getName() + " rather than IOException", e);
}

if (!reported)
{
fail("malformed OCSP request accepted");
}
}

public static void main(String[] args)
{
runTest(new OCSPMalformedInputTest());
}
}
Loading