1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18
19package java.security.cert;
20
21import java.io.IOException;
22import java.math.BigInteger;
23import java.util.ArrayList;
24import java.util.Collection;
25import java.util.Collections;
26import java.util.Date;
27import javax.security.auth.x500.X500Principal;
28import org.apache.harmony.security.asn1.ASN1Integer;
29import org.apache.harmony.security.asn1.ASN1OctetString;
30import org.apache.harmony.security.x501.Name;
31
32/**
33 * A CRL selector ({@code CRLSelector} for selecting {@code
34 * X509CRL}s that match the specified criteria.
35 * <p>
36 * When constructed, all criteria are set to default values that will match any
37 * {@code X509CRL}.
38 */
39public class X509CRLSelector implements CRLSelector {
40
41    // issuerNames criterion:
42    // contains X.500 distinguished names in CANONICAL format
43    private ArrayList<String> issuerNames;
44    // contains X500Principal objects corresponding to the names
45    // from issuerNames collection (above)
46    private ArrayList<X500Principal> issuerPrincipals;
47    // minCRLNumber criterion
48    private BigInteger minCRL;
49    // maxCRLNumber criterion
50    private BigInteger maxCRL;
51    // dateAndTime criterion
52    private long dateAndTime = -1;
53    // the certificate being checked
54    private X509Certificate certificateChecking;
55
56    /**
57     * Creates a new {@code X509CertSelector}.
58     */
59    public X509CRLSelector() { }
60
61    /**
62     * Sets the criterion for the issuer distinguished names.
63     * <p>
64     * The CRL issuer must match at least one of the specified distinguished
65     * names.
66     *
67     * @param issuers
68     *            the list of issuer distinguished names to match, or {@code
69     *            null} if any issuer distinguished name will do.
70     */
71    public void setIssuers(Collection<X500Principal> issuers) {
72        if (issuers == null) {
73            issuerNames = null;
74            issuerPrincipals = null;
75            return;
76        }
77        issuerNames = new ArrayList<String>(issuers.size());
78        issuerPrincipals = new ArrayList<X500Principal>(issuers);
79        for (X500Principal issuer: issuers) {
80            issuerNames.add(issuer.getName(X500Principal.CANONICAL));
81        }
82    }
83
84    /**
85     * <b>Do not use:</b> use {@link #setIssuers(Collection)} or one of
86     * {@link #addIssuerName} instead. Sets the criterion for the issuer
87     * distinguished names.
88     * <p>
89     * The CRL issuer must match at least one of the specified distinguished
90     * names.
91     * <p>
92     * The specified parameter {@code names} is a collection with an entry for
93     * each name to be included in the criterion. The name is specified as a
94     * {@code String} or a byte array specifying the name (in RFC 2253 or ASN.1
95     * DER encoded form)
96     *
97     * @param names
98     *            the list of issuer distinguished names to match, or {@code
99     *            null} if any issuer distinguished name will do.
100     * @throws IOException
101     *             if parsing fails.
102     */
103    public void setIssuerNames(Collection<?> names) throws IOException {
104        if (names == null) {
105            issuerNames = null;
106            issuerPrincipals = null;
107            return;
108        }
109        if (names.size() == 0) {
110            return;
111        }
112        issuerNames = new ArrayList<String>(names.size());
113        for (Object name: names) {
114            if (name instanceof String) {
115                issuerNames.add(
116                        new Name((String) name).getName(
117                            X500Principal.CANONICAL));
118            } else if (name instanceof byte[]) {
119                issuerNames.add(
120                        new Name((byte[]) name).getName(
121                            X500Principal.CANONICAL));
122            } else {
123                throw new IOException("name neither a String nor a byte[]");
124            }
125        }
126    }
127
128    /**
129     * Adds an issuer to the criterion for the issuer distinguished names.
130     * <p>
131     * The CRL issuer must match at least one of the specified distinguished
132     * names.
133     *
134     * @param issuer
135     *            the issuer to add to the criterion
136     */
137    public void addIssuer(X500Principal issuer) {
138        if (issuer == null) {
139            throw new NullPointerException("issuer == null");
140        }
141        if (issuerNames == null) {
142            issuerNames = new ArrayList<String>();
143        }
144        String name = issuer.getName(X500Principal.CANONICAL);
145        if (!issuerNames.contains(name)) {
146            issuerNames.add(name);
147        }
148        if (issuerPrincipals == null) {
149            issuerPrincipals = new ArrayList<X500Principal>(issuerNames.size());
150        }
151        // extend the list of issuer Principals
152        int size = issuerNames.size() - 1;
153        for (int i=issuerPrincipals.size(); i<size; i++) {
154            issuerPrincipals.add(new X500Principal(issuerNames.get(i)));
155        }
156        issuerPrincipals.add(issuer);
157    }
158
159    /**
160     * <b>Do not use:</b>, use {@link #addIssuer(X500Principal)} or
161     * {@link #addIssuerName(byte[])} instead. It can fail to match some CRLs
162     * because of a loss of encoding information in a RFC 2253 string.
163     * <p>
164     * Adds an issuer to the criterion for the issuer distinguished names. The
165     * CRK issuer must match at least one of the specified distinguished names.
166     *
167     * @param iss_name
168     *            the RFC 2253 encoded name.
169     * @throws IOException
170     *             if parsing fails.
171     */
172    public void addIssuerName(String iss_name) throws IOException {
173        if (issuerNames == null) {
174            issuerNames = new ArrayList<String>();
175        }
176
177        if (iss_name == null) {
178            iss_name = "";
179        }
180
181        String name = new Name(iss_name).getName(X500Principal.CANONICAL);
182        if (!issuerNames.contains(name)) {
183            issuerNames.add(name);
184        }
185    }
186
187    /**
188     * Adds an issuer to the criterion for the issuer distinguished names.
189     * <p>
190     * The CRL issuer must match at least one of the specified distinguished
191     * names.
192     *
193     * @param iss_name
194     *            the issuer to add to the criterion in ASN.1 DER encoded form.
195     * @throws IOException
196     *             if parsing fails.
197     */
198    public void addIssuerName(byte[] iss_name) throws IOException {
199        if (iss_name == null) {
200            throw new NullPointerException("iss_name == null");
201        }
202        if (issuerNames == null) {
203            issuerNames = new ArrayList<String>();
204        }
205        String name = new Name(iss_name).getName(X500Principal.CANONICAL);
206        if (!issuerNames.contains(name)) {
207            issuerNames.add(name);
208        }
209    }
210
211    /**
212     * Sets the criterion for the minimum CRL number.
213     * <p>
214     * The CRL must have a number extension with a value greater than or equal
215     * to the specified parameter.
216     *
217     * @param minCRL
218     *            the minimum CRL number or null to not check the minimum CRL
219     *            number
220     */
221    public void setMinCRLNumber(BigInteger minCRL) {
222        this.minCRL = minCRL;
223    }
224
225    /**
226     * Sets the criterion for the maximum CRL number.
227     * <p>
228     * The CRL must have a number extension with a value less than or equal to
229     * the specified parameter.
230     *
231     * @param maxCRL
232     *            the maximum CRL number or null to not check the maximum CRL
233     *            number.
234     */
235    public void setMaxCRLNumber(BigInteger maxCRL) {
236        this.maxCRL = maxCRL;
237    }
238
239    /**
240     * Sets the criterion for the CRL update period.
241     * <p>
242     * The CRL's {@code thisUpdate} value must be equal or before the specified
243     * date and the {@code nextUpdate} value must be after the specified date.
244     *
245     * @param dateAndTime
246     *            the date to search for valid CRL's or {@code null} to not
247     *            check the date.
248     */
249    public void setDateAndTime(Date dateAndTime) {
250        if (dateAndTime == null) {
251            this.dateAndTime = -1;
252            return;
253        }
254        this.dateAndTime = dateAndTime.getTime();
255    }
256
257    /**
258     * Sets a certificate hint to find CRLs. It's not a criterion but may help
259     * finding relevant CRLs.
260     *
261     * @param cert
262     *            the certificate hint or {@code null}.
263     */
264    public void setCertificateChecking(X509Certificate cert) {
265        this.certificateChecking = cert;
266    }
267
268    /**
269     * Returns the criterion for the issuer distinguished names.
270     * <p>
271     * The CRL issuer must match at least one of the distinguished names.
272     *
273     * @return the unmodifiable list of issuer distinguished names to match, or
274     *         {@code null} if any issuer distinguished name will do.
275     */
276    public Collection<X500Principal> getIssuers() {
277        if (issuerNames == null) {
278            return null;
279        }
280        if (issuerPrincipals == null) {
281            issuerPrincipals = new ArrayList<X500Principal>(issuerNames.size());
282        }
283        int size = issuerNames.size();
284        // extend the list of issuer Principals
285        for (int i=issuerPrincipals.size(); i<size; i++) {
286            issuerPrincipals.add(new X500Principal(issuerNames.get(i)));
287        }
288        return Collections.unmodifiableCollection(issuerPrincipals);
289    }
290
291    /**
292     * Returns the criterion for the issuer distinguished names.
293     * <p>
294     * The CRL issuer must match at least one of the distinguished names.
295     *
296     * @return a copy of the list of issuer distinguished names to
297     *         match, or {@code null} if any issuer distinguished name
298     *         will do. The elements may be strings or ASN.1 DER
299     *         encoded byte arrays.
300     */
301    public Collection<Object> getIssuerNames() {
302        if (issuerNames == null) {
303            return null;
304        }
305        return (Collection<Object>) issuerNames.clone();
306    }
307
308    /**
309     * Returns the criterion for the minimum CRL number.
310     * <p>
311     * The CRL must have a number extension with a value greater than or equal
312     * to the returned value.
313     *
314     * @return the minimum CRL number or {@code null} if the minimum CRL number
315     *         is not to be checked.
316     */
317    public BigInteger getMinCRL() {
318        return minCRL;
319    }
320
321    /**
322     * Returns the criterion for the maximum CRL number.
323     * <p>
324     * The CRL must have a number extension with a value less than or equal to
325     * the returned value.
326     *
327     * @return the maximum CRL number or null if the maximum CRL number is not
328     *         checked.
329     */
330    public BigInteger getMaxCRL() {
331        return maxCRL;
332    }
333
334    /**
335     * Returns the criterion for the CRL update period.
336     * <p>
337     * The CRL's {@code thisUpdate} value must be equal or before the returned
338     * date and the {@code nextUpdate} value must be after the returned date.
339     *
340     * @return the date to search for valid CRL's or {@code null} if the date is
341     *         not checked.
342     */
343    public Date getDateAndTime() {
344        if (dateAndTime == -1) {
345            return null;
346        }
347        return new Date(dateAndTime);
348    }
349
350    /**
351     * Returns the certificate hint to find CRLs. It's not a criterion but may
352     * help finding relevant CRLs.
353     *
354     * @return the certificate hint or {@code null} if none set.
355     */
356    public X509Certificate getCertificateChecking() {
357        return certificateChecking;
358    }
359
360    /**
361     * Returns a string representation of this {@code X509CRLSelector} instance.
362     *
363     * @return a string representation of this {@code X509CRLSelector} instance.
364     */
365    public String toString() {
366        StringBuilder result = new StringBuilder();
367        result.append("X509CRLSelector:\n[");
368        if (issuerNames != null) {
369            result.append("\n  IssuerNames:\n  [");
370            int size = issuerNames.size();
371            for (int i=0; i<size; i++) {
372                result.append("\n    "
373                    + issuerNames.get(i));
374            }
375            result.append("\n  ]");
376        }
377        if (minCRL != null) {
378            result.append("\n  minCRL: " + minCRL);
379        }
380        if (maxCRL != null) {
381            result.append("\n  maxCRL: " + maxCRL);
382        }
383        if (dateAndTime != -1) {
384            result.append("\n  dateAndTime: " + (new Date(dateAndTime)));
385        }
386        if (certificateChecking != null) {
387            result.append("\n  certificateChecking: " + certificateChecking);
388        }
389        result.append("\n]");
390        return result.toString();
391    }
392
393    /**
394     * Returns whether the specified CRL matches all the criteria collected in
395     * this instance.
396     *
397     * @param crl
398     *            the CRL to check.
399     * @return {@code true} if the CRL matches all the criteria, otherwise
400     *         {@code false}.
401     */
402    public boolean match(CRL crl) {
403        if (!(crl instanceof X509CRL)) {
404            return false;
405        }
406        X509CRL crlist = (X509CRL) crl;
407        if ((issuerNames != null) &&
408                // the search speed depends on the class of issuerNames
409                !(issuerNames.contains(
410                        crlist.getIssuerX500Principal().getName(
411                            X500Principal.CANONICAL)))) {
412            return false;
413        }
414        if ((minCRL != null) || (maxCRL != null)) {
415            try {
416                // As specified in rfc 3280 (http://www.ietf.org/rfc/rfc3280.txt)
417                // CRL Number Extension's OID is 2.5.29.20 .
418                byte[] bytes = crlist.getExtensionValue("2.5.29.20");
419                bytes = (byte[]) ASN1OctetString.getInstance().decode(bytes);
420                BigInteger crlNumber = new BigInteger((byte[])
421                        ASN1Integer.getInstance().decode(bytes));
422                if ((minCRL != null) && (crlNumber.compareTo(minCRL) < 0)) {
423                    return false;
424                }
425                if ((maxCRL != null) && (crlNumber.compareTo(maxCRL) > 0)) {
426                    return false;
427                }
428            } catch (IOException e) {
429                return false;
430            }
431        }
432        if (dateAndTime != -1) {
433            Date thisUp = crlist.getThisUpdate();
434            Date nextUp = crlist.getNextUpdate();
435            if ((thisUp == null) || (nextUp == null)) {
436                return false;
437            }
438            if ((dateAndTime < thisUp.getTime())
439                                || (dateAndTime > nextUp.getTime())) {
440                return false;
441            }
442        }
443        return true;
444    }
445
446    /**
447     * Clones this {@code X509CRL} instance.
448     *
449     * @return the cloned instance.
450     */
451    public Object clone() {
452        X509CRLSelector result;
453
454        try {
455            result = (X509CRLSelector) super.clone();
456            if (issuerNames != null) {
457                result.issuerNames = new ArrayList<String>(issuerNames);
458            }
459        } catch (CloneNotSupportedException e) {
460            result = null;
461        }
462        return result;
463    }
464}
465