Skip to content

Commit a161740

Browse files
committed
Test the returned type in the OER getInstance identity guards: UINT32.getInstance and etsi102941 Version.getInstance guarded on UINT8, EtsiTs103097DataEncryptedUnicast.getInstance on its sibling EtsiTs103097DataEncrypted, and OEROptional.getObject had its isInstance arguments transposed, so the first two rejected their own type and the others threw ClassCastException or resolved every optional field reflectively, relates to github #2373.
1 parent 033b499 commit a161740

8 files changed

Lines changed: 91 additions & 6 deletions

File tree

‎CONTRIBUTORS.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@
594594
<li>subbudvk &lt;https://github.com/subbudvk&gt; - initial author on S2K parser hardening work for OpenPGP API.</li>
595595
<li>mkarasik &lt;https://github.com/mkarasik&gt; - initial work on EST server-side key generation (RFC 7030 4.4).</li>
596596
<li>Bernd Pr&uuml;nster (A-SIT Plus) &lt;bernd.pruenster&#064;a-sit.at&gt; - reported lenient ASN.1 UTCTime/GeneralizedTime parsing accepting structurally malformed content, with fuzzing-derived test cases.</li>
597-
<li>Naveed Khan &lt;https://github.com/rootvector2&gt; - constant time comparison of membership and confirmation tags in the MLS API. Reported unbounded array allocation in the BKS/UBER keystore load path (OutOfMemoryError DoS from a crafted keystore) and introduction of a capped decompression limit in PGPCompressedData.getDataStream(), both with POC. Original submission creating S/MIME backing temp files with owner-only permissions (PR #2326). Rejecting empty sequences in the X.509 extension parsers whose RFC 5280 syntax is SEQUENCE SIZE (1..MAX) - CRLDistPoint, CertificatePolicies, ExtendedKeyUsage, PolicyMappings, and SubjectDirectoryAttributes (PR #2331). Hardening asn1.cms.SignerInfo decode to reject a non-INTEGER version or non-tagged unsignedAttrs via getInstance rather than leaking a ClassCastException (PR #2342). Fixing an off-by-4 header-length guard in the OpenPGP NotationData signature subpacket parser (PR #2346). Rejecting CR/LF in the S/MIME streaming writer header names and values (SMIMEEnvelopedWriter/SMIMESignedWriter withHeader) to prevent MIME header injection (PR #2348). Adding minimum-length guards to the SM2 decrypt and GOST28147/DSTU7624/DESede/RC2 key-wrap unwrap paths (PR #2359). Guarding the ECDH session-key length in the JCE OpenPGP decryptor (PR #2383).</li>
597+
<li>Naveed Khan &lt;https://github.com/rootvector2&gt; - constant time comparison of membership and confirmation tags in the MLS API. Reported unbounded array allocation in the BKS/UBER keystore load path (OutOfMemoryError DoS from a crafted keystore) and introduction of a capped decompression limit in PGPCompressedData.getDataStream(), both with POC. Original submission creating S/MIME backing temp files with owner-only permissions (PR #2326). Rejecting empty sequences in the X.509 extension parsers whose RFC 5280 syntax is SEQUENCE SIZE (1..MAX) - CRLDistPoint, CertificatePolicies, ExtendedKeyUsage, PolicyMappings, and SubjectDirectoryAttributes (PR #2331). Hardening asn1.cms.SignerInfo decode to reject a non-INTEGER version or non-tagged unsignedAttrs via getInstance rather than leaking a ClassCastException (PR #2342). Fixing an off-by-4 header-length guard in the OpenPGP NotationData signature subpacket parser (PR #2346). Rejecting CR/LF in the S/MIME streaming writer header names and values (SMIMEEnvelopedWriter/SMIMESignedWriter withHeader) to prevent MIME header injection (PR #2348). Adding minimum-length guards to the SM2 decrypt and GOST28147/DSTU7624/DESede/RC2 key-wrap unwrap paths (PR #2359). Guarding the ECDH session-key length in the JCE OpenPGP decryptor (PR #2383). Fixing the identity fast-path type guards in the OER getInstance factories and the transposed isInstance arguments in OEROptional.getObject (PR #2373).</li>
598598
<li>suraj0208 &lt;https://github.com/suraj0208&gt; - initial work on auto-detecting private key reader (JcaPrivateKeyReader).</li>
599599
<li>liamgilligan &lt;https://github.com/liamgilligan&gt; - noticing the BIP-340 step numbering in the BIP340Signer signing comments was incorrect (PR #2340).</li>
600600
<li>digi-scrypt &lt;https://github.com/digi-scrypt&gt; - disabling DTD and external-entity resolution in KMIPInputStream to close an XXE (local file disclosure / SSRF) exposure in KMIP XML parsing (PR #2315).</li>

‎docs/releasenotes.html‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ <h2>2.0 Release History</h2>
2323
Date:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2026, TBD
2424
<h3>2.1.2 Defects Fixed</h3>
2525
<ul>
26+
<li>Four type-coercion helpers in the OER / IEEE 1609.2 (ITS) decoder tested the wrong type in the identity fast path that lets a getInstance() factory return an argument that is already of the target type. org.bouncycastle.oer.its.ieee1609dot2.basetypes.UINT32.getInstance and org.bouncycastle.oer.its.etsi102941.basetypes.Version.getInstance guarded on UINT8 - a sibling of UINT32 under UintBase, and unrelated to Version - so passing a UINT8 threw ClassCastException, while passing an actual UINT32 or Version missed the fast path and fell through to ASN1Integer.getInstance, which rejects them: neither factory accepted its own type. org.bouncycastle.oer.its.etsi103097.EtsiTs103097DataEncryptedUnicast.getInstance guarded on its sibling EtsiTs103097DataEncrypted and then cast to the unicast type, so an EtsiTs103097DataEncrypted threw ClassCastException. org.bouncycastle.oer.OEROptional.getObject(Class) called value.getClass().isInstance(type) with the arguments transposed, which is always false because the argument is a java.lang.Class, so the cast path was dead and every optional field was resolved reflectively, failing with IllegalStateException for a target type with no static getInstance. Each guard now names the type it returns, matching the sibling UINT8 / UINT16 / UINT64 and EtsiTs103097DataEncrypted factories (github #2373).</li>
2627
<li>DefaultAlgorithmNameFinder and DefaultSignatureNameFinder had no entries at all for the ShangMi algorithms, so an SM2 signature AlgorithmIdentifier that DefaultSignatureAlgorithmIdentifierFinder itself produces came back named only by its OID string - getAlgorithmName(GMObjectIdentifiers.sm2sign_with_sm3) returned "1.2.156.10197.1.501" and hasAlgorithmName returned false. Both finders now name sm2sign_with_sm3 as SM3WITHSM2 and sm2sign_with_sha256 as SHA256WITHSM2, and DefaultAlgorithmNameFinder additionally names the sm3 digest. All three resolve through the BC provider, as Signature and MessageDigest respectively. The remaining GM arc - the SM4 cipher modes, the sm2encrypt variants, and the SM1 / SM6 / SSF33 ciphers BC does not implement - is still unnamed (github #2377).</li>
2728
<li>The RFC 4998 evidence-record classes compared the digest AlgorithmIdentifier named by a time-stamp authority with the one their own DigestCalculator uses, and did so with AlgorithmIdentifier.equals(), which compares the encodings. A TSA that names SHA-256 with an explicit NULL parameters field - DigiCert among them - therefore failed against BC's own calculator, which names it with the parameters absent, and ERSArchiveTimeStampGenerator.generateArchiveTimeStamp rejected the response with "time stamp imprint for wrong algorithm". Both spellings name the same digest and RFC 5754 sec. 2 requires a receiver to accept either, while requiring that identifiers be generated with the parameters absent, which BC already does. The three affected comparisons - the two in ERSArchiveTimeStampGenerator and the digest check in ERSEvidenceRecord.renew - now use the new AlgorithmIdentifier.areEquivalent, which matches on the algorithm and treats an absent parameters field and NULL as the same, and the consistency check across an evidence record's archive time stamp chain uses it too. An identifier carrying an actual parameter structure is never equivalent to one carrying none (github #2379).</li>
2829
<li>EDIPartyName.toASN1Primitive emitted the nameAssigner and partyName DirectoryStrings without their context tags, so an EDIPartyName built through its public constructor could not be parsed back by EDIPartyName.getInstance, which correctly requires them. RFC 5280 sec. 4.2.1.6 tags both members [0] and [1], and those tags are explicit despite the module's IMPLICIT TAGS because DirectoryString is a CHOICE, which X.680 does not allow to be tagged implicitly - the decoder already had this right. The encoder now matches it. Note the type was added during the 1.85 cycle and GeneralName validates its ediPartyName alternative through it, so a GeneralName carrying an untagged ediPartyName - including one BC itself produced - is rejected where 1.84 passed it through unexamined; the untagged form is not read leniently (github #2380).</li>

‎util/src/main/java/org/bouncycastle/oer/OEROptional.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public <T> T getObject(final Class<T> type)
6262

6363
if (defined)
6464
{
65-
if (value.getClass().isInstance(type))
65+
if (type.isInstance(value))
6666
{
6767
return type.cast(value);
6868
}

‎util/src/main/java/org/bouncycastle/oer/its/etsi102941/basetypes/Version.java‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import org.bouncycastle.asn1.ASN1Integer;
66
import org.bouncycastle.asn1.ASN1Object;
77
import org.bouncycastle.asn1.ASN1Primitive;
8-
import org.bouncycastle.oer.its.ieee1609dot2.basetypes.UINT8;
98

109
public class Version
1110
extends ASN1Object
@@ -39,7 +38,7 @@ public BigInteger getVersion()
3938

4039
public static Version getInstance(Object o)
4140
{
42-
if (o instanceof UINT8)
41+
if (o instanceof Version)
4342
{
4443
return (Version)o;
4544
}

‎util/src/main/java/org/bouncycastle/oer/its/etsi103097/EtsiTs103097DataEncryptedUnicast.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ protected EtsiTs103097DataEncryptedUnicast(ASN1Sequence src)
1818

1919
public static EtsiTs103097DataEncryptedUnicast getInstance(Object o)
2020
{
21-
if (o instanceof EtsiTs103097DataEncrypted)
21+
if (o instanceof EtsiTs103097DataEncryptedUnicast)
2222
{
2323
return (EtsiTs103097DataEncryptedUnicast)o;
2424
}

‎util/src/main/java/org/bouncycastle/oer/its/ieee1609dot2/basetypes/UINT32.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ protected UINT32(ASN1Integer integer)
3232

3333
public static UINT32 getInstance(Object o)
3434
{
35-
if (o instanceof UINT8)
35+
if (o instanceof UINT32)
3636
{
3737
return (UINT32)o;
3838
}

‎util/src/test/java/org/bouncycastle/oer/test/AllTests.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public static Test suite()
4242
suite.addTestSuite(OERExtensionTest.class);
4343
suite.addTestSuite(OERInputStreamLimitTest.class);
4444
suite.addTestSuite(OERInputStreamMalformedTest.class);
45+
suite.addTestSuite(OERTypeGuardTest.class);
4546

4647
return new BCTestSetup(suite);
4748
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package org.bouncycastle.oer.test;
2+
3+
import java.math.BigInteger;
4+
5+
import junit.framework.TestCase;
6+
import org.bouncycastle.asn1.ASN1Encodable;
7+
import org.bouncycastle.asn1.ASN1Integer;
8+
import org.bouncycastle.oer.OEROptional;
9+
import org.bouncycastle.oer.its.etsi102941.basetypes.Version;
10+
import org.bouncycastle.oer.its.etsi103097.EtsiTs103097DataEncrypted;
11+
import org.bouncycastle.oer.its.etsi103097.EtsiTs103097DataEncryptedUnicast;
12+
import org.bouncycastle.oer.its.ieee1609dot2.Ieee1609Dot2Content;
13+
import org.bouncycastle.oer.its.ieee1609dot2.basetypes.UINT32;
14+
import org.bouncycastle.oer.its.ieee1609dot2.basetypes.UINT8;
15+
import org.bouncycastle.util.BigIntegers;
16+
17+
/**
18+
* The identity fast path in the OER getInstance factories has to test the type it returns.
19+
*/
20+
public class OERTypeGuardTest
21+
extends TestCase
22+
{
23+
public void testUINT32GetInstance()
24+
{
25+
UINT32 uint32 = new UINT32(7);
26+
27+
assertSame(uint32, UINT32.getInstance(uint32));
28+
assertEquals(BigInteger.valueOf(7), UINT32.getInstance(new ASN1Integer(7)).getValue());
29+
30+
// a UINT8 is a sibling of UINT32 under UintBase, not a UINT32
31+
try
32+
{
33+
UINT32.getInstance(new UINT8(7));
34+
fail("UINT8 accepted as UINT32");
35+
}
36+
catch (IllegalArgumentException e)
37+
{
38+
// expected
39+
}
40+
}
41+
42+
public void testVersionGetInstance()
43+
{
44+
Version version = new Version(1);
45+
46+
assertSame(version, Version.getInstance(version));
47+
assertEquals(BigIntegers.ONE, Version.getInstance(new ASN1Integer(1)).getVersion());
48+
49+
try
50+
{
51+
Version.getInstance(new UINT8(1));
52+
fail("UINT8 accepted as Version");
53+
}
54+
catch (IllegalArgumentException e)
55+
{
56+
// expected
57+
}
58+
}
59+
60+
public void testEtsiTs103097DataEncryptedUnicastGetInstance()
61+
{
62+
Ieee1609Dot2Content content = Ieee1609Dot2Content.unsecuredData(new byte[]{1, 2, 3});
63+
64+
EtsiTs103097DataEncryptedUnicast unicast = new EtsiTs103097DataEncryptedUnicast(content);
65+
66+
assertSame(unicast, EtsiTs103097DataEncryptedUnicast.getInstance(unicast));
67+
68+
// EtsiTs103097DataEncrypted is a sibling under EtsiTs103097Data, so it has to be decoded
69+
// rather than cast
70+
EtsiTs103097DataEncrypted encrypted = new EtsiTs103097DataEncrypted(content);
71+
72+
assertEquals(encrypted, EtsiTs103097DataEncryptedUnicast.getInstance(encrypted));
73+
}
74+
75+
public void testOEROptionalGetObject()
76+
{
77+
ASN1Integer value = new ASN1Integer(3);
78+
79+
assertSame(value, OEROptional.getInstance(value).getObject(ASN1Integer.class));
80+
81+
// ASN1Encodable has no getInstance, so only the cast path can satisfy this
82+
assertSame(value, OEROptional.getValue(ASN1Encodable.class, value));
83+
}
84+
}

0 commit comments

Comments
 (0)