package org.bouncycastle.jce.cert; import java.io.ByteArrayInputStream; import java.io.IOException; import java.math.BigInteger; import java.security.cert.CRL; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Object; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DERInteger; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.asn1.x509.X509Name; import org.bouncycastle.jce.PrincipalUtil; /** * A <code>CRLSelector</code> that selects <code>X509CRLs</code> that match * all specified criteria. This class is particularly useful when selecting CRLs * from a <code>CertStore</code> to check revocation status of a particular * certificate.<br /> * <br /> * When first constructed, an <code>X509CRLSelector</code> has no criteria * enabled and each of the <code>get</code> methods return a default value (<code>null</code>). * Therefore, the {@link #match match} method would return <code>true</code> * for any <code>X509CRL</code>. Typically, several criteria are enabled (by * calling {@link #setIssuerNames setIssuerNames} or * {@link #setDateAndTime setDateAndTime}, for instance) and then the * <code>X509CRLSelector</code> is passed to * {@link CertStore#getCRLs CertStore.getCRLs} or some similar method.<br /> * <br /> * Please refer to RFC 2459 for definitions of the X.509 CRL fields and * extensions mentioned below.<br /> * <br /> * <b>Concurrent Access</b><br /> * <br /> * Unless otherwise specified, the methods defined in this class are not * thread-safe. Multiple threads that need to access a single object * concurrently should synchronize amongst themselves and provide the necessary * locking. Multiple threads each manipulating separate objects need not * synchronize.<br /> * <br /> * Uses {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream}, * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence}, * {@link org.bouncycastle.asn1.ASN1ObjectIdentifier ASN1ObjectIdentifier}, * {@link org.bouncycastle.asn1.DEROutputStream DEROutputStream}, * {@link org.bouncycastle.asn1.ASN1Object ASN1Object}, * {@link org.bouncycastle.asn1.x509.X509Name X509Name} * * @see CRLSelector * @see X509CRL */ public class X509CRLSelector implements CRLSelector { private Set issuerNames = null; private Set issuerNamesX509 = null; private BigInteger minCRL = null; private BigInteger maxCRL = null; private Date dateAndTime = null; private X509Certificate certChecking = null; /** * Creates an <code>X509CRLSelector</code>. Initially, no criteria are * set so any <code>X509CRL</code> will match. */ public X509CRLSelector() { } /** * Sets the issuerNames criterion. The issuer distinguished name in the * <code>X509CRL</code> must match at least one of the specified * distinguished names. If <code>null</code>, any issuer distinguished * name will do.<br /> * <br /> * This method allows the caller to specify, with a single method call, the * complete set of issuer names which <code>X509CRLs</code> may contain. * The specified value replaces the previous value for the issuerNames * criterion.<br /> * <br /> * The <code>names</code> parameter (if not <code>null</code>) is a * <code>Collection</code> of names. Each name is a <code>String</code> * or a byte array representing a distinguished name (in RFC 2253 or ASN.1 * DER encoded form, respectively). If <code>null</code> is supplied as * the value for this argument, no issuerNames check will be performed.<br /> * <br /> * Note that the <code>names</code> parameter can contain duplicate * distinguished names, but they may be removed from the * <code>Collection</code> of names returned by the * {@link #getIssuerNames getIssuerNames} method.<br /> * <br /> * If a name is specified as a byte array, it should contain a single DER * encoded distinguished name, as defined in X.501. The ASN.1 notation for * this structure is as follows. * * <pre><code> * Name ::= CHOICE { * RDNSequence } * * RDNSequence ::= SEQUENCE OF RDN * * RDN ::= * SET SIZE (1 .. MAX) OF AttributeTypeAndValue * * AttributeTypeAndValue ::= SEQUENCE { * type AttributeType, * value AttributeValue } * * AttributeType ::= OBJECT IDENTIFIER * * AttributeValue ::= ANY DEFINED BY AttributeType * .... * DirectoryString ::= CHOICE { * teletexString TeletexString (SIZE (1..MAX)), * printableString PrintableString (SIZE (1..MAX)), * universalString UniversalString (SIZE (1..MAX)), * utf8String UTF8String (SIZE (1.. MAX)), * bmpString BMPString (SIZE (1..MAX)) } * </code></pre> * * <br /> * <br /> * Note that a deep copy is performed on the <code>Collection</code> to * protect against subsequent modifications. * * @param names * a <code>Collection</code> of names (or <code>null</code>) * * @exception IOException * if a parsing error occurs * * @see #getIssuerNames */ public void setIssuerNames(Collection names) throws IOException { if (names == null || names.isEmpty()) { issuerNames = null; issuerNamesX509 = null; } else { Object item; Iterator iter = names.iterator(); while (iter.hasNext()) { item = iter.next(); if (item instanceof String) { addIssuerName((String)item); } else if (item instanceof byte[]) { addIssuerName((byte[])item); } else { throw new IOException("name not byte[]or String: " + item.toString()); } } } } /** * Adds a name to the issuerNames criterion. The issuer distinguished name * in the <code>X509CRL</code> must match at least one of the specified * distinguished names.<br /> * <br /> * This method allows the caller to add a name to the set of issuer names * which <code>X509CRLs</code> may contain. The specified name is added to * any previous value for the issuerNames criterion. If the specified name * is a duplicate, it may be ignored.<br /> * <br /> * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name} for parsing the * name * * @param name * the name in RFC 2253 form * * @exception IOException * if a parsing error occurs */ public void addIssuerName(String name) throws IOException { if (issuerNames == null) { issuerNames = new HashSet(); issuerNamesX509 = new HashSet(); } X509Name nameX509; try { nameX509 = new X509Name(name); } catch (IllegalArgumentException ex) { throw new IOException(ex.getMessage()); } issuerNamesX509.add(nameX509); issuerNames.add(name); } /** * Adds a name to the issuerNames criterion. The issuer distinguished name * in the <code>X509CRL</code> must match at least one of the specified * distinguished names.<br /> * <br /> * This method allows the caller to add a name to the set of issuer names * which <code>X509CRLs</code> may contain. The specified name is added to * any previous value for the issuerNames criterion. If the specified name * is a duplicate, it may be ignored. If a name is specified as a byte * array, it should contain a single DER encoded distinguished name, as * defined in X.501. The ASN.1 notation for this structure is as follows.<br /> * <br /> * The name is provided as a byte array. This byte array should contain a * single DER encoded distinguished name, as defined in X.501. The ASN.1 * notation for this structure appears in the documentation for * {@link #setIssuerNames setIssuerNames(Collection names)}.<br /> * <br /> * Note that the byte array supplied here is cloned to protect against * subsequent modifications.<br /> * <br /> * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name} for parsing the * name, {@link org.bouncycastle.asn1.ASN1InputStream ASN1InputStream}, * {@link org.bouncycastle.asn1.ASN1Object ASN1Object} and * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence} * * @param name * a byte array containing the name in ASN.1 DER encoded form * * @exception IOException * if a parsing error occurs */ public void addIssuerName(byte[] name) throws IOException { if (issuerNames == null) { issuerNames = new HashSet(); issuerNamesX509 = new HashSet(); } ByteArrayInputStream inStream = new ByteArrayInputStream(name); ASN1InputStream derInStream = new ASN1InputStream(inStream); ASN1Object obj = derInStream.readObject(); if (obj instanceof ASN1Sequence) { issuerNamesX509.add(new X509Name((ASN1Sequence)obj)); } else { throw new IOException("parsing error"); } issuerNames.add(name.clone()); } /** * Sets the minCRLNumber criterion. The <code>X509CRL</code> must have a * CRL number extension whose value is greater than or equal to the * specified value. If <code>null</code>, no minCRLNumber check will be * done. * * @param minCRL * the minimum CRL number accepted (or <code>null</code>) */ public void setMinCRLNumber(BigInteger minCRL) { this.minCRL = minCRL; } /** * Sets the maxCRLNumber criterion. The <code>X509CRL</code> must have a * CRL number extension whose value is less than or equal to the specified * value. If <code>null</code>, no maxCRLNumber check will be done. * * @param maxCRL * the maximum CRL number accepted (or <code>null</code>) */ public void setMaxCRLNumber(BigInteger maxCRL) { this.maxCRL = maxCRL; } /** * Sets the dateAndTime criterion. The specified date must be equal to or * later than the value of the thisUpdate component of the * <code>X509CRL</code> and earlier than the value of the nextUpdate * component. There is no match if the <code>X509CRL</code> does not * contain a nextUpdate component. If <code>null</code>, no dateAndTime * check will be done.<br /> * <br /> * Note that the <code>Date</code> supplied here is cloned to protect * against subsequent modifications. * * @param dateAndTime * the <code>Date</code> to match against (or <code>null</code>) * * @see #getDateAndTime */ public void setDateAndTime(Date dateAndTime) { if (dateAndTime == null) { this.dateAndTime = null; } else { this.dateAndTime = new Date(dateAndTime.getTime()); } } /** * Sets the certificate being checked. This is not a criterion. Rather, it * is optional information that may help a <code>CertStore</code> find * CRLs that would be relevant when checking revocation for the specified * certificate. If <code>null</code> is specified, then no such optional * information is provided. * * @param cert * the <code>X509Certificate</code> being checked (or * <code>null</code>) * * @see #getCertificateChecking */ public void setCertificateChecking(X509Certificate cert) { certChecking = cert; } /** * Returns a copy of the issuerNames criterion. The issuer distinguished * name in the <code>X509CRL</code> must match at least one of the * specified distinguished names. If the value returned is <code>null</code>, * any issuer distinguished name will do.<br /> * <br /> * If the value returned is not <code>null</code>, it is a * <code>Collection</code> of names. Each name is a <code>String</code> * or a byte array representing a distinguished name (in RFC 2253 or ASN.1 * DER encoded form, respectively). Note that the <code>Collection</code> * returned may contain duplicate names.<br /> * <br /> * If a name is specified as a byte array, it should contain a single DER * encoded distinguished name, as defined in X.501. The ASN.1 notation for * this structure is given in the documentation for * {@link #setIssuerNames setIssuerNames(Collection names)}.<br /> * <br /> * Note that a deep copy is performed on the <code>Collection</code> to * protect against subsequent modifications. * * @return a <code>Collection</code> of names (or <code>null</code>) * @see #setIssuerNames */ public Collection getIssuerNames() { if (issuerNames == null) { return null; } Collection set = new HashSet(); Iterator iter = issuerNames.iterator(); Object item; while (iter.hasNext()) { item = iter.next(); if (item instanceof String) { set.add(new String((String)item)); } else if (item instanceof byte[]) { set.add(((byte[])item).clone()); } } return set; } /** * Returns the minCRLNumber criterion. The <code>X509CRL</code> must have * a CRL number extension whose value is greater than or equal to the * specified value. If <code>null</code>, no minCRLNumber check will be * done. * * @return the minimum CRL number accepted (or <code>null</code>) */ public BigInteger getMinCRL() { return minCRL; } /** * Returns the maxCRLNumber criterion. The <code>X509CRL</code> must have * a CRL number extension whose value is less than or equal to the specified * value. If <code>null</code>, no maxCRLNumber check will be done. * * @return the maximum CRL number accepted (or <code>null</code>) */ public BigInteger getMaxCRL() { return maxCRL; } /** * Returns the dateAndTime criterion. The specified date must be equal to or * later than the value of the thisUpdate component of the * <code>X509CRL</code> and earlier than the value of the nextUpdate * component. There is no match if the <code>X509CRL</code> does not * contain a nextUpdate component. If <code>null</code>, no dateAndTime * check will be done.<br /> * <br /> * Note that the <code>Date</code> returned is cloned to protect against * subsequent modifications. * * @return the <code>Date</code> to match against (or <code>null</code>) * * @see #setDateAndTime */ public Date getDateAndTime() { if (dateAndTime == null) { return null; } return new Date(dateAndTime.getTime()); } /** * Returns the certificate being checked. This is not a criterion. Rather, * it is optional information that may help a <code>CertStore</code> find * CRLs that would be relevant when checking revocation for the specified * certificate. If the value returned is <code>null</code>, then no such * optional information is provided. * * @return the certificate being checked (or <code>null</code>) * * @see #setCertificateChecking */ public X509Certificate getCertificateChecking() { return certChecking; } /** * Returns a printable representation of the <code>X509CRLSelector</code>.<br /> * <br /> * Uses * {@link org.bouncycastle.asn1.x509.X509Name#toString X509Name.toString} to * format the output * * @return a <code>String</code> describing the contents of the * <code>X509CRLSelector</code>. */ public String toString() { StringBuffer s = new StringBuffer(); s.append("X509CRLSelector: [\n"); if (issuerNamesX509 != null) { s.append(" IssuerNames:\n"); Iterator iter = issuerNamesX509.iterator(); while (iter.hasNext()) { s.append(" ").append(iter.next()).append('\n'); } } if (minCRL != null) { s.append(" minCRLNumber: ").append(minCRL).append('\n'); } if (maxCRL != null) { s.append(" maxCRLNumber: ").append(maxCRL).append('\n'); } if (dateAndTime != null) { s.append(" dateAndTime: ").append(dateAndTime).append('\n'); } if (certChecking != null) { s.append(" Certificate being checked: ").append(certChecking).append('\n'); } s.append(']'); return s.toString(); } /** * Decides whether a <code>CRL</code> should be selected.<br /> * <br /> * Uses * {@link org.bouncycastle.asn1.x509.X509Name#toString X509Name.toString} to * parse and to compare the crl parameter issuer and * {@link org.bouncycastle.asn1.x509.X509Extensions#CRLNumber CRLNumber} to * access the CRL number extension. * * @param crl * the <code>CRL</code> to be checked * * @return <code>true</code> if the <code>CRL</code> should be selected, * <code>false</code> otherwise */ public boolean match(CRL crl) { if (!(crl instanceof X509CRL)) { return false; } X509CRL crlX509 = (X509CRL)crl; boolean test; if (issuerNamesX509 != null) { Iterator iter = issuerNamesX509.iterator(); test = false; X509Name crlIssuer = null; try { crlIssuer = PrincipalUtil.getIssuerX509Principal(crlX509); } catch (Exception ex) { return false; } while (iter.hasNext()) { if (crlIssuer.equals(iter.next(), true)) { test = true; break; } } if (!test) { return false; } } byte[] data = crlX509.getExtensionValue(X509Extensions.CRLNumber .getId()); if (data != null) { try { ByteArrayInputStream inStream = new ByteArrayInputStream(data); ASN1InputStream derInputStream = new ASN1InputStream(inStream); inStream = new ByteArrayInputStream( ((ASN1OctetString)derInputStream.readObject()) .getOctets()); derInputStream = new ASN1InputStream(inStream); BigInteger crlNumber = ((DERInteger)derInputStream.readObject()) .getPositiveValue(); if (minCRL != null && minCRL.compareTo(crlNumber) > 0) { return false; } if (maxCRL != null && maxCRL.compareTo(crlNumber) < 0) { return false; } } catch (IOException ex) { return false; } } else if (minCRL != null || maxCRL != null) { return false; } if (dateAndTime != null) { Date check = crlX509.getThisUpdate(); if (check == null) { return false; } else if (dateAndTime.before(check)) { return false; } check = crlX509.getNextUpdate(); if (check == null) { return false; } else if (!dateAndTime.before(check)) { return false; } } return true; } /** * Returns a copy of this object. * * @return the copy */ public Object clone() { try { X509CRLSelector copy = (X509CRLSelector)super.clone(); if (issuerNames != null) { copy.issuerNames = new HashSet(); Iterator iter = issuerNames.iterator(); Object obj; while (iter.hasNext()) { obj = iter.next(); if (obj instanceof byte[]) { copy.issuerNames.add(((byte[])obj).clone()); } else { copy.issuerNames.add(obj); } } copy.issuerNamesX509 = new HashSet(issuerNamesX509); } return copy; } catch (CloneNotSupportedException e) { /* Cannot happen */ throw new InternalError(e.toString()); } } /** * Decides whether a <code>CRL</code> should be selected. * * @param crl * the <code>CRL</code> to be checked * * @return <code>true</code> if the <code>CRL</code> should be selected, * <code>false</code> otherwise */ public boolean equals(Object obj) { if (!(obj instanceof X509CRLSelector)) { return false; } X509CRLSelector equalsCRL = (X509CRLSelector)obj; if (!equals(dateAndTime, equalsCRL.dateAndTime)) { return false; } if (!equals(minCRL, equalsCRL.minCRL)) { return false; } if (!equals(maxCRL, equalsCRL.maxCRL)) { return false; } if (!equals(issuerNamesX509, equalsCRL.issuerNamesX509)) { return false; } if (!equals(certChecking, equalsCRL.certChecking)) { return false; } return true; } /** * Return <code>true</code> if two Objects are unequal. * This means that one is <code>null</code> and the other is * not or <code>obj1.equals(obj2)</code> returns * <code>false</code>. **/ private boolean equals(Object obj1, Object obj2) { if (obj1 == null) { if (obj2 != null) { return true; } } else if (!obj1.equals(obj2)) { return true; } return false; } }