/* * Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.security.cert; import java.io.IOException; import java.math.BigInteger; import java.util.*; import javax.security.auth.x500.X500Principal; import sun.security.util.Debug; import sun.security.util.DerInputStream; import sun.security.x509.CRLNumberExtension; import sun.security.x509.X500Name; /** * A CRLSelector that selects X509CRLs that * match all specified criteria. This class is particularly useful when * selecting CRLs from a CertStore to check revocation status * of a particular certificate. *

* When first constructed, an X509CRLSelector has no criteria * enabled and each of the get methods return a default * value (null). Therefore, the {@link #match match} method * would return true for any X509CRL. Typically, * several criteria are enabled (by calling {@link #setIssuers setIssuers} * or {@link #setDateAndTime setDateAndTime}, for instance) and then the * X509CRLSelector is passed to * {@link CertStore#getCRLs CertStore.getCRLs} or some similar * method. *

* Please refer to RFC 3280: * Internet X.509 Public Key Infrastructure Certificate and CRL Profile * for definitions of the X.509 CRL fields and extensions mentioned below. *

* Concurrent Access *

* 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. * * @see CRLSelector * @see X509CRL * * @since 1.4 * @author Steve Hanna */ public class X509CRLSelector implements CRLSelector { static { CertPathHelperImpl.initialize(); } private static final Debug debug = Debug.getInstance("certpath"); private HashSet issuerNames; private HashSet issuerX500Principals; private BigInteger minCRL; private BigInteger maxCRL; private Date dateAndTime; private X509Certificate certChecking; private long skew = 0; /** * Creates an X509CRLSelector. Initially, no criteria are set * so any X509CRL will match. */ public X509CRLSelector() {} /** * Sets the issuerNames criterion. The issuer distinguished name in the * X509CRL must match at least one of the specified * distinguished names. If null, any issuer distinguished name * will do. *

* This method allows the caller to specify, with a single method call, * the complete set of issuer names which X509CRLs may contain. * The specified value replaces the previous value for the issuerNames * criterion. *

* The names parameter (if not null) is a * Collection of X500Principals. *

* Note that the names parameter can contain duplicate * distinguished names, but they may be removed from the * Collection of names returned by the * {@link #getIssuers getIssuers} method. *

* Note that a copy is performed on the Collection to * protect against subsequent modifications. * * @param issuers a Collection of X500Principals * (or null) * @see #getIssuers * @since 1.5 */ public void setIssuers(Collection issuers) { if ((issuers == null) || issuers.isEmpty()) { issuerNames = null; issuerX500Principals = null; } else { // clone issuerX500Principals = new HashSet(issuers); issuerNames = new HashSet(); for (X500Principal p : issuerX500Principals) { issuerNames.add(p.getEncoded()); } } } /** * Note: use {@linkplain #setIssuers(Collection)} instead * or only specify the byte array form of distinguished names when using * this method. See {@link #addIssuerName(String)} for more information. *

* Sets the issuerNames criterion. The issuer distinguished name in the * X509CRL must match at least one of the specified * distinguished names. If null, any issuer distinguished name * will do. *

* This method allows the caller to specify, with a single method call, * the complete set of issuer names which X509CRLs may contain. * The specified value replaces the previous value for the issuerNames * criterion. *

* The names parameter (if not null) is a * Collection of names. Each name is a String * or a byte array representing a distinguished name (in * RFC 2253 or * ASN.1 DER encoded form, respectively). If null is supplied * as the value for this argument, no issuerNames check will be performed. *

* Note that the names parameter can contain duplicate * distinguished names, but they may be removed from the * Collection of names returned by the * {@link #getIssuerNames getIssuerNames} method. *

* 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. *


     * Name ::= CHOICE {
     *   RDNSequence }
     *
     * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
     *
     * RelativeDistinguishedName ::=
     *   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)) }
     * 
*

* Note that a deep copy is performed on the Collection to * protect against subsequent modifications. * * @param names a Collection of names (or null) * @throws IOException if a parsing error occurs * @see #getIssuerNames */ public void setIssuerNames(Collection names) throws IOException { if (names == null || names.size() == 0) { issuerNames = null; issuerX500Principals = null; } else { HashSet tempNames = cloneAndCheckIssuerNames(names); // Ensure that we either set both of these or neither issuerX500Principals = parseIssuerNames(tempNames); issuerNames = tempNames; } } /** * Adds a name to the issuerNames criterion. The issuer distinguished * name in the X509CRL must match at least one of the specified * distinguished names. *

* This method allows the caller to add a name to the set of issuer names * which X509CRLs 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. * * @param issuer the issuer as X500Principal * @since 1.5 */ public void addIssuer(X500Principal issuer) { addIssuerNameInternal(issuer.getEncoded(), issuer); } /** * Denigrated, use * {@linkplain #addIssuer(X500Principal)} or * {@linkplain #addIssuerName(byte[])} instead. This method should not be * relied on as it can fail to match some CRLs because of a loss of * encoding information in the RFC 2253 String form of some distinguished * names. *

* Adds a name to the issuerNames criterion. The issuer distinguished * name in the X509CRL must match at least one of the specified * distinguished names. *

* This method allows the caller to add a name to the set of issuer names * which X509CRLs 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. * * @param name the name in RFC 2253 form * @throws IOException if a parsing error occurs */ public void addIssuerName(String name) throws IOException { addIssuerNameInternal(name, new X500Name(name).asX500Principal()); } /** * Adds a name to the issuerNames criterion. The issuer distinguished * name in the X509CRL must match at least one of the specified * distinguished names. *

* This method allows the caller to add a name to the set of issuer names * which X509CRLs 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. *

* 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)}. *

* Note that the byte array supplied here is cloned to protect against * subsequent modifications. * * @param name a byte array containing the name in ASN.1 DER encoded form * @throws IOException if a parsing error occurs */ public void addIssuerName(byte[] name) throws IOException { // clone because byte arrays are modifiable addIssuerNameInternal(name.clone(), new X500Name(name).asX500Principal()); } /** * A private method that adds a name (String or byte array) to the * issuerNames criterion. The issuer distinguished * name in the X509CRL must match at least one of the specified * distinguished names. * * @param name the name in string or byte array form * @param principal the name in X500Principal form * @throws IOException if a parsing error occurs */ private void addIssuerNameInternal(Object name, X500Principal principal) { if (issuerNames == null) { issuerNames = new HashSet(); } if (issuerX500Principals == null) { issuerX500Principals = new HashSet(); } issuerNames.add(name); issuerX500Principals.add(principal); } /** * Clone and check an argument of the form passed to * setIssuerNames. Throw an IOException if the argument is malformed. * * @param names a Collection of names. Each entry is a * String or a byte array (the name, in string or ASN.1 * DER encoded form, respectively). null is * not an acceptable value. * @return a deep copy of the specified Collection * @throws IOException if a parsing error occurs */ private static HashSet cloneAndCheckIssuerNames(Collection names) throws IOException { HashSet namesCopy = new HashSet(); Iterator i = names.iterator(); while (i.hasNext()) { Object nameObject = i.next(); if (!(nameObject instanceof byte []) && !(nameObject instanceof String)) throw new IOException("name not byte array or String"); if (nameObject instanceof byte []) namesCopy.add(((byte []) nameObject).clone()); else namesCopy.add(nameObject); } return(namesCopy); } /** * Clone an argument of the form passed to setIssuerNames. * Throw a RuntimeException if the argument is malformed. *

* This method wraps cloneAndCheckIssuerNames, changing any IOException * into a RuntimeException. This method should be used when the object being * cloned has already been checked, so there should never be any exceptions. * * @param names a Collection of names. Each entry is a * String or a byte array (the name, in string or ASN.1 * DER encoded form, respectively). null is * not an acceptable value. * @return a deep copy of the specified Collection * @throws RuntimeException if a parsing error occurs */ private static HashSet cloneIssuerNames(Collection names) { try { return cloneAndCheckIssuerNames(names); } catch (IOException ioe) { throw new RuntimeException(ioe); } } /** * Parse an argument of the form passed to setIssuerNames, * returning a Collection of issuerX500Principals. * Throw an IOException if the argument is malformed. * * @param names a Collection of names. Each entry is a * String or a byte array (the name, in string or ASN.1 * DER encoded form, respectively). Null is * not an acceptable value. * @return a HashSet of issuerX500Principals * @throws IOException if a parsing error occurs */ private static HashSet parseIssuerNames(Collection names) throws IOException { HashSet x500Principals = new HashSet(); for (Iterator t = names.iterator(); t.hasNext(); ) { Object nameObject = t.next(); if (nameObject instanceof String) { x500Principals.add(new X500Name((String)nameObject).asX500Principal()); } else { try { x500Principals.add(new X500Principal((byte[])nameObject)); } catch (IllegalArgumentException e) { throw (IOException)new IOException("Invalid name").initCause(e); } } } return x500Principals; } /** * Sets the minCRLNumber criterion. The X509CRL must have a * CRL number extension whose value is greater than or equal to the * specified value. If null, no minCRLNumber check will be * done. * * @param minCRL the minimum CRL number accepted (or null) */ public void setMinCRLNumber(BigInteger minCRL) { this.minCRL = minCRL; } /** * Sets the maxCRLNumber criterion. The X509CRL must have a * CRL number extension whose value is less than or equal to the * specified value. If null, no maxCRLNumber check will be * done. * * @param maxCRL the maximum CRL number accepted (or null) */ 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 X509CRL and earlier than the value of the * nextUpdate component. There is no match if the X509CRL * does not contain a nextUpdate component. * If null, no dateAndTime check will be done. *

* Note that the Date supplied here is cloned to protect * against subsequent modifications. * * @param dateAndTime the Date to match against * (or null) * @see #getDateAndTime */ public void setDateAndTime(Date dateAndTime) { if (dateAndTime == null) this.dateAndTime = null; else this.dateAndTime = new Date(dateAndTime.getTime()); this.skew = 0; } /** * Sets the dateAndTime criterion and allows for the specified clock skew * (in milliseconds) when checking against the validity period of the CRL. */ void setDateAndTime(Date dateAndTime, long skew) { this.dateAndTime = (dateAndTime == null ? null : new Date(dateAndTime.getTime())); this.skew = skew; } /** * Sets the certificate being checked. This is not a criterion. Rather, * it is optional information that may help a CertStore * find CRLs that would be relevant when checking revocation for the * specified certificate. If null is specified, then no * such optional information is provided. * * @param cert the X509Certificate being checked * (or null) * @see #getCertificateChecking */ public void setCertificateChecking(X509Certificate cert) { certChecking = cert; } /** * Returns the issuerNames criterion. The issuer distinguished * name in the X509CRL must match at least one of the specified * distinguished names. If the value returned is null, any * issuer distinguished name will do. *

* If the value returned is not null, it is a * unmodifiable Collection of X500Principals. * * @return an unmodifiable Collection of names * (or null) * @see #setIssuers * @since 1.5 */ public Collection getIssuers() { if (issuerX500Principals == null) { return null; } return Collections.unmodifiableCollection(issuerX500Principals); } /** * Returns a copy of the issuerNames criterion. The issuer distinguished * name in the X509CRL must match at least one of the specified * distinguished names. If the value returned is null, any * issuer distinguished name will do. *

* If the value returned is not null, it is a * Collection of names. Each name is a String * or a byte array representing a distinguished name (in RFC 2253 or * ASN.1 DER encoded form, respectively). Note that the * Collection returned may contain duplicate names. *

* 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)}. *

* Note that a deep copy is performed on the Collection to * protect against subsequent modifications. * * @return a Collection of names (or null) * @see #setIssuerNames */ public Collection getIssuerNames() { if (issuerNames == null) { return null; } return cloneIssuerNames(issuerNames); } /** * Returns the minCRLNumber criterion. The X509CRL must have a * CRL number extension whose value is greater than or equal to the * specified value. If null, no minCRLNumber check will be done. * * @return the minimum CRL number accepted (or null) */ public BigInteger getMinCRL() { return minCRL; } /** * Returns the maxCRLNumber criterion. The X509CRL must have a * CRL number extension whose value is less than or equal to the * specified value. If null, no maxCRLNumber check will be * done. * * @return the maximum CRL number accepted (or null) */ 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 X509CRL and earlier than the value of the * nextUpdate component. There is no match if the * X509CRL does not contain a nextUpdate component. * If null, no dateAndTime check will be done. *

* Note that the Date returned is cloned to protect against * subsequent modifications. * * @return the Date to match against (or null) * @see #setDateAndTime */ public Date getDateAndTime() { if (dateAndTime == null) return null; return (Date) dateAndTime.clone(); } /** * Returns the certificate being checked. This is not a criterion. Rather, * it is optional information that may help a CertStore * find CRLs that would be relevant when checking revocation for the * specified certificate. If the value returned is null, then * no such optional information is provided. * * @return the certificate being checked (or null) * @see #setCertificateChecking */ public X509Certificate getCertificateChecking() { return certChecking; } /** * Returns a printable representation of the X509CRLSelector. * * @return a String describing the contents of the * X509CRLSelector. */ public String toString() { StringBuffer sb = new StringBuffer(); sb.append("X509CRLSelector: [\n"); if (issuerNames != null) { sb.append(" IssuerNames:\n"); Iterator i = issuerNames.iterator(); while (i.hasNext()) sb.append(" " + i.next() + "\n"); } if (minCRL != null) sb.append(" minCRLNumber: " + minCRL + "\n"); if (maxCRL != null) sb.append(" maxCRLNumber: " + maxCRL + "\n"); if (dateAndTime != null) sb.append(" dateAndTime: " + dateAndTime + "\n"); if (certChecking != null) sb.append(" Certificate being checked: " + certChecking + "\n"); sb.append("]"); return sb.toString(); } /** * Decides whether a CRL should be selected. * * @param crl the CRL to be checked * @return true if the CRL should be selected, * false otherwise */ public boolean match(CRL crl) { if (!(crl instanceof X509CRL)) { return false; } X509CRL xcrl = (X509CRL)crl; /* match on issuer name */ if (issuerNames != null) { X500Principal issuer = xcrl.getIssuerX500Principal(); Iterator i = issuerX500Principals.iterator(); boolean found = false; while (!found && i.hasNext()) { if (i.next().equals(issuer)) { found = true; } } if (!found) { if (debug != null) { debug.println("X509CRLSelector.match: issuer DNs " + "don't match"); } return false; } } if ((minCRL != null) || (maxCRL != null)) { /* Get CRL number extension from CRL */ byte[] crlNumExtVal = xcrl.getExtensionValue("2.5.29.20"); if (crlNumExtVal == null) { if (debug != null) { debug.println("X509CRLSelector.match: no CRLNumber"); } } BigInteger crlNum; try { DerInputStream in = new DerInputStream(crlNumExtVal); byte[] encoded = in.getOctetString(); CRLNumberExtension crlNumExt = new CRLNumberExtension(Boolean.FALSE, encoded); crlNum = (BigInteger)crlNumExt.get(CRLNumberExtension.NUMBER); } catch (IOException ex) { if (debug != null) { debug.println("X509CRLSelector.match: exception in " + "decoding CRL number"); } return false; } /* match on minCRLNumber */ if (minCRL != null) { if (crlNum.compareTo(minCRL) < 0) { if (debug != null) { debug.println("X509CRLSelector.match: CRLNumber too small"); } return false; } } /* match on maxCRLNumber */ if (maxCRL != null) { if (crlNum.compareTo(maxCRL) > 0) { if (debug != null) { debug.println("X509CRLSelector.match: CRLNumber too large"); } return false; } } } /* match on dateAndTime */ if (dateAndTime != null) { Date crlThisUpdate = xcrl.getThisUpdate(); Date nextUpdate = xcrl.getNextUpdate(); if (nextUpdate == null) { if (debug != null) { debug.println("X509CRLSelector.match: nextUpdate null"); } return false; } Date nowPlusSkew = dateAndTime; Date nowMinusSkew = dateAndTime; if (skew > 0) { nowPlusSkew = new Date(dateAndTime.getTime() + skew); nowMinusSkew = new Date(dateAndTime.getTime() - skew); } if (nowMinusSkew.after(nextUpdate) || nowPlusSkew.before(crlThisUpdate)) { if (debug != null) { debug.println("X509CRLSelector.match: update out of range"); } 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(issuerNames); copy.issuerX500Principals = new HashSet(issuerX500Principals); } return copy; } catch (CloneNotSupportedException e) { /* Cannot happen */ throw new InternalError(e.toString()); } } }