Skip to content

Commit 832eece

Browse files
committed
PKIX: Fix CertPathValidator parsing of the ISIS-MTT dateOfCertGen extension (in the chain validity model), which was missing the unwrap of the extnValue OCTET STRING. A new ValidityModelTest covers the shell and chain validity models through the provider API.
1 parent c6ae478 commit 832eece

8 files changed

Lines changed: 314 additions & 16 deletions

File tree

‎docs/releasenotes.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ Date: 2026, TBD
157157
- Signature.setParameter(...) on the composite ML-DSA services threw NullPointerException when called before initSign / initVerify, the composite counterpart of the base-engine defect fixed for issue #2396. The composite SignatureSpi has its own engineSetParameter, which applies a context by re-initialising the component signatures with the key the Signature holds, and it went straight to that re-initialisation without checking there was a key to re-initialise with - with no key the intent is unknowable, so the null key fell into the signing branch and all thirty-six composite services, plus the generic COMPOSITE service, failed identically. The context may now be set either side of initialisation, as it may be for the base ML-DSA and SLH-DSA services: setting it on a signature that already has a key re-initialises the component signatures at once as before, and setting it beforehand records it to be applied when the key arrives. Three further problems in the same method are fixed with it. A parameter spec that is not one of the provider's own but carries a context - the shape another provider's ML-DSA spec has, which the base engines accept - had the context applied and was then reported as rejected by the same call, so a caller that took the exception at its word went on to produce signatures bound to a context it believed had not been set; such a spec is now simply accepted, and one carrying no context at all is still refused. getParameters() cached the AlgorithmParameters it built and setting a new context did not clear that cache, so getParameters() went on reporting the previous context. And on the generic COMPOSITE service, whose algorithm is not known until the key arrives, a CompositeSignatureSpec set beforehand threw NullPointerException from the absent digest; its choice of pre-hash mode is now recorded and applied at initialisation. Setting a context after initialisation, which is what the existing tests and examples do, produces exactly the signatures it did before (github #2412).
158158
- The structural check on decoded ASN.1 UTCTime / GeneralizedTime content introduced in 1.85 gave no way through for the zone-less UTCTime "YYMMDDHHMMSS", so a single such field made the structure carrying it unreadable: a CMS SignedData whose signing-time attribute lacked its trailing "Z" - a shape found in signatures in circulation - failed to load through both CMSSignedData and CMSSignedDataParser.getSignerInfos() with "invalid UTCTime format", and nothing else in the message could be examined. The value is not a legal UTCTime, X.680 sec. 47.3 making the zone mandatory, but ASN1UTCTime.getTime() has always carried an explicit branch reading it as GMT, so it does denote a real instant and re-encodes unchanged. Setting the new system/security property org.bouncycastle.asn1.allow_zoneless_utctime (Properties.ASN1_ALLOW_ZONELESS_UTCTIME) to "true" admits it - and admits nothing else: every other malformed value, the zone-less "YYMMDDHHMM" without seconds included, is still rejected with the property set, and GeneralizedTime, whose zone is optional to begin with, is unaffected. The property has to be set for the value to decode at all, generation is unaffected, and the value is still not DER, so writing it through a DEROutputStream is refused when Properties.ASN1_ALLOW_NON_DER_TIME is "false". Separately, CMSSignedDataParser.getSignerInfos() let a SignerInfo that failed to decode escape as an unchecked IllegalStateException; it now reports it as the CMSException the method declares (github #2411).
159159
- The BCJSSE key managers could not select an SM2 certificate for TLS 1.3. ProvX509KeyManager registers a TLS 1.3 EC public-key filter per named curve, but only for the NIST and Brainpool curves, so a key store entry on the sm2p256v1 curve was never matched against the "EC/sm2p256v1" key type the sm2sig_sm3 signature scheme asks for, and a server configured for the RFC 8998 cipher suites failed the handshake with "found no selectable cipher suite among the 1 offered: [TLS_SM4_GCM_SM3]" despite the provider supporting both suites and holding a usable credential. The filter is now registered for curveSM2 on both the client and the server side. As before, the RFC 8998 profile requires explicit configuration: the curveSM2 named group and the sm2sig_sm3 signature scheme are not enabled by default (github #2416).
160+
- The BC "PKIX" CertPathValidator in the chain validity model (PKIXExtendedParameters.CHAIN_VALIDITY_MODEL) could not use the ISIS-MTT dateOfCertGen extension. CertPathValidatorUtilities.getValidCertDateFromValidityModel() passed the raw result of X509Certificate.getExtensionValue(), which is the DER encoding of the extnValue OCTET STRING, straight to ASN1GeneralizedTime.getInstance(), so any end-entity certificate carrying the extension failed validation with "Could not validate time of certificate." rather than having its issuer checked at the recorded generation time. The OCTET STRING is now unwrapped first, and the underlying decoding error is chained on the reported exception.
160161

161162
### 2.1.3 Additional Features and Functionality
162163

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,16 +1116,18 @@ protected static Date getValidCertDateFromValidityModel(Date validityDate, int v
11161116
byte[] extBytes = issuedCert.getExtensionValue(ISISMTTObjectIdentifiers.id_isismtt_at_dateOfCertGen.getId());
11171117
if (extBytes != null)
11181118
{
1119-
dateOfCertgen = ASN1GeneralizedTime.getInstance(ASN1Primitive.fromByteArray(extBytes));
1119+
// getExtensionValue returns the DER encoding of the extnValue OCTET STRING
1120+
byte[] extValue = ASN1OctetString.getInstance(extBytes).getOctets();
1121+
dateOfCertgen = ASN1GeneralizedTime.getInstance(ASN1Primitive.fromByteArray(extValue));
11201122
}
11211123
}
11221124
catch (IOException e)
11231125
{
1124-
throw new AnnotatedException("Date of cert gen extension could not be read.");
1126+
throw new AnnotatedException("Date of cert gen extension could not be read.", e);
11251127
}
11261128
catch (IllegalArgumentException e)
11271129
{
1128-
throw new AnnotatedException("Date of cert gen extension could not be read.");
1130+
throw new AnnotatedException("Date of cert gen extension could not be read.", e);
11291131
}
11301132
if (dateOfCertgen != null)
11311133
{

‎prov/src/main/jdk1.1/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,16 +1262,18 @@ protected static Date getValidCertDateFromValidityModel(Date validityDate, int v
12621262
byte[] extBytes = issuedCert.getExtensionValue(ISISMTTObjectIdentifiers.id_isismtt_at_dateOfCertGen.getId());
12631263
if (extBytes != null)
12641264
{
1265-
dateOfCertgen = ASN1GeneralizedTime.getInstance(ASN1Primitive.fromByteArray(extBytes));
1265+
// getExtensionValue returns the DER encoding of the extnValue OCTET STRING
1266+
byte[] extValue = ASN1OctetString.getInstance(extBytes).getOctets();
1267+
dateOfCertgen = ASN1GeneralizedTime.getInstance(ASN1Primitive.fromByteArray(extValue));
12661268
}
12671269
}
12681270
catch (IOException e)
12691271
{
1270-
throw new AnnotatedException("Date of cert gen extension could not be read.");
1272+
throw new AnnotatedException("Date of cert gen extension could not be read.", e);
12711273
}
12721274
catch (IllegalArgumentException e)
12731275
{
1274-
throw new AnnotatedException("Date of cert gen extension could not be read.");
1276+
throw new AnnotatedException("Date of cert gen extension could not be read.", e);
12751277
}
12761278
if (dateOfCertgen != null)
12771279
{

‎prov/src/main/jdk1.3/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,16 +1131,18 @@ protected static Date getValidCertDateFromValidityModel(Date validityDate, int v
11311131
byte[] extBytes = issuedCert.getExtensionValue(ISISMTTObjectIdentifiers.id_isismtt_at_dateOfCertGen.getId());
11321132
if (extBytes != null)
11331133
{
1134-
dateOfCertgen = ASN1GeneralizedTime.getInstance(ASN1Primitive.fromByteArray(extBytes));
1134+
// getExtensionValue returns the DER encoding of the extnValue OCTET STRING
1135+
byte[] extValue = ASN1OctetString.getInstance(extBytes).getOctets();
1136+
dateOfCertgen = ASN1GeneralizedTime.getInstance(ASN1Primitive.fromByteArray(extValue));
11351137
}
11361138
}
11371139
catch (IOException e)
11381140
{
1139-
throw new AnnotatedException("Date of cert gen extension could not be read.");
1141+
throw new AnnotatedException("Date of cert gen extension could not be read.", e);
11401142
}
11411143
catch (IllegalArgumentException e)
11421144
{
1143-
throw new AnnotatedException("Date of cert gen extension could not be read.");
1145+
throw new AnnotatedException("Date of cert gen extension could not be read.", e);
11441146
}
11451147
if (dateOfCertgen != null)
11461148
{

‎prov/src/main/jdk1.4/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,16 +1123,18 @@ protected static Date getValidCertDateFromValidityModel(Date validityDate, int v
11231123
byte[] extBytes = issuedCert.getExtensionValue(ISISMTTObjectIdentifiers.id_isismtt_at_dateOfCertGen.getId());
11241124
if (extBytes != null)
11251125
{
1126-
dateOfCertgen = ASN1GeneralizedTime.getInstance(ASN1Primitive.fromByteArray(extBytes));
1126+
// getExtensionValue returns the DER encoding of the extnValue OCTET STRING
1127+
byte[] extValue = ASN1OctetString.getInstance(extBytes).getOctets();
1128+
dateOfCertgen = ASN1GeneralizedTime.getInstance(ASN1Primitive.fromByteArray(extValue));
11271129
}
11281130
}
11291131
catch (IOException e)
11301132
{
1131-
throw new AnnotatedException("Date of cert gen extension could not be read.");
1133+
throw new AnnotatedException("Date of cert gen extension could not be read.", e);
11321134
}
11331135
catch (IllegalArgumentException e)
11341136
{
1135-
throw new AnnotatedException("Date of cert gen extension could not be read.");
1137+
throw new AnnotatedException("Date of cert gen extension could not be read.", e);
11361138
}
11371139
if (dateOfCertgen != null)
11381140
{

‎prov/src/test/java/org/bouncycastle/jce/provider/test/RegressionTest.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ public class RegressionTest
111111
new SM9SignatureTest(),
112112
new ThreefishTest(),
113113
new TLSKDFTest(),
114+
new ValidityModelTest(),
114115
new WrapTest(),
115116
new X509CertificatePairTest(),
116117
new X509LDAPCertStoreTest(),

‎prov/src/test/java/org/bouncycastle/jce/provider/test/TestCertificateGen.java‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,21 @@ public static X509Certificate createCert(X500Name signerName, PrivateKey signerK
109109
public static X509Certificate createCert(X500Name signerName, PrivateKey signerKey, X500Name dn, String sigName, Extensions extensions, PublicKey pubKey)
110110
throws Exception
111111
{
112-
V3TBSCertificateGenerator certGen = new V3TBSCertificateGenerator();
113-
114112
long time = System.currentTimeMillis();
115113

114+
return createCert(signerName, signerKey, dn, sigName, new Date(time - 5000), new Date(time + 30 * 60 * 1000), extensions, pubKey);
115+
}
116+
117+
public static X509Certificate createCert(X500Name signerName, PrivateKey signerKey, X500Name dn, String sigName, Date notBefore, Date notAfter, Extensions extensions, PublicKey pubKey)
118+
throws Exception
119+
{
120+
V3TBSCertificateGenerator certGen = new V3TBSCertificateGenerator();
121+
116122
certGen.setSerialNumber(ASN1Integer.valueOf(getSerialNumber()));
117123
certGen.setIssuer(signerName);
118124
certGen.setSubject(dn);
119-
certGen.setStartDate(new Time(new Date(time - 5000)));
120-
certGen.setEndDate(new Time(new Date(time + 30 * 60 * 1000)));
125+
certGen.setStartDate(new Time(notBefore));
126+
certGen.setEndDate(new Time(notAfter));
121127
certGen.setSignature((AlgorithmIdentifier)algIds.get(sigName));
122128
certGen.setSubjectPublicKeyInfo(SubjectPublicKeyInfo.getInstance(pubKey.getEncoded()));
123129
certGen.setExtensions(extensions);

0 commit comments

Comments
 (0)