Skip to content

Commit 3b22ea6

Browse files
authored
Merge pull request #1 from CBitLabs/permissive-parsing
Add support to parse invalid certs but indicate why they are invalid.
2 parents 739a531 + d01e8e5 commit 3b22ea6

7 files changed

Lines changed: 154 additions & 44 deletions

File tree

β€Žcore/src/main/java/org/bouncycastle/asn1/x509/Certificate.javaβ€Ž

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.bouncycastle.asn1.ASN1Sequence;
88
import org.bouncycastle.asn1.ASN1TaggedObject;
99
import org.bouncycastle.asn1.x500.X500Name;
10+
import org.bouncycastle.util.IllegalArgumentWarningException;
1011

1112
/**
1213
* an X509Certificate structure.
@@ -53,12 +54,19 @@ private Certificate(
5354
{
5455
this.seq = seq;
5556

57+
IllegalArgumentWarningException exception = null;
58+
5659
//
5760
// correct x509 certficate
5861
//
5962
if (seq.size() == 3)
6063
{
64+
try {
6165
tbsCert = TBSCertificate.getInstance(seq.getObjectAt(0));
66+
} catch (IllegalArgumentWarningException ex) {
67+
tbsCert = (TBSCertificate) ex.getObject(TBSCertificate.class);
68+
exception = ex;
69+
}
6270
sigAlgId = AlgorithmIdentifier.getInstance(seq.getObjectAt(1));
6371

6472
sig = ASN1BitString.getInstance(seq.getObjectAt(2));
@@ -67,6 +75,10 @@ private Certificate(
6775
{
6876
throw new IllegalArgumentException("sequence wrong size for a certificate");
6977
}
78+
79+
if (exception != null) {
80+
throw new IllegalArgumentWarningException(this, exception);
81+
}
7082
}
7183

7284
public TBSCertificate getTBSCertificate()
@@ -124,6 +136,7 @@ public ASN1BitString getSignature()
124136
return sig;
125137
}
126138

139+
@Override
127140
public ASN1Primitive toASN1Primitive()
128141
{
129142
return seq;

β€Žcore/src/main/java/org/bouncycastle/asn1/x509/Extensions.javaβ€Ž

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package org.bouncycastle.asn1.x509;
22

3-
import java.util.Enumeration;
4-
import java.util.Hashtable;
5-
import java.util.Vector;
6-
73
import org.bouncycastle.asn1.ASN1Encodable;
84
import org.bouncycastle.asn1.ASN1EncodableVector;
95
import org.bouncycastle.asn1.ASN1Object;
@@ -12,6 +8,11 @@
128
import org.bouncycastle.asn1.ASN1Sequence;
139
import org.bouncycastle.asn1.ASN1TaggedObject;
1410
import org.bouncycastle.asn1.DERSequence;
11+
import org.bouncycastle.util.IllegalArgumentWarningException;
12+
13+
import java.util.Enumeration;
14+
import java.util.Hashtable;
15+
import java.util.Vector;
1516

1617
/**
1718
* <pre>
@@ -71,19 +72,25 @@ private Extensions(
7172
ASN1Sequence seq)
7273
{
7374
Enumeration e = seq.getObjects();
75+
String error = null;
7476

7577
while (e.hasMoreElements())
7678
{
7779
Extension ext = Extension.getInstance(e.nextElement());
7880

7981
if (extensions.containsKey(ext.getExtnId()))
8082
{
81-
throw new IllegalArgumentException("repeated extension found: " + ext.getExtnId());
83+
error = "repeated extension found: " + ext.getExtnId();
84+
continue;
8285
}
8386

8487
extensions.put(ext.getExtnId(), ext);
8588
ordering.addElement(ext.getExtnId());
8689
}
90+
91+
if (error != null) {
92+
throw new IllegalArgumentWarningException(error, this);
93+
}
8794
}
8895

8996
/**
@@ -163,6 +170,7 @@ public ASN1Encodable getExtensionParsedValue(ASN1ObjectIdentifier oid)
163170
* extnValue OCTET STRING }
164171
* </pre>
165172
*/
173+
@Override
166174
public ASN1Primitive toASN1Primitive()
167175
{
168176
ASN1EncodableVector vec = new ASN1EncodableVector(ordering.size());

β€Žcore/src/main/java/org/bouncycastle/asn1/x509/TBSCertificate.javaβ€Ž

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@
1010
import org.bouncycastle.asn1.DERSequence;
1111
import org.bouncycastle.asn1.DERTaggedObject;
1212
import org.bouncycastle.asn1.x500.X500Name;
13+
import org.bouncycastle.util.IllegalArgumentWarningException;
1314
import org.bouncycastle.util.Properties;
1415

16+
import java.util.ArrayList;
17+
import java.util.Collection;
18+
import java.util.List;
19+
1520
/**
1621
* The TBSCertificate object.
1722
* <pre>
@@ -47,6 +52,7 @@ public class TBSCertificate
4752
ASN1BitString issuerUniqueId;
4853
ASN1BitString subjectUniqueId;
4954
Extensions extensions;
55+
List<String> errors;
5056

5157
public static TBSCertificate getInstance(
5258
ASN1TaggedObject obj,
@@ -103,7 +109,8 @@ else if (version.hasValue(1))
103109
}
104110
else if (!version.hasValue(2))
105111
{
106-
throw new IllegalArgumentException("version number not recognised");
112+
addError(
113+
String.format("Certificate version number value %d not 0, 1 or 2", version.getValue()));
107114
}
108115

109116
serialNumber = ASN1Integer.getInstance(seq.getObjectAt(seqStart + 1));
@@ -129,7 +136,8 @@ else if (!version.hasValue(2))
129136
int extras = seq.size() - (seqStart + 6) - 1;
130137
if (extras != 0 && isV1)
131138
{
132-
throw new IllegalArgumentException("version 1 certificate contains extra data");
139+
addError("version 1 certificate contains extra data");
140+
extras = 0; // Ignore the extra data
133141
}
134142

135143
while (extras > 0)
@@ -147,15 +155,43 @@ else if (!version.hasValue(2))
147155
case 3:
148156
if (isV2)
149157
{
150-
throw new IllegalArgumentException("version 2 certificate cannot contain extensions");
158+
addError("version 2 certificate cannot contain extensions");
159+
throw new IllegalArgumentWarningException(errors, this);
160+
}
161+
try {
162+
extensions = Extensions.getInstance(ASN1Sequence.getInstance(extra, true));
163+
} catch (IllegalArgumentWarningException ex) {
164+
extensions = ex.getObject(Extensions.class);
165+
addErrors(ex.getMessages());
151166
}
152-
extensions = Extensions.getInstance(ASN1Sequence.getInstance(extra, true));
153167
break;
154168
default:
155-
throw new IllegalArgumentException("Unknown tag encountered in structure: " + extra.getTagNo());
169+
addError("Unknown tag encountered in structure: " + extra.getTagNo());
170+
throw new IllegalArgumentWarningException(errors, this);
156171
}
157172
extras--;
158173
}
174+
175+
if (errors != null) {
176+
throw new IllegalArgumentWarningException(errors, this);
177+
}
178+
}
179+
180+
private void addError(String error) {
181+
if (errors == null) {
182+
errors = new ArrayList<>();
183+
}
184+
errors.add(error);
185+
}
186+
187+
private void addErrors(List<String> errors) {
188+
for (String error : errors) {
189+
addError(error);
190+
}
191+
}
192+
193+
public Collection<String> getErrors() {
194+
return errors;
159195
}
160196

161197
public int getVersionNumber()
@@ -218,6 +254,7 @@ public Extensions getExtensions()
218254
return extensions;
219255
}
220256

257+
@Override
221258
public ASN1Primitive toASN1Primitive()
222259
{
223260
if (Properties.getPropertyValue("org.bouncycastle.x509.allow_non-der_tbscert") != null)

β€Žcore/src/main/java/org/bouncycastle/crypto/params/RSAKeyParameters.javaβ€Ž

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package org.bouncycastle.crypto.params;
22

3-
import java.math.BigInteger;
4-
53
import org.bouncycastle.crypto.CryptoServicesRegistrar;
64
import org.bouncycastle.math.Primes;
75
import org.bouncycastle.util.BigIntegers;
6+
import org.bouncycastle.util.IllegalArgumentWarningException;
87
import org.bouncycastle.util.Properties;
98

9+
import java.math.BigInteger;
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
1013
public class RSAKeyParameters
1114
extends AsymmetricKeyParameter
1215
{
@@ -41,16 +44,20 @@ public RSAKeyParameters(
4144
{
4245
super(isPrivate);
4346

47+
this.modulus = modulus;
48+
this.exponent = exponent;
49+
4450
if (!isPrivate)
4551
{
4652
if ((exponent.intValue() & 1) == 0)
4753
{
48-
throw new IllegalArgumentException("RSA publicExponent is even");
54+
throw new IllegalArgumentWarningException("RSA publicExponent is even", this);
4955
}
5056
}
5157

52-
this.modulus = validated.contains(modulus) ? modulus : validate(modulus, isInternal);
53-
this.exponent = exponent;
58+
if (!validated.contains(modulus)) {
59+
validate(modulus, isInternal);
60+
}
5461
}
5562

5663
private BigInteger validate(BigInteger modulus, boolean isInternal)
@@ -62,44 +69,48 @@ private BigInteger validate(BigInteger modulus, boolean isInternal)
6269
return modulus;
6370
}
6471

72+
List<String> issues = new ArrayList<>();
73+
6574
if ((modulus.intValue() & 1) == 0)
6675
{
67-
throw new IllegalArgumentException("RSA modulus is even");
76+
issues.add("RSA modulus is even");
6877
}
6978

7079
// If you need to set this you need to have a serious word to whoever is generating
7180
// your keys.
72-
if (Properties.isOverrideSet("org.bouncycastle.rsa.allow_unsafe_mod"))
81+
if (!Properties.isOverrideSet("org.bouncycastle.rsa.allow_unsafe_mod"))
7382
{
74-
return modulus;
75-
}
76-
77-
int maxBitLength = Properties.asInteger("org.bouncycastle.rsa.max_size", 15360);
83+
int maxBitLength = Properties.asInteger("org.bouncycastle.rsa.max_size", 15360);
7884

79-
int modBitLength = modulus.bitLength();
80-
if (maxBitLength < modBitLength)
81-
{
82-
throw new IllegalArgumentException("modulus value out of range");
83-
}
85+
int modBitLength = modulus.bitLength();
86+
if (maxBitLength < modBitLength)
87+
{
88+
issues.add("modulus value out of range");
89+
}
8490

85-
if (!modulus.gcd(SMALL_PRIMES_PRODUCT).equals(ONE))
86-
{
87-
throw new IllegalArgumentException("RSA modulus has a small prime factor");
88-
}
91+
if (!modulus.gcd(SMALL_PRIMES_PRODUCT).equals(ONE))
92+
{
93+
issues.add("RSA modulus has a small prime factor");
94+
}
8995

90-
int bits = modulus.bitLength() / 2;
91-
int iterations = Properties.asInteger("org.bouncycastle.rsa.max_mr_tests", getMRIterations(bits));
96+
int bits = modulus.bitLength() / 2;
97+
int iterations = Properties.asInteger("org.bouncycastle.rsa.max_mr_tests", getMRIterations(bits));
9298

93-
if (iterations > 0)
94-
{
95-
Primes.MROutput mr = Primes.enhancedMRProbablePrimeTest(modulus, CryptoServicesRegistrar.getSecureRandom(), iterations);
96-
if (!mr.isProvablyComposite())
99+
if (iterations > 0)
97100
{
98-
throw new IllegalArgumentException("RSA modulus is not composite");
101+
Primes.MROutput mr = Primes.enhancedMRProbablePrimeTest(modulus, CryptoServicesRegistrar.getSecureRandom(), iterations);
102+
if (!mr.isProvablyComposite())
103+
{
104+
issues.add("RSA modulus is not composite");
105+
}
106+
}
107+
108+
if (!issues.isEmpty()) {
109+
throw new IllegalArgumentWarningException(issues, this);
99110
}
100-
}
101111

102-
validated.add(modulus);
112+
validated.add(modulus);
113+
}
103114

104115
return modulus;
105116
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.bouncycastle.util;
2+
3+
import java.util.Collections;
4+
import java.util.List;
5+
6+
public class IllegalArgumentWarningException extends IllegalArgumentException {
7+
8+
private static final long serialVersionUID = 5735291408274180892L;
9+
List<String> messages;
10+
Object object;
11+
12+
public IllegalArgumentWarningException(List<String> messages, Object object, Throwable cause) {
13+
super(messages.get(0), cause);
14+
this.messages = messages;
15+
this.object = object;
16+
}
17+
18+
public IllegalArgumentWarningException(List<String> messages, Object object) {
19+
this(messages, object, null);
20+
}
21+
22+
public IllegalArgumentWarningException(String message, Object object) {
23+
this(Collections.singletonList(message), object, null);
24+
}
25+
26+
public IllegalArgumentWarningException(Object object, Throwable cause) {
27+
this(Collections.singletonList(cause.getMessage()), object, cause);
28+
}
29+
30+
public List<String> getMessages() {
31+
return messages;
32+
}
33+
34+
public <T> T getObject(Class<T> clazz) {
35+
if (clazz.isInstance(object)) {
36+
return (T) object;
37+
}
38+
throw new IllegalArgumentException(messages.get(0), this);
39+
}
40+
41+
}

β€Žcore/src/main/java/org/bouncycastle/util/test/SimpleTest.javaβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ protected void isEquals(
4747
{
4848
if (!a.equals(b))
4949
{
50-
throw new TestFailedException(SimpleTestResult.failed(this, "no message"));
50+
throw new TestFailedException(SimpleTestResult.failed(this, String.format("isEqual fail: %s != %s", a, b)));
5151
}
5252
}
5353

@@ -57,7 +57,7 @@ protected void isEquals(
5757
{
5858
if (a != b)
5959
{
60-
throw new TestFailedException(SimpleTestResult.failed(this, "no message"));
60+
throw new TestFailedException(SimpleTestResult.failed(this, String.format("isEqual fail: %s != %s", a, b)));
6161
}
6262
}
6363

0 commit comments

Comments
 (0)