diff --git a/core/src/main/java/org/bouncycastle/asn1/x509/Certificate.java b/core/src/main/java/org/bouncycastle/asn1/x509/Certificate.java index e64a197572..2f85f2cf0a 100644 --- a/core/src/main/java/org/bouncycastle/asn1/x509/Certificate.java +++ b/core/src/main/java/org/bouncycastle/asn1/x509/Certificate.java @@ -7,6 +7,7 @@ import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1TaggedObject; import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.util.IllegalArgumentWarningException; /** * an X509Certificate structure. @@ -53,12 +54,19 @@ private Certificate( { this.seq = seq; + IllegalArgumentWarningException exception = null; + // // correct x509 certficate // if (seq.size() == 3) { - tbsCert = TBSCertificate.getInstance(seq.getObjectAt(0)); + try { + tbsCert = TBSCertificate.getInstance(seq.getObjectAt(0)); + } catch (IllegalArgumentWarningException ex) { + tbsCert = (TBSCertificate) ex.getObject(TBSCertificate.class); + exception = ex; + } sigAlgId = AlgorithmIdentifier.getInstance(seq.getObjectAt(1)); sig = ASN1BitString.getInstance(seq.getObjectAt(2)); @@ -67,6 +75,10 @@ private Certificate( { throw new IllegalArgumentException("sequence wrong size for a certificate"); } + + if (exception != null) { + throw new IllegalArgumentWarningException(this, exception); + } } public TBSCertificate getTBSCertificate() @@ -124,6 +136,7 @@ public ASN1BitString getSignature() return sig; } + @Override public ASN1Primitive toASN1Primitive() { return seq; diff --git a/core/src/main/java/org/bouncycastle/asn1/x509/Extensions.java b/core/src/main/java/org/bouncycastle/asn1/x509/Extensions.java index d25187aac5..e015d475e8 100644 --- a/core/src/main/java/org/bouncycastle/asn1/x509/Extensions.java +++ b/core/src/main/java/org/bouncycastle/asn1/x509/Extensions.java @@ -12,6 +12,7 @@ import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1TaggedObject; import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.util.IllegalArgumentWarningException; import org.bouncycastle.util.Properties; /** @@ -77,6 +78,7 @@ private Extensions( } Enumeration e = seq.getObjects(); + String error = null; while (e.hasMoreElements()) { @@ -86,13 +88,18 @@ private Extensions( { if (!Properties.isOverrideSet("org.bouncycastle.x509.ignore_repeated_extensions")) { - throw new IllegalArgumentException("repeated extension found: " + ext.getExtnId()); + error = "repeated extension found: " + ext.getExtnId(); + continue; } } extensions.put(ext.getExtnId(), ext); ordering.addElement(ext.getExtnId()); } + + if (error != null) { + throw new IllegalArgumentWarningException(error, this); + } } /** @@ -177,6 +184,7 @@ public ASN1Encodable getExtensionParsedValue(ASN1ObjectIdentifier oid) * extnValue OCTET STRING } * */ + @Override public ASN1Primitive toASN1Primitive() { ASN1EncodableVector vec = new ASN1EncodableVector(ordering.size()); diff --git a/core/src/main/java/org/bouncycastle/asn1/x509/TBSCertificate.java b/core/src/main/java/org/bouncycastle/asn1/x509/TBSCertificate.java index 76763291b6..c97da4240e 100644 --- a/core/src/main/java/org/bouncycastle/asn1/x509/TBSCertificate.java +++ b/core/src/main/java/org/bouncycastle/asn1/x509/TBSCertificate.java @@ -10,8 +10,13 @@ import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.DERTaggedObject; import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.util.IllegalArgumentWarningException; import org.bouncycastle.util.Properties; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + /** * The TBSCertificate object. *
@@ -47,6 +52,7 @@ public class TBSCertificate ASN1BitString issuerUniqueId; ASN1BitString subjectUniqueId; Extensions extensions; + Listerrors; public static TBSCertificate getInstance( ASN1TaggedObject obj, @@ -92,7 +98,7 @@ private TBSCertificate( boolean isV1 = false; boolean isV2 = false; - + if (version.hasValue(0)) { isV1 = true; @@ -103,7 +109,8 @@ else if (version.hasValue(1)) } else if (!version.hasValue(2)) { - throw new IllegalArgumentException("version number not recognised"); + addError( + String.format("Certificate version number value %d not 0, 1 or 2", version.getValue())); } serialNumber = ASN1Integer.getInstance(seq.getObjectAt(seqStart + 1)); @@ -129,9 +136,10 @@ else if (!version.hasValue(2)) int extras = seq.size() - (seqStart + 6) - 1; if (extras != 0 && isV1) { - throw new IllegalArgumentException("version 1 certificate contains extra data"); + addError("version 1 certificate contains extra data"); + extras = 0; // Ignore the extra data } - + while (extras > 0) { ASN1TaggedObject extra = (ASN1TaggedObject)seq.getObjectAt(seqStart + 6 + extras); @@ -147,15 +155,43 @@ else if (!version.hasValue(2)) case 3: if (isV2) { - throw new IllegalArgumentException("version 2 certificate cannot contain extensions"); + addError("version 2 certificate cannot contain extensions"); + throw new IllegalArgumentWarningException(errors, this); + } + try { + extensions = Extensions.getInstance(ASN1Sequence.getInstance(extra, true)); + } catch (IllegalArgumentWarningException ex) { + extensions = ex.getObject(Extensions.class); + addErrors(ex.getMessages()); } - extensions = Extensions.getInstance(ASN1Sequence.getInstance(extra, true)); break; default: - throw new IllegalArgumentException("Unknown tag encountered in structure: " + extra.getTagNo()); + addError("Unknown tag encountered in structure: " + extra.getTagNo()); + throw new IllegalArgumentWarningException(errors, this); } extras--; } + + if (errors != null) { + throw new IllegalArgumentWarningException(errors, this); + } + } + + private void addError(String error) { + if (errors == null) { + errors = new ArrayList<>(); + } + errors.add(error); + } + + private void addErrors(List errors) { + for (String error : errors) { + addError(error); + } + } + + public Collection getErrors() { + return errors; } public int getVersionNumber() @@ -218,6 +254,7 @@ public Extensions getExtensions() return extensions; } + @Override public ASN1Primitive toASN1Primitive() { if (Properties.getPropertyValue("org.bouncycastle.x509.allow_non-der_tbscert") != null) diff --git a/core/src/main/java/org/bouncycastle/crypto/params/RSAKeyParameters.java b/core/src/main/java/org/bouncycastle/crypto/params/RSAKeyParameters.java index 65b0b84ee4..1e2a3e89c8 100644 --- a/core/src/main/java/org/bouncycastle/crypto/params/RSAKeyParameters.java +++ b/core/src/main/java/org/bouncycastle/crypto/params/RSAKeyParameters.java @@ -1,10 +1,13 @@ package org.bouncycastle.crypto.params; import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.bouncycastle.math.Primes; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.IllegalArgumentWarningException; import org.bouncycastle.util.Properties; public class RSAKeyParameters @@ -41,16 +44,20 @@ public RSAKeyParameters( { super(isPrivate); + this.modulus = modulus; + this.exponent = exponent; + if (!isPrivate) { if ((exponent.intValue() & 1) == 0) { - throw new IllegalArgumentException("RSA publicExponent is even"); + throw new IllegalArgumentWarningException("RSA publicExponent is even", this); } } - this.modulus = validated.contains(modulus) ? modulus : validate(modulus, isInternal); - this.exponent = exponent; + if (!validated.contains(modulus)) { + validate(modulus, isInternal); + } } private BigInteger validate(BigInteger modulus, boolean isInternal) @@ -62,44 +69,48 @@ private BigInteger validate(BigInteger modulus, boolean isInternal) return modulus; } + List issues = new ArrayList<>(); + if ((modulus.intValue() & 1) == 0) { - throw new IllegalArgumentException("RSA modulus is even"); + issues.add("RSA modulus is even"); } // If you need to set this you need to have a serious word to whoever is generating // your keys. - if (Properties.isOverrideSet("org.bouncycastle.rsa.allow_unsafe_mod")) + if (!Properties.isOverrideSet("org.bouncycastle.rsa.allow_unsafe_mod")) { - return modulus; - } - - int maxBitLength = Properties.asInteger("org.bouncycastle.rsa.max_size", 15360); + int maxBitLength = Properties.asInteger("org.bouncycastle.rsa.max_size", 15360); - int modBitLength = modulus.bitLength(); - if (maxBitLength < modBitLength) - { - throw new IllegalArgumentException("modulus value out of range"); - } + int modBitLength = modulus.bitLength(); + if (maxBitLength < modBitLength) + { + issues.add("modulus value out of range"); + } - if (!modulus.gcd(SMALL_PRIMES_PRODUCT).equals(ONE)) - { - throw new IllegalArgumentException("RSA modulus has a small prime factor"); - } + if (!modulus.gcd(SMALL_PRIMES_PRODUCT).equals(ONE)) + { + issues.add("RSA modulus has a small prime factor"); + } - int bits = modulus.bitLength() / 2; - int iterations = Properties.asInteger("org.bouncycastle.rsa.max_mr_tests", getMRIterations(bits)); + int bits = modulus.bitLength() / 2; + int iterations = Properties.asInteger("org.bouncycastle.rsa.max_mr_tests", getMRIterations(bits)); - if (iterations > 0) - { - Primes.MROutput mr = Primes.enhancedMRProbablePrimeTest(modulus, CryptoServicesRegistrar.getSecureRandom(), iterations); - if (!mr.isProvablyComposite()) + if (iterations > 0) { - throw new IllegalArgumentException("RSA modulus is not composite"); + Primes.MROutput mr = Primes.enhancedMRProbablePrimeTest(modulus, CryptoServicesRegistrar.getSecureRandom(), iterations); + if (!mr.isProvablyComposite()) + { + issues.add("RSA modulus is not composite"); + } + } + + if (!issues.isEmpty()) { + throw new IllegalArgumentWarningException(issues, this); } - } - validated.add(modulus); + validated.add(modulus); + } return modulus; } diff --git a/core/src/main/java/org/bouncycastle/util/IllegalArgumentWarningException.java b/core/src/main/java/org/bouncycastle/util/IllegalArgumentWarningException.java new file mode 100644 index 0000000000..521a5efce0 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/util/IllegalArgumentWarningException.java @@ -0,0 +1,79 @@ +package org.bouncycastle.util; + +import java.util.Collections; +import java.util.List; + +/** + * Exception which is thrown when parsing a certificate when errors in the + * parsing are detected. This provides a list of the errors that were + * detected along with the (mostly) parsed object. + */ +public class IllegalArgumentWarningException extends IllegalArgumentException { + + private static final long serialVersionUID = 5735291408274180892L; + List messages; + Object object; + + /** + * Basic constructor. + * + * @param messages Non empty list of messages to associate with this Exception. + * @param object The partially parsed object. + * @param cause The underlying exception (if any). + */ + public IllegalArgumentWarningException(List messages, Object object, Throwable cause) { + super(messages.get(0), cause); + this.messages = messages; + this.object = object; + } + + /** + * Basic constructor. + * + * @param messages Non empty list of messages to associate with this Exception. + * @param object The partially parsed object. + */ + public IllegalArgumentWarningException(List messages, Object object) { + this(messages, object, null); + } + + /** + * Basic constructor. + * + * @param message Single message to be associated with this Exception. + * @param object The partially parsed object. + */ + public IllegalArgumentWarningException(String message, Object object) { + this(Collections.singletonList(message), object, null); + } + + /** + * Basic constructor. + * + * @param object The partially parsed object. + * @param cause The underlying exception. + */ + public IllegalArgumentWarningException(Object object, Throwable cause) { + this(Collections.singletonList(cause.getMessage()), object, cause); + } + + /** + * Gets the list of error messages. + */ + public List getMessages() { + return messages; + } + + /** + * This gets the partially parsed object -- but only if you provide the correct + * class! + * + * @param clazz The class of the object that you are expecting. + */ + public T getObject(Class clazz) { + if (clazz.isInstance(object)) { + return (T) object; + } + throw new IllegalArgumentException(messages.get(0), this); + } +} diff --git a/core/src/main/java/org/bouncycastle/util/test/SimpleTest.java b/core/src/main/java/org/bouncycastle/util/test/SimpleTest.java index 66ce33bd79..c429cb29cc 100644 --- a/core/src/main/java/org/bouncycastle/util/test/SimpleTest.java +++ b/core/src/main/java/org/bouncycastle/util/test/SimpleTest.java @@ -47,7 +47,7 @@ protected void isEquals( { if (!a.equals(b)) { - throw new TestFailedException(SimpleTestResult.failed(this, "no message")); + throw new TestFailedException(SimpleTestResult.failed(this, String.format("isEqual fail: %s != %s", a, b))); } } @@ -57,7 +57,7 @@ protected void isEquals( { if (a != b) { - throw new TestFailedException(SimpleTestResult.failed(this, "no message")); + throw new TestFailedException(SimpleTestResult.failed(this, String.format("isEqual fail: %s != %s", a, b))); } } diff --git a/prov/src/test/java/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java b/prov/src/test/java/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java index 1989c5f2e3..2e3dc17da4 100644 --- a/prov/src/test/java/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java +++ b/prov/src/test/java/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java @@ -582,7 +582,7 @@ private void checkInvalidCertPath() { checkInvalidPath(extInvTrust, extInvCA, extInvEE, "version 1 certificate contains extra data"); checkInvalidPath(extInvV2Trust, extInvV2CA, extInvV2EE, "version 2 certificate cannot contain extensions"); - checkInvalidPath(extInvVersionTrust, extInvVersionCA, extInvVersionEE, "version number not recognised"); + checkInvalidPath(extInvVersionTrust, extInvVersionCA, extInvVersionEE, "Certificate version number value 5 not 0, 1 or 2"); checkInvalidPath(extInvExtTrust, extInvExtCA, extInvExtEE, "repeated extension found: 2.5.29.15"); } @@ -621,7 +621,7 @@ private void checkInvalidPath(byte[] root, byte[] inter, byte[] ee, String expec } catch (CertPathValidatorException e) { - isTrue(e.getMessage().equals(expected)); + isEquals(e.getMessage(), expected); } // check that our cert factory also rejects - the EE is always the invalid one @@ -633,7 +633,7 @@ private void checkInvalidPath(byte[] root, byte[] inter, byte[] ee, String expec } catch (CertificateException e) { - isTrue(e.getMessage().equals("parsing issue: " + expected)); + isEquals(e.getMessage(), "parsing issue: " + expected); } }