diff --git a/ph-security/pom.xml b/ph-security/pom.xml
index 92abea437..2a97ff8d2 100644
--- a/ph-security/pom.xml
+++ b/ph-security/pom.xml
@@ -73,7 +73,7 @@
com.helger.commons
ph-bc
- true
+ test
com.helger.commons
diff --git a/ph-security/src/main/java/com/helger/security/crl/CRLDistributionPointParser.java b/ph-security/src/main/java/com/helger/security/crl/CRLDistributionPointParser.java
new file mode 100644
index 000000000..8e70e2353
--- /dev/null
+++ b/ph-security/src/main/java/com/helger/security/crl/CRLDistributionPointParser.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2014-2026 Philip Helger (www.helger.com)
+ * philip[at]helger[dot]com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.helger.security.crl;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.jspecify.annotations.NonNull;
+
+import com.helger.collection.commons.CommonsArrayList;
+import com.helger.collection.commons.ICommonsList;
+
+/**
+ * Parser for the small DER subset used by the X.509 CRL Distribution Points extension.
+ *
+ * @author GT
+ */
+final class CRLDistributionPointParser
+{
+ private static final int TAG_OCTET_STRING = 0x04;
+ private static final int TAG_SEQUENCE = 0x30;
+ private static final int TAG_DISTRIBUTION_POINT_NAME = 0xa0;
+ private static final int TAG_FULL_NAME = 0xa0;
+ private static final int TAG_URI = 0x86;
+
+ private CRLDistributionPointParser ()
+ {}
+
+ @NonNull
+ public static ICommonsList parse (final byte @NonNull [] aExtensionValue)
+ {
+ try
+ {
+ final DERReader aOuterReader = new DERReader (aExtensionValue);
+ final DERValue aExtensionOctets = aOuterReader.readExpected (TAG_OCTET_STRING);
+ aOuterReader.requireEnd ();
+
+ final DERReader aExtensionReader = aExtensionOctets.createReader ();
+ final DERValue aDistributionPointsSequence = aExtensionReader.readExpected (TAG_SEQUENCE);
+ aExtensionReader.requireEnd ();
+
+ final ICommonsList ret = new CommonsArrayList <> ();
+ final DERReader aDistributionPointsReader = aDistributionPointsSequence.createReader ();
+ while (aDistributionPointsReader.hasRemaining ())
+ {
+ final DERValue aDistributionPoint = aDistributionPointsReader.readExpected (TAG_SEQUENCE);
+ _readDistributionPoint (aDistributionPoint, ret);
+ }
+ return ret;
+ }
+ catch (final IOException ex)
+ {
+ throw new UncheckedIOException ("Failed to decode the X.509 CRL Distribution Points extension", ex);
+ }
+ }
+
+ private static void _readDistributionPoint (@NonNull final DERValue aDistributionPoint,
+ @NonNull final ICommonsList aTarget) throws IOException
+ {
+ final DERReader aFieldsReader = aDistributionPoint.createReader ();
+ while (aFieldsReader.hasRemaining ())
+ {
+ final DERValue aField = aFieldsReader.read ();
+ if (aField.getTag () == TAG_DISTRIBUTION_POINT_NAME)
+ _readDistributionPointName (aField, aTarget);
+ }
+ }
+
+ private static void _readDistributionPointName (@NonNull final DERValue aDistributionPointName,
+ @NonNull final ICommonsList aTarget) throws IOException
+ {
+ final DERReader aNameReader = aDistributionPointName.createReader ();
+ final DERValue aName = aNameReader.read ();
+ aNameReader.requireEnd ();
+
+ if (aName.getTag () == TAG_FULL_NAME)
+ {
+ final DERReader aGeneralNamesReader = aName.createReader ();
+ while (aGeneralNamesReader.hasRemaining ())
+ {
+ final DERValue aGeneralName = aGeneralNamesReader.read ();
+ if (aGeneralName.getTag () == TAG_URI)
+ aTarget.add (_readIA5String (aGeneralName.getValue ()).trim ());
+ }
+ }
+ }
+
+ @NonNull
+ private static String _readIA5String (final byte @NonNull [] aValue) throws IOException
+ {
+ for (final byte b : aValue)
+ if ((b & 0x80) != 0)
+ throw new IOException ("IA5String contains a non-ASCII byte");
+ return new String (aValue, StandardCharsets.US_ASCII);
+ }
+
+ private static final class DERReader
+ {
+ private final byte [] m_aData;
+ private int m_nPosition;
+
+ DERReader (final byte @NonNull [] aData)
+ {
+ m_aData = aData;
+ }
+
+ public boolean hasRemaining ()
+ {
+ return m_nPosition < m_aData.length;
+ }
+
+ public void requireEnd () throws IOException
+ {
+ if (hasRemaining ())
+ throw new IOException ("Unexpected trailing DER data");
+ }
+
+ @NonNull
+ public DERValue readExpected (final int nExpectedTag) throws IOException
+ {
+ final DERValue ret = read ();
+ if (ret.getTag () != nExpectedTag)
+ throw new IOException ("Expected DER tag " + nExpectedTag + " but found " + ret.getTag ());
+ return ret;
+ }
+
+ @NonNull
+ public DERValue read () throws IOException
+ {
+ final int nTag = _readUnsignedByte ();
+ if ((nTag & 0x1f) == 0x1f)
+ throw new IOException ("High-tag-number DER values are not supported");
+
+ final int nLength = _readLength ();
+ if (nLength > m_aData.length - m_nPosition)
+ throw new IOException ("DER value length exceeds the available data");
+
+ final byte [] aValue = Arrays.copyOfRange (m_aData, m_nPosition, m_nPosition + nLength);
+ m_nPosition += nLength;
+ return new DERValue (nTag, aValue);
+ }
+
+ private int _readLength () throws IOException
+ {
+ final int nFirst = _readUnsignedByte ();
+ if ((nFirst & 0x80) == 0)
+ return nFirst;
+
+ final int nLengthBytes = nFirst & 0x7f;
+ if (nLengthBytes == 0)
+ throw new IOException ("Indefinite-length encoding is not valid DER");
+ if (nLengthBytes > 4 || nLengthBytes > m_aData.length - m_nPosition)
+ throw new IOException ("Invalid DER length field");
+ if ((m_aData[m_nPosition] & 0xff) == 0)
+ throw new IOException ("DER length has a redundant leading zero");
+
+ long nLength = 0;
+ for (int i = 0; i < nLengthBytes; ++i)
+ nLength = (nLength << 8) | _readUnsignedByte ();
+ if (nLength < 128 || nLength > Integer.MAX_VALUE)
+ throw new IOException ("Invalid DER length value");
+ return (int) nLength;
+ }
+
+ private int _readUnsignedByte () throws IOException
+ {
+ if (!hasRemaining ())
+ throw new IOException ("Unexpected end of DER data");
+ return m_aData[m_nPosition++] & 0xff;
+ }
+ }
+
+ private static final class DERValue
+ {
+ private final int m_nTag;
+ private final byte [] m_aValue;
+
+ DERValue (final int nTag, final byte @NonNull [] aValue)
+ {
+ m_nTag = nTag;
+ m_aValue = aValue;
+ }
+
+ public int getTag ()
+ {
+ return m_nTag;
+ }
+
+ public byte @NonNull [] getValue ()
+ {
+ return m_aValue;
+ }
+
+ @NonNull
+ public DERReader createReader ()
+ {
+ return new DERReader (m_aValue);
+ }
+ }
+}
diff --git a/ph-security/src/main/java/com/helger/security/crl/CRLHelper.java b/ph-security/src/main/java/com/helger/security/crl/CRLHelper.java
index 59e7b91c1..c5ed268de 100644
--- a/ph-security/src/main/java/com/helger/security/crl/CRLHelper.java
+++ b/ph-security/src/main/java/com/helger/security/crl/CRLHelper.java
@@ -16,24 +16,12 @@
*/
package com.helger.security.crl;
-import java.io.IOException;
-import java.io.UncheckedIOException;
import java.security.cert.CRLException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
-import org.bouncycastle.asn1.ASN1IA5String;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Primitive;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.x509.CRLDistPoint;
-import org.bouncycastle.asn1.x509.DistributionPoint;
-import org.bouncycastle.asn1.x509.DistributionPointName;
-import org.bouncycastle.asn1.x509.Extension;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.GeneralNames;
import org.jspecify.annotations.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,8 +34,7 @@
import com.helger.collection.commons.ICommonsList;
/**
- * Helper class to deal with CRLs. This class requires BouncyCastle to be in the
- * classpath.
+ * Helper class to deal with CRLs.
*
* @author Philip Helger
* @since 11.2.0
@@ -56,6 +43,7 @@
public final class CRLHelper
{
private static final Logger LOGGER = LoggerFactory.getLogger (CRLHelper.class);
+ private static final String CRL_DISTRIBUTION_POINTS_OID = "2.5.29.31";
private CRLHelper ()
{}
@@ -104,67 +92,14 @@ public static X509CRL convertToCRL (final byte @NonNull @Nonempty [] aCRLBytes)
public static ICommonsList getAllDistributionPoints (@NonNull final X509Certificate aCert)
{
ValueEnforcer.notNull (aCert, "Certificate");
- final ICommonsList ret = new CommonsArrayList <> ();
+ final byte [] aExtensionValue = aCert.getExtensionValue (CRL_DISTRIBUTION_POINTS_OID);
+ if (aExtensionValue == null)
+ return new CommonsArrayList <> ();
- // Gets the DER-encoded OCTET string for the extension value for
- // CRLDistributionPoints
- final byte [] aExtensionValue = aCert.getExtensionValue (Extension.cRLDistributionPoints.getId ());
- if (aExtensionValue != null)
- {
- // crlDPExtensionValue is encoded in ASN.1 format.
- try (final ASN1InputStream aAsn1IS = new ASN1InputStream (aExtensionValue))
- {
- // DER (Distinguished Encoding Rules) is one of ASN.1 encoding rules
- // defined in ITU-T X.690, 2002, specification.
- // ASN.1 encoding rules can be used to encode any data object into a
- // binary file. Read the object in octets.
- final CRLDistPoint aDistPoint;
- try
- {
- final DEROctetString aCrlDEROctetString = (DEROctetString) aAsn1IS.readObject ();
- // Get Input stream in octets
- try (final ASN1InputStream aAsn1InOctets = new ASN1InputStream (aCrlDEROctetString.getOctets ()))
- {
- final ASN1Primitive aCrlDERObject = aAsn1InOctets.readObject ();
- aDistPoint = CRLDistPoint.getInstance (aCrlDERObject);
- }
- }
- catch (final IOException e)
- {
- throw new UncheckedIOException (e);
- }
-
- // Loop through ASN1Encodable DistributionPoints
- for (final DistributionPoint aDP : aDistPoint.getDistributionPoints ())
- {
- // get ASN1Encodable DistributionPointName
- final DistributionPointName aDPName = aDP.getDistributionPoint ();
- if (aDPName != null && aDPName.getType () == DistributionPointName.FULL_NAME)
- {
- // Create ASN1Encodable General Names
- final GeneralName [] aGenNames = GeneralNames.getInstance (aDPName.getName ()).getNames ();
- // Look for a URI
- for (final GeneralName aGenName : aGenNames)
- {
- if (aGenName.getTagNo () == GeneralName.uniformResourceIdentifier)
- {
- // DERIA5String contains an ascii string.
- // A IA5String is a restricted character string type in the
- // ASN.1 notation
- final String sURL = ASN1IA5String.getInstance (aGenName.getName ()).getString ().trim ();
- if (LOGGER.isDebugEnabled ())
- LOGGER.debug ("Found CRL URL '" + sURL + "' in certificate");
- ret.add (sURL);
- }
- }
- }
- }
- }
- catch (final IOException ex)
- {
- throw new UncheckedIOException (ex);
- }
- }
+ final ICommonsList ret = CRLDistributionPointParser.parse (aExtensionValue);
+ if (LOGGER.isDebugEnabled ())
+ for (final String sURL : ret)
+ LOGGER.debug ("Found CRL URL '" + sURL + "' in certificate");
return ret;
}
}
diff --git a/ph-security/src/main/java/com/helger/security/oscp/EOCSPResponseStatus.java b/ph-security/src/main/java/com/helger/security/oscp/EOCSPResponseStatus.java
index 8a86df24d..62c551be9 100644
--- a/ph-security/src/main/java/com/helger/security/oscp/EOCSPResponseStatus.java
+++ b/ph-security/src/main/java/com/helger/security/oscp/EOCSPResponseStatus.java
@@ -16,7 +16,6 @@
*/
package com.helger.security.oscp;
-import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
import org.jspecify.annotations.Nullable;
import com.helger.base.id.IHasIntID;
@@ -30,12 +29,12 @@
*/
public enum EOCSPResponseStatus implements ISuccessIndicator, IHasIntID
{
- SUCCESSFUL (OCSPResponseStatus.SUCCESSFUL),
- MALFORMED_REQUEST (OCSPResponseStatus.MALFORMED_REQUEST),
- INTERNAL_ERROR (OCSPResponseStatus.INTERNAL_ERROR),
- TRY_LATER (OCSPResponseStatus.TRY_LATER),
- SIG_REQUIRED (OCSPResponseStatus.SIG_REQUIRED),
- UNAUTHORIZED (OCSPResponseStatus.UNAUTHORIZED);
+ SUCCESSFUL (0),
+ MALFORMED_REQUEST (1),
+ INTERNAL_ERROR (2),
+ TRY_LATER (3),
+ SIG_REQUIRED (5),
+ UNAUTHORIZED (6);
private final int m_nValue;
diff --git a/ph-security/src/test/java/com/helger/security/PhSecurityWithoutBCProbe.java b/ph-security/src/test/java/com/helger/security/PhSecurityWithoutBCProbe.java
new file mode 100644
index 000000000..877c6652e
--- /dev/null
+++ b/ph-security/src/test/java/com/helger/security/PhSecurityWithoutBCProbe.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014-2026 Philip Helger (www.helger.com)
+ * philip[at]helger[dot]com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.helger.security;
+
+import java.io.FileInputStream;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+
+import com.helger.collection.commons.ICommonsList;
+import com.helger.security.certificate.CertificateHelper;
+import com.helger.security.certificate.TrustedCACertificates;
+import com.helger.security.crl.CRLHelper;
+import com.helger.security.oscp.EOCSPResponseStatus;
+
+/** Invoked through an isolated class loader by {@link PhSecurityWithoutBCTest}. */
+public final class PhSecurityWithoutBCProbe
+{
+ private PhSecurityWithoutBCProbe ()
+ {}
+
+ public static String verify () throws Exception
+ {
+ final KeyStore aAPKeyStore = KeyStore.getInstance ("PKCS12");
+ try (final FileInputStream aIS = new FileInputStream ("src/test/resources/keystores/keystore-pw-peppol-expired-2023.p12"))
+ {
+ aAPKeyStore.load (aIS, "peppol".toCharArray ());
+ }
+ final X509Certificate aAPCert = (X509Certificate) aAPKeyStore.getCertificate (aAPKeyStore.aliases ().nextElement ());
+ if (CertificateHelper.isCA (aAPCert))
+ throw new IllegalStateException ("The AP certificate must not be treated as a CA");
+
+ final ICommonsList aCRLURLs = CRLHelper.getAllDistributionPoints (aAPCert);
+ if (aCRLURLs.size () != 1)
+ throw new IllegalStateException ("Expected one CRL distribution point but found " + aCRLURLs.size ());
+
+ final KeyStore aTrustStore = KeyStore.getInstance ("JKS");
+ try (final FileInputStream aIS = new FileInputStream ("src/test/resources/keystores/truststore-peppol-prod.jks"))
+ {
+ aTrustStore.load (aIS, "peppol".toCharArray ());
+ }
+ final X509Certificate aCACert = (X509Certificate) aTrustStore.getCertificate (aTrustStore.aliases ().nextElement ());
+ final TrustedCACertificates aTrustedCAs = new TrustedCACertificates ();
+ aTrustedCAs.addTrustedCACertificate (aCACert);
+
+ return aCRLURLs.get (0) +
+ '|' +
+ aTrustedCAs.getAllTrustedCACertificates ().size () +
+ '|' +
+ EOCSPResponseStatus.SUCCESSFUL.getID () +
+ ',' +
+ EOCSPResponseStatus.UNAUTHORIZED.getID ();
+ }
+}
diff --git a/ph-security/src/test/java/com/helger/security/PhSecurityWithoutBCTest.java b/ph-security/src/test/java/com/helger/security/PhSecurityWithoutBCTest.java
new file mode 100644
index 000000000..b924cb28a
--- /dev/null
+++ b/ph-security/src/test/java/com/helger/security/PhSecurityWithoutBCTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014-2026 Philip Helger (www.helger.com)
+ * philip[at]helger[dot]com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.helger.security;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import org.junit.Test;
+
+/** Verifies the standard X.509 helpers without Bouncy Castle or {@code ph-bc}. */
+public final class PhSecurityWithoutBCTest
+{
+ private static final String PROBE_CLASS = PhSecurityWithoutBCProbe.class.getName ();
+
+ @Test
+ public void testStandardX509HelpersWithoutBC () throws Exception
+ {
+ final URL [] aURLs = { new File ("target/classes").toURI ().toURL (),
+ new File ("target/test-classes").toURI ().toURL () };
+ try (final URLClassLoader aCL = new URLClassLoader (aURLs, getClass ().getClassLoader ())
+ {
+ @Override
+ protected Class > loadClass (final String sName, final boolean bResolve) throws ClassNotFoundException
+ {
+ if (sName.startsWith ("org.bouncycastle.") || sName.startsWith ("com.helger.bc."))
+ throw new ClassNotFoundException ("Bouncy Castle deliberately hidden from test class loader");
+
+ if (sName.startsWith ("com.helger.security."))
+ synchronized (getClassLoadingLock (sName))
+ {
+ Class > ret = findLoadedClass (sName);
+ if (ret == null)
+ ret = findClass (sName);
+ if (bResolve)
+ resolveClass (ret);
+ return ret;
+ }
+
+ return super.loadClass (sName, bResolve);
+ }
+ })
+ {
+ _assertClassIsHidden (aCL, "org.bouncycastle.asn1.ASN1Primitive");
+ _assertClassIsHidden (aCL, "com.helger.bc.PBCProvider");
+
+ final Class > aProbeClass = Class.forName (PROBE_CLASS, true, aCL);
+ final String sResult = (String) aProbeClass.getMethod ("verify").invoke (null);
+ assertEquals ("http://pki-crl.symauth.com/ca_6a937734a393a0805bf33cda8b331093/LatestCRL.crl|1|0,6",
+ sResult);
+ }
+ }
+
+ private static void _assertClassIsHidden (final ClassLoader aClassLoader, final String sClassName)
+ {
+ try
+ {
+ aClassLoader.loadClass (sClassName);
+ fail (sClassName + " must not be visible to the isolated class loader");
+ }
+ catch (final ClassNotFoundException ex)
+ {
+ // Expected
+ }
+ }
+}
diff --git a/ph-security/src/test/java/com/helger/security/crl/CRLHelperTest.java b/ph-security/src/test/java/com/helger/security/crl/CRLHelperTest.java
index f5a8a8abc..49e14a330 100644
--- a/ph-security/src/test/java/com/helger/security/crl/CRLHelperTest.java
+++ b/ph-security/src/test/java/com/helger/security/crl/CRLHelperTest.java
@@ -18,12 +18,20 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
import java.io.File;
+import java.io.UncheckedIOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.X509Certificate;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.DistributionPoint;
+import org.bouncycastle.asn1.x509.DistributionPointName;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
import org.junit.Test;
import com.helger.collection.commons.ICommonsList;
@@ -56,4 +64,37 @@ public void testGetAllDistributionPoints () throws KeyStoreException
assertEquals (1, aList.size ());
assertEquals ("http://pki-crl.symauth.com/ca_6a937734a393a0805bf33cda8b331093/LatestCRL.crl", aList.get (0));
}
+
+ @Test
+ public void testRejectsTruncatedDER ()
+ {
+ assertThrows (UncheckedIOException.class,
+ () -> CRLDistributionPointParser.parse (new byte [] { 0x04, (byte) 0x82, 0x01 }));
+ }
+
+ @Test
+ public void testMultipleDistributionPointsAndGeneralNames () throws Exception
+ {
+ final GeneralNames aFullNames = new GeneralNames (new GeneralName [] { new GeneralName (GeneralName.dNSName,
+ "crl.example.org"),
+ new GeneralName (GeneralName.uniformResourceIdentifier,
+ "https://crl.example.org/one.crl"),
+ new GeneralName (GeneralName.uniformResourceIdentifier,
+ "ldap://crl.example.org/two") });
+ final DistributionPoint aNamedDistributionPoint = new DistributionPoint (new DistributionPointName (DistributionPointName.FULL_NAME,
+ aFullNames),
+ null,
+ null);
+ final DistributionPoint aIssuerOnlyDistributionPoint = new DistributionPoint (null,
+ null,
+ new GeneralNames (new GeneralName (GeneralName.uniformResourceIdentifier,
+ "https://issuer.example.org/not-a-distribution-point.crl")));
+ final byte [] aEncodedExtension = new DEROctetString (new CRLDistPoint (new DistributionPoint [] { aNamedDistributionPoint,
+ aIssuerOnlyDistributionPoint }).getEncoded ()).getEncoded ();
+
+ final ICommonsList aURLs = CRLDistributionPointParser.parse (aEncodedExtension);
+ assertEquals (2, aURLs.size ());
+ assertEquals ("https://crl.example.org/one.crl", aURLs.get (0));
+ assertEquals ("ldap://crl.example.org/two", aURLs.get (1));
+ }
}