X509CertSelector.java revision 7365de1056414750d0a7d1fdd26025fd247f0d04
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
18package java.security.cert;
19
20import java.io.IOException;
21import java.math.BigInteger;
22import java.security.PublicKey;
23import java.util.ArrayList;
24import java.util.Arrays;
25import java.util.Collection;
26import java.util.Collections;
27import java.util.Date;
28import java.util.HashSet;
29import java.util.Iterator;
30import java.util.List;
31import java.util.Set;
32import javax.security.auth.x500.X500Principal;
33import org.apache.harmony.security.asn1.ASN1OctetString;
34import org.apache.harmony.security.x509.AlgorithmIdentifier;
35import org.apache.harmony.security.x509.CertificatePolicies;
36import org.apache.harmony.security.x509.GeneralName;
37import org.apache.harmony.security.x509.GeneralNames;
38import org.apache.harmony.security.x509.NameConstraints;
39import org.apache.harmony.security.x509.PolicyInformation;
40import org.apache.harmony.security.x509.PrivateKeyUsagePeriod;
41import org.apache.harmony.security.x509.SubjectPublicKeyInfo;
42
43
44
45/**
46 * A certificate selector ({@code CertSelector} for selecting {@code
47 * X509Certificate}s that match the specified criteria.
48 */
49public class X509CertSelector implements CertSelector {
50
51    // match criteria
52    private X509Certificate certificateEquals;
53    private BigInteger serialNumber;
54    private X500Principal issuer;
55    private X500Principal subject;
56    private byte[] subjectKeyIdentifier;
57    private byte[] authorityKeyIdentifier;
58    private Date certificateValid;
59    private String subjectPublicKeyAlgID;
60    private Date privateKeyValid;
61    private byte[] subjectPublicKey;
62    private boolean[] keyUsage;
63    private Set extendedKeyUsage;
64    private boolean matchAllNames = true;
65    private int pathLen = -1;
66    private List[] subjectAltNames;
67    private NameConstraints nameConstraints;
68    private Set policies;
69    private ArrayList pathToNames;
70
71    // needed to avoid needless encoding/decoding work
72    private PublicKey subjectPublicKeyImpl;
73    private String issuerName;
74    private byte[] issuerBytes;
75
76    /**
77     * Creates a new {@code X509CertSelector}.
78     */
79    public X509CertSelector() {}
80
81    /**
82     * Sets the certificate that a matching certificate must be equal to.
83     *
84     * @param certificate
85     *            the certificate to match, or null to not check this criteria.
86     */
87    public void setCertificate(X509Certificate certificate) {
88        certificateEquals = certificate;
89    }
90
91    /**
92     * Returns the certificate that a matching certificate must be equal to.
93     *
94     * @return the certificate to match, or null if this criteria is not
95     *         checked.
96     */
97    public X509Certificate getCertificate() {
98        return certificateEquals;
99    }
100
101    /**
102     * Sets the serial number that a certificate must match.
103     *
104     * @param serialNumber
105     *            the serial number to match, or {@code null} to not check the
106     *            serial number.
107     */
108    public void setSerialNumber(BigInteger serialNumber) {
109        this.serialNumber = serialNumber;
110    }
111
112    /**
113     * Returns the serial number that a certificate must match.
114     *
115     * @return the serial number to match, or {@code null} if the serial number
116     *         is not to be checked.
117     */
118    public BigInteger getSerialNumber() {
119        return serialNumber;
120    }
121
122    /**
123     * Sets the issuer that a certificate must match.
124     *
125     * @param issuer
126     *            the issuer to match, or {@code null} if the issuer is not to
127     *            be checked.
128     */
129    public void setIssuer(X500Principal issuer) {
130        this.issuer = issuer;
131        this.issuerName = null;
132        this.issuerBytes = null;
133    }
134
135    /**
136     * Returns the issuer that a certificate must match.
137     *
138     * @return the issuer that a certificate must match, or {@code null} if the
139     *         issuer is not to be checked.
140     */
141    public X500Principal getIssuer() {
142        return issuer;
143    }
144
145    /**
146     * <b>Do not use</b>, use {@link #getIssuer()} or
147     * {@link #getIssuerAsBytes()} instead. Sets the issuer that a certificate
148     * must match.
149     *
150     * @param issuerName
151     *            the issuer in a RFC 2253 format string, or {@code null} to not
152     *            check the issuer.
153     * @throws IOException
154     *             if parsing the issuer fails.
155     */
156    public void setIssuer(String issuerName) throws IOException {
157        if (issuerName == null) {
158            this.issuer = null;
159            this.issuerName = null;
160            this.issuerBytes = null;
161            return;
162        }
163        try {
164            this.issuer = new X500Principal(issuerName);
165            this.issuerName = issuerName;
166            this.issuerBytes = null;
167        } catch (IllegalArgumentException e) {
168            throw new IOException(e.getMessage());
169        }
170    }
171
172    /**
173     * <b>Do not use</b>, use {@link #getIssuer()} or
174     * {@link #getIssuerAsBytes()} instead. Returns the issuer that a
175     * certificate must match in a RFC 2253 format string.
176     *
177     * @return the issuer in a RFC 2253 format string, or {@code null} if the
178     *         issuer is not to be checked.
179     */
180    public String getIssuerAsString() {
181        if (issuer == null) {
182            return null;
183        }
184        if (issuerName == null) {
185            issuerName = issuer.getName();
186        }
187        return issuerName;
188    }
189
190    /**
191     * Sets the issuer that a certificate must match.
192     *
193     * @param issuerDN
194     *            the distinguished issuer name in ASN.1 DER encoded format, or
195     *            {@code null} to not check the issuer.
196     * @throws IOException
197     *             if decoding the issuer fail.
198     */
199    public void setIssuer(byte[] issuerDN) throws IOException {
200        if (issuerDN == null) {
201            issuer = null;
202            return;
203        }
204        try {
205            issuer = new X500Principal(issuerDN);
206            this.issuerName = null;
207            this.issuerBytes = new byte[issuerDN.length];
208            System.arraycopy(issuerDN, 0, this.issuerBytes, 0, issuerDN.length);
209        } catch (IllegalArgumentException e) {
210            throw new IOException(e.getMessage());
211        }
212    }
213
214    /**
215     * Returns the issuer that a certificate must match.
216     *
217     * @return the distinguished issuer name in ASN.1 DER encoded format, or
218     *         {@code null} if the issuer is not to be checked.
219     * @throws IOException
220     *             if encoding the issuer fails.
221     */
222    public byte[] getIssuerAsBytes() throws IOException {
223        if (issuer == null) {
224            return null;
225        }
226        if (issuerBytes == null) {
227            issuerBytes = issuer.getEncoded();
228        }
229        byte[] result = new byte[issuerBytes.length];
230        System.arraycopy(issuerBytes, 0, result, 0, issuerBytes.length);
231        return result;
232    }
233
234    /**
235     * Set the subject that a certificate must match.
236     *
237     * @param subject
238     *            the subject distinguished name or {@code null} to not check
239     *            the subject.
240     */
241    public void setSubject(X500Principal subject) {
242        this.subject = subject;
243    }
244
245    /**
246     * Returns the subject that a certificate must match.
247     *
248     * @return the subject distinguished name, or null if the subject is not to
249     *         be checked.
250     */
251    public X500Principal getSubject() {
252        return subject;
253    }
254
255    /**
256     * <b>Do not use</b>, use {@link #setSubject(byte[])} or
257     * {@link #setSubject(X500Principal)} instead. Returns the subject that a
258     * certificate must match.
259     *
260     * @param subjectDN
261     *            the subject distinguished name in RFC 2253 format or {@code
262     *            null} to not check the subject.
263     * @throws IOException
264     *             if decoding the subject fails.
265     */
266    public void setSubject(String subjectDN) throws IOException {
267        if (subjectDN == null) {
268            subject = null;
269            return;
270        }
271        try {
272            subject = new X500Principal(subjectDN);
273        } catch (IllegalArgumentException e) {
274            throw new IOException(e.getMessage());
275        }
276    }
277
278    /**
279     * <b>Do not use</b>, use {@link #getSubject()} or
280     * {@link #getSubjectAsBytes()} instead. Returns the subject that a
281     * certificate must match.
282     *
283     * @return the subject distinguished name in RFC 2253 format, or {@code
284     *         null} if the subject is not to be checked.
285     */
286    public String getSubjectAsString() {
287        if (subject == null) {
288            return null;
289        }
290        return subject.getName();
291    }
292
293    /**
294     * Sets the subject that a certificate must match.
295     *
296     * @param subjectDN
297     *            the subject distinguished name in ASN.1 DER format, or {@code
298     *            null} to not check the subject.
299     * @throws IOException
300     *             if decoding the subject fails.
301     */
302    public void setSubject(byte[] subjectDN) throws IOException {
303        if (subjectDN == null) {
304            subject = null;
305            return;
306        }
307        try {
308            subject = new X500Principal(subjectDN);
309        } catch (IllegalArgumentException e) {
310            throw new IOException(e.getMessage());
311        }
312    }
313
314    /**
315     * Returns the subject that a certificate must match.
316     *
317     * @return the subject distinguished name in ASN.1 DER format, or {@code
318     *         null} if the subject is not to be checked.
319     * @throws IOException
320     *             if encoding the subject fails.
321     */
322    public byte[] getSubjectAsBytes() throws IOException {
323        if (subject == null) {
324            return null;
325        }
326        return subject.getEncoded();
327    }
328
329    /**
330     * Sets the criterion for the {@literal SubjectKeyIdentifier} extension.
331     * <p>
332     * The {@code subjectKeyIdentifier} should be a single DER encoded value.
333     *
334     * @param subjectKeyIdentifier
335     *            the subject key identifier or {@code null} to disable this
336     *            check.
337     */
338    public void setSubjectKeyIdentifier(byte[] subjectKeyIdentifier) {
339        if (subjectKeyIdentifier == null) {
340            this.subjectKeyIdentifier = null;
341            return;
342        }
343        this.subjectKeyIdentifier = new byte[subjectKeyIdentifier.length];
344        System.arraycopy(subjectKeyIdentifier, 0, this.subjectKeyIdentifier, 0,
345                         subjectKeyIdentifier.length);
346    }
347
348    /**
349     * Returns the criterion for the {@literal SubjectKeyIdentifier} extension.
350     *
351     * @return the subject key identifier or {@code null} if it is not to be
352     *         checked.
353     */
354    public byte[] getSubjectKeyIdentifier() {
355        if (subjectKeyIdentifier == null) {
356            return null;
357        }
358        byte[] res = new byte[subjectKeyIdentifier.length];
359        System.arraycopy(subjectKeyIdentifier, 0, res, 0, res.length);
360        return res;
361    }
362
363    /**
364     * Sets the criterion for the {@literal AuthorityKeyIdentifier} extension.
365     *
366     * @param authorityKeyIdentifier
367     *            the authority key identifier, or {@code null} to disable this
368     *            check.
369     */
370    public void setAuthorityKeyIdentifier(byte[] authorityKeyIdentifier) {
371        if (authorityKeyIdentifier == null) {
372            this.authorityKeyIdentifier = null;
373            return;
374        }
375        this.authorityKeyIdentifier = new byte[authorityKeyIdentifier.length];
376        System.arraycopy(authorityKeyIdentifier, 0,
377                         this.authorityKeyIdentifier, 0,
378                         authorityKeyIdentifier.length);
379    }
380
381    /**
382     * Returns the criterion for the {@literal AuthorityKeyIdentifier}
383     * extension.
384     *
385     * @return the authority key identifier, or {@code null} if it is not to be
386     *         checked.
387     */
388    public byte[] getAuthorityKeyIdentifier() {
389        if (authorityKeyIdentifier == null) {
390            return null;
391        }
392        byte[] res = new byte[authorityKeyIdentifier.length];
393        System.arraycopy(authorityKeyIdentifier, 0, res, 0, res.length);
394        return res;
395    }
396
397    /**
398     * Sets the criterion for the validity date of the certificate.
399     * <p>
400     * The certificate must be valid at the specified date.
401     * @param certificateValid
402     *            the validity date or {@code null} to not check the date.
403     */
404    public void setCertificateValid(Date certificateValid) {
405        this.certificateValid = (certificateValid == null)
406                                ? null
407                                : (Date) certificateValid.clone();
408    }
409
410    /**
411     * Returns the criterion for the validity date of the certificate.
412     *
413     * @return the validity date or {@code null} if the date is not to be
414     *         checked.
415     */
416    public Date getCertificateValid() {
417        return (certificateValid == null)
418                                ? null
419                                : (Date) certificateValid.clone();
420    }
421
422    /**
423     * Sets the criterion for the validity date of the private key.
424     * <p>
425     * The private key must be valid at the specified date.
426     *
427     * @param privateKeyValid
428     *            the validity date or {@code null} to not check the date.
429     */
430    public void setPrivateKeyValid(Date privateKeyValid) {
431        if (privateKeyValid == null) {
432            this.privateKeyValid = null;
433            return;
434        }
435        this.privateKeyValid = (Date) privateKeyValid.clone();
436    }
437
438    /**
439     * Returns the criterion for the validity date of the private key.
440     * <p>
441     * The private key must be valid at the specified date.
442     *
443     * @return the validity date or {@code null} if the date is not to be
444     *         checked.
445     */
446    public Date getPrivateKeyValid() {
447        if (privateKeyValid != null) {
448            return (Date) privateKeyValid.clone();
449        }
450        return null;
451    }
452
453    private void checkOID(String oid) throws IOException {
454        int beg = 0;
455        int end = oid.indexOf('.', beg);
456        try {
457            int comp = Integer.parseInt(oid.substring(beg, end));
458            beg = end + 1;
459            if ((comp < 0) || (comp > 2)) {
460                throw new IOException("Bad OID: " + oid);
461            }
462            end = oid.indexOf('.', beg);
463            comp = Integer.parseInt(oid.substring(beg, end));
464            if ((comp < 0) || (comp > 39)) {
465                throw new IOException("Bad OID: " + oid);
466            }
467        } catch (IndexOutOfBoundsException e) {
468            throw new IOException("Bad OID: " + oid);
469        } catch (NumberFormatException e) {
470            throw new IOException("Bad OID: " + oid);
471        }
472    }
473
474    /**
475     * Sets the criterion for the subject public key signature algorithm.
476     * <p>
477     * The certificate must contain a subject public key with the algorithm
478     * specified.
479     *
480     * @param oid
481     *            the OID (object identifier) of the signature algorithm or
482     *            {@code null} to not check the OID.
483     * @throws IOException
484     *             if the specified object identifier is invalid.
485     */
486    public void setSubjectPublicKeyAlgID(String oid) throws IOException {
487        if (oid == null) {
488            subjectPublicKeyAlgID = null;
489            return;
490        }
491        checkOID(oid);
492        subjectPublicKeyAlgID = oid;
493    }
494
495    /**
496     * Returns the criterion for the subject public key signature algorithm.
497     *
498     * @return the OID (object identifier) or the signature algorithm or {@code
499     *         null} if it's not to be checked.
500     */
501    public String getSubjectPublicKeyAlgID() {
502        return subjectPublicKeyAlgID;
503    }
504
505    /**
506     * Sets the criterion for the subject public key.
507     *
508     * @param key
509     *            the subject public key or {@code null} to not check the key.
510     */
511    public void setSubjectPublicKey(PublicKey key) {
512        subjectPublicKey = (key == null) ? null : key.getEncoded();
513        subjectPublicKeyImpl = key;
514    }
515
516    /**
517     * Sets the criterion for the subject public key.
518     *
519     * @param key
520     *            the subject public key in ASN.1 DER encoded format or {@code null} to
521     *            not check the key.
522     * @throws IOException
523     *             if decoding the the public key fails.
524     */
525    public void setSubjectPublicKey(byte[] key) throws IOException {
526        if (key == null) {
527            subjectPublicKey = null;
528            subjectPublicKeyImpl = null;
529            return;
530        }
531        subjectPublicKey = new byte[key.length];
532        System.arraycopy(key, 0, subjectPublicKey, 0, key.length);
533        subjectPublicKeyImpl =
534            ((SubjectPublicKeyInfo) SubjectPublicKeyInfo.ASN1.decode(key))
535            .getPublicKey();
536    }
537
538    /**
539     * Returns the criterion for the subject public key.
540     *
541     * @return the subject public key or {@code null} if the key is not to be
542     *         checked.
543     */
544    public PublicKey getSubjectPublicKey() {
545        return subjectPublicKeyImpl;
546    }
547
548    /**
549     * Sets the criterion for the {@literal KeyUsage} extension.
550     *
551     * @param keyUsage
552     *            the boolean array in the format as returned by
553     *            {@link X509Certificate#getKeyUsage()}, or {@code null} to not
554     *            check the key usage.
555     */
556    public void setKeyUsage(boolean[] keyUsage) {
557        if (keyUsage == null) {
558            this.keyUsage = null;
559            return;
560        }
561        this.keyUsage = new boolean[keyUsage.length];
562        System.arraycopy(keyUsage, 0, this.keyUsage, 0, keyUsage.length);
563    }
564
565    /**
566     * Returns the criterion for the {@literal KeyUsage} extension.
567     *
568     * @return the boolean array in the format as returned by
569     *         {@link X509Certificate#getKeyUsage()}, or {@code null} if the key
570     *         usage is not to be checked.
571     */
572    public boolean[] getKeyUsage() {
573        if (keyUsage == null) {
574            return null;
575        }
576        boolean[] result = new boolean[keyUsage.length];
577        System.arraycopy(keyUsage, 0, result, 0, keyUsage.length);
578        return result;
579    }
580
581    /**
582     * Sets the criterion for the {@literal ExtendedKeyUsage} extension.
583     *
584     * @param keyUsage
585     *            the set of key usage OIDs, or {@code null} to not check it.
586     * @throws IOException
587     *             if one of the OIDs is invalid.
588     */
589    public void setExtendedKeyUsage(Set<String> keyUsage)
590                             throws IOException {
591        extendedKeyUsage = null;
592        if ((keyUsage == null) || (keyUsage.size() == 0)) {
593            return;
594        }
595        HashSet key_u = new HashSet();
596        Iterator it = keyUsage.iterator();
597        while (it.hasNext()) {
598            String usage = (String) it.next();
599            checkOID(usage);
600            key_u.add(usage);
601        }
602        extendedKeyUsage = Collections.unmodifiableSet(key_u);
603    }
604
605    /**
606     * Returns the criterion for the {@literal ExtendedKeyUsage} extension.
607     *
608     * @return the set of key usage OIDs, or {@code null} if it's not to be
609     *         checked.
610     */
611    public Set<String> getExtendedKeyUsage() {
612        return extendedKeyUsage;
613    }
614
615    /**
616     * Sets the flag for the matching behavior for subject alternative names.
617     * <p>
618     * The flag indicates whether a certificate must contain all or at least one
619     * of the subject alternative names specified by {@link
620     * #setSubjectAlternativeNames} or {@link #addSubjectAlternativeName}.
621     *
622     * @param matchAllNames
623     *            {@code true} if a certificate must contain all of the
624     *            specified subject alternative names, otherwise {@code false}.
625     */
626    public void setMatchAllSubjectAltNames(boolean matchAllNames) {
627        this.matchAllNames = matchAllNames;
628    }
629
630    /**
631     * Returns the flag for the matching behavior for subject alternative names.
632     * <p>
633     * The flag indicates whether a certificate must contain all or at least one
634     * of the subject alternative names specified by {@link
635     * #setSubjectAlternativeNames} or {@link #addSubjectAlternativeName}.
636     *
637     * @return {@code true} if a certificate must contain all of the specified
638     *         subject alternative names, otherwise {@code false}.
639     */
640    public boolean getMatchAllSubjectAltNames() {
641        return matchAllNames;
642    }
643
644    /**
645     * Sets the criterion for subject alternative names.
646     * <p>
647     * the certificate must contain all or at least one of the specified subject
648     * alternative names. The behavior is specified by
649     * {@link #getMatchAllSubjectAltNames}.
650     * <p>
651     * The specified parameter {@code names} is a collection with an entry for
652     * each name to be included in the criterion. The name is specified as a
653     * {@code List}, the first entry must be an {@code Integer} specifying the
654     * name type (0-8), the second entry must be a {@code String} or a byte
655     * array specifying the name (in string or ASN.1 DER encoded form)
656     *
657     * @param names
658     *            the names collection or {@code null} to not perform this check.
659     * @throws IOException
660     *             if the decoding of a name fails.
661     */
662    public void setSubjectAlternativeNames(Collection<List<?>> names)
663                                    throws IOException {
664        subjectAltNames = null;
665        if ((names == null) || (names.size() == 0)) {
666            return;
667        }
668        Iterator it = names.iterator();
669        while (it.hasNext()) {
670            List name = (List) it.next();
671            int tag = ((Integer) name.get(0)).intValue();
672            Object value = name.get(1);
673            if (value instanceof String) {
674                addSubjectAlternativeName(tag, (String) value);
675            } else if (value instanceof byte[]) {
676                addSubjectAlternativeName(tag, (byte[]) value);
677            } else {
678                throw new IOException("name neither a String nor a byte[]");
679            }
680        }
681    }
682
683    /**
684     * Adds a subject alternative name to the respective criterion.
685     *
686     * @param tag
687     *            the type of the name
688     * @param name
689     *            the name in string format.
690     * @throws IOException
691     *             if parsing the name fails.
692     */
693    public void addSubjectAlternativeName(int tag, String name)
694                                                       throws IOException {
695        GeneralName alt_name = new GeneralName(tag, name);
696        // create only if there was not any errors
697        if (subjectAltNames == null) {
698            subjectAltNames = new ArrayList[9];
699        }
700        if (subjectAltNames[tag] == null) {
701            subjectAltNames[tag] = new ArrayList();
702        }
703        subjectAltNames[tag].add(alt_name);
704    }
705
706    /**
707     * Adds a subject alternative name to the respective criterion.
708     *
709     * @param tag
710     *            the type of the name.
711     * @param name
712     *            the name in ASN.1 DER encoded form.
713     * @throws IOException
714     *             if the decoding of the name fails.
715     */
716    public void addSubjectAlternativeName(int tag, byte[] name)
717                                            throws IOException {
718        GeneralName alt_name = new GeneralName(tag, name);
719        // create only if there was not any errors
720        if (subjectAltNames == null) {
721            subjectAltNames = new ArrayList[9];
722        }
723        if (subjectAltNames[tag] == null) {
724            subjectAltNames[tag] = new ArrayList();
725        }
726        subjectAltNames[tag].add(alt_name);
727    }
728
729    /**
730     * Returns the criterion for subject alternative names.
731     * <p>
732     * the certificate must contain all or at least one of the specified subject
733     * alternative names. The behavior is specified by
734     * {@link #getMatchAllSubjectAltNames}.
735     * <p>
736     * The subject alternative names is a collection with an entry for each name
737     * included in the criterion. The name is specified as a {@code List}, the
738     * first entry is an {@code Integer} specifying the name type (0-8), the
739     * second entry is byte array specifying the name in ASN.1 DER encoded form)
740     *
741     * @return the names collection or {@code null} if none specified.
742     */
743    public Collection<List<?>> getSubjectAlternativeNames() {
744        if (subjectAltNames == null) {
745            return null;
746        }
747        ArrayList result = new ArrayList();
748        for (int tag=0; tag<9; tag++) {
749            if (subjectAltNames[tag] != null) {
750                for (int name=0; name<subjectAltNames[tag].size(); name++) {
751                    Object neim = subjectAltNames[tag].get(name);
752                    if (neim instanceof byte[]) {
753                        byte[] arr_neim = (byte[]) neim;
754                        neim = new byte[arr_neim.length];
755                        System.arraycopy(arr_neim, 0, neim, 0, arr_neim.length);
756                    }
757                    List list = new ArrayList(2);
758                    list.add(Integer.valueOf(tag)); // android-changed
759                    list.add(neim);
760                    result.add(list);
761                }
762            }
763        }
764        return result;
765    }
766
767    /**
768     * Sets the criterion for the name constraints.
769     * <p>
770     * The certificate must constraint subject and subject alternative names
771     * that match the specified name constraints.
772     * <p>
773     * The name constraints in ASN.1:
774     *
775     * <pre>
776     * NameConstraints ::= SEQUENCE {
777     *        permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
778     *        excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
779     *
780     * GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
781     *
782     * GeneralSubtree ::= SEQUENCE {
783     *        base                    GeneralName,
784     *        minimum         [0]     BaseDistance DEFAULT 0,
785     *        maximum         [1]     BaseDistance OPTIONAL }
786     *
787     * BaseDistance ::= INTEGER (0..MAX)
788     *
789     * GeneralName ::= CHOICE {
790     *        otherName                       [0]     OtherName,
791     *        rfc822Name                      [1]     IA5String,
792     *        dNSName                         [2]     IA5String,
793     *        x400Address                     [3]     ORAddress,
794     *        directoryName                   [4]     Name,
795     *        ediPartyName                    [5]     EDIPartyName,
796     *        uniformResourceIdentifier       [6]     IA5String,
797     *        iPAddress                       [7]     OCTET STRING,
798     *        registeredID                    [8]     OBJECT IDENTIFIER}
799     *
800     * </pre>
801     *
802     * @param bytes
803     *            the name constraints in ASN.1 DER encoded format, or null to
804     *            not check any constraints.
805     * @throws IOException
806     *             if decoding the name constraints fail.
807     */
808    public void setNameConstraints(byte[] bytes) throws IOException {
809        this.nameConstraints = (bytes == null)
810            ? null
811            : (NameConstraints) NameConstraints.ASN1.decode(bytes);
812    }
813
814    /**
815     * Returns the criterion for the name constraints.
816     *
817     * @return the name constraints or {@code null} if none specified.
818     * @see #setNameConstraints
819     */
820    public byte[] getNameConstraints() {
821        return (nameConstraints == null)
822            ? null
823            : nameConstraints.getEncoded();
824    }
825
826    /**
827     * Sets the criterion for the basic constraints extension.
828     * <p>
829     * A value greater than or equal to zero indicates that a certificate must
830     * include a basic constraints extension with a path length of a least that
831     * value. A value of {@code -2} indicates that only end-entity certificates
832     * are accepted. A value of {@code -1} indicates that no check is done.
833     *
834     * @param pathLen
835     *            the value specifying the criterion.
836     * @throws IllegalArgumentException
837     *             if {@code pathLen} is less than {@code -2}.
838     */
839    public void setBasicConstraints(int pathLen) {
840        if (pathLen < -2) {
841            throw new IllegalArgumentException("pathLen < -2");
842        }
843        this.pathLen = pathLen;
844    }
845
846    /**
847     * Returns the criterion for the basic constraints extension.
848     * <p>
849     * A value greater than or equal to zero indicates that a certificate must
850     * include a basic constraints extension with a path length of a least that
851     * value. A value of {@code -2} indicates that only end-entity certificates
852     * are accepted. A value of {@code -1} indicates that no check is done.
853     *
854     * @return the value of the criterion.
855     */
856    public int getBasicConstraints() {
857        return pathLen;
858    }
859
860    /**
861     * Sets the criterion for the policy constraint.
862     * <p>
863     * The certificate must have at least one of the specified certificate
864     * policy extensions. For an empty set the certificate must have at least
865     * some policies in its policy extension.
866     *
867     * @param policies
868     *            the certificate policy OIDs, an empty set, or {@code null} to
869     *            not perform this check.
870     * @throws IOException
871     *             if parsing the specified OIDs fails.
872     */
873    public void setPolicy(Set<String> policies) throws IOException {
874        if (policies == null) {
875            this.policies = null;
876            return;
877        }
878        HashSet pols = new HashSet(policies.size());
879        Iterator it = policies.iterator();
880        while (it.hasNext()) {
881            String certPolicyId = (String) it.next();
882            checkOID(certPolicyId);
883            pols.add(certPolicyId);
884        }
885        this.policies = Collections.unmodifiableSet(pols);
886    }
887
888    /**
889     * Returns the criterion for the policy constraint.
890     * <p>
891     * The certificate must have at least one of the certificate policy
892     * extensions. For an empty set the certificate must have at least some
893     * policies in its policy extension.
894     *
895     * @return the certificate policy OIDs, an empty set, or {@code null} if not
896     *         to be checked.
897     */
898    public Set<String> getPolicy() {
899        return policies;
900    }
901
902    /**
903     * Sets the criterion for the pathToNames constraint.
904     * <p>
905     * This allows to specify the complete set of names, a certificate's name
906     * constraints must permit.
907     * <p>
908     * The specified parameter {@code names} is a collection with an entry for
909     * each name to be included in the criterion. The name is specified as a
910     * {@code List}, the first entry must be an {@code Integer} specifying the
911     * name type (0-8), the second entry must be a {@code String} or a byte
912     * array specifying the name (in string or ASN.1 DER encoded form)
913     *
914     * @param names
915     *            the names collection or {@code null} to not perform this
916     *            check.
917     * @throws IOException
918     *             if decoding fails.
919     */
920    public void setPathToNames(Collection<List<?>> names)
921                                                        throws IOException {
922        pathToNames = null;
923        if ((names == null) || (names.size() == 0)) {
924            return;
925        }
926        Iterator it = names.iterator();
927        while (it.hasNext()) {
928            List name = (List) it.next();
929            int tag = ((Integer) name.get(0)).intValue();
930            Object value = name.get(1);
931            if (value instanceof String) {
932                addPathToName(tag, (String) value);
933            } else if (value instanceof byte[]) {
934                addPathToName(tag, (byte[]) value);
935            } else {
936                throw new IOException("name neither a String nor a byte[]");
937            }
938        }
939    }
940
941    /**
942     * Adds a {@literal "pathToName"} to the respective criterion.
943     *
944     * @param type
945     *            the type of the name.
946     * @param name
947     *            the name in string format.
948     * @throws IOException
949     *             if parsing fails.
950     * @see #setPathToNames
951     */
952    public void addPathToName(int type, String name) throws IOException {
953        GeneralName path_name = new GeneralName(type, name);
954        // create only if there was not any errors
955        if (pathToNames == null) {
956            pathToNames = new ArrayList();
957        }
958        pathToNames.add(path_name);
959    }
960
961    /**
962     * Adds a {@literal "pathToName"} to the respective criterion.
963     *
964     * @param type
965     *            the type of the name
966     * @param name
967     *            the name in ASN.1 DER encoded form.
968     * @throws IOException
969     *             if decoding fails.
970     * @see #setPathToNames
971     */
972    public void addPathToName(int type, byte[] name) throws IOException {
973        GeneralName path_name= new GeneralName(type, name);
974        // create only if there was not any errors
975        if (pathToNames == null) {
976            pathToNames = new ArrayList();
977        }
978        pathToNames.add(path_name);
979    }
980
981    /**
982     * Returns the criterion for the pathToNames constraint.
983     * <p>
984     * The constraint is a collection with an entry for each name to be included
985     * in the criterion. The name is specified as a {@code List}, the first
986     * entry is an {@code Integer} specifying the name type (0-8), the second
987     * entry is a byte array specifying the name in ASN.1 DER encoded form.
988     *
989     * @return the pathToNames constraint or {@code null} if none specified.
990     */
991    public Collection<List<?>> getPathToNames() {
992        if (pathToNames == null) {
993            return null;
994        }
995        ArrayList result = new ArrayList();
996        Iterator it = pathToNames.iterator();
997        while (it.hasNext()) {
998            GeneralName name = (GeneralName) it.next();
999            result.add(name.getAsList());
1000        }
1001        return result;
1002    }
1003
1004    /**
1005     * Returns a string representation of this {@code X509CertSelector}
1006     * instance.
1007     *
1008     * @return a string representation of this {@code X509CertSelector}
1009     *         instance.
1010     */
1011    public String toString() {
1012        // For convenient reading of the string representation
1013        // all of the fields named according to the rfc 3280
1014        // (http://www.ietf.org/rfc/rfc3280.txt).
1015
1016        StringBuilder result = new StringBuilder();
1017        result.append("X509CertSelector: \n[");
1018        if (this.certificateEquals != null) {
1019            result.append("\n  certificateEquals: " + certificateEquals);
1020        }
1021        if (this.serialNumber != null) {
1022            //FIXME: needs DRL's BigInteger.toString implementation
1023            //result.append("\n  serialNumber: " + serialNumber);
1024        }
1025        if (this.issuer != null) {
1026            result.append("\n  issuer: " + issuer);
1027        }
1028        if (this.subject != null) {
1029            result.append("\n  subject: " + subject);
1030        }
1031        if (this.subjectKeyIdentifier != null) {
1032            result.append("\n  subjectKeyIdentifier: "
1033                    + getBytesAsString(subjectKeyIdentifier));
1034        }
1035        if (this.authorityKeyIdentifier != null) {
1036            result.append("\n  authorityKeyIdentifier: "
1037                    + getBytesAsString(authorityKeyIdentifier));
1038        }
1039        if (this.certificateValid != null) {
1040            result.append("\n  certificateValid: " + certificateValid);
1041        }
1042        if (this.subjectPublicKeyAlgID != null) {
1043            result.append("\n  subjectPublicKeyAlgID: "
1044                    + subjectPublicKeyAlgID);
1045        }
1046        if (this.privateKeyValid != null) {
1047            result.append("\n  privateKeyValid: " + privateKeyValid);
1048        }
1049        if (this.subjectPublicKey != null) {
1050            result.append("\n  subjectPublicKey: "
1051                    + getBytesAsString(subjectPublicKey));
1052        }
1053        if (this.keyUsage != null) {
1054            result.append("\n  keyUsage: \n  [");
1055            String[] kuNames = new String[] {
1056                "digitalSignature", "nonRepudiation", "keyEncipherment",
1057                "dataEncipherment", "keyAgreement", "keyCertSign", "cRLSign",
1058                "encipherOnly", "decipherOnly"
1059            };
1060            for (int i=0; i<9; i++) {
1061                if (keyUsage[i]) {
1062                    result.append("\n    " + kuNames[i]);
1063                }
1064            }
1065            result.append("\n  ]");
1066        }
1067        if (this.extendedKeyUsage != null) {
1068            result.append("\n  extendedKeyUsage: "
1069                    + extendedKeyUsage.toString());
1070        }
1071        result.append("\n  matchAllNames: " + matchAllNames);
1072        result.append("\n  pathLen: " + pathLen);
1073        if (this.subjectAltNames != null) {
1074            result.append("\n  subjectAltNames:  \n  [");
1075            for (int i=0; i<9; i++) {
1076                List names = this.subjectAltNames[i];
1077                if (names != null) {
1078                    int size = names.size();
1079                    for (int j=0; j<size; j++) {
1080                        result.append("\n    "
1081                            + ((GeneralName)names.get(j)).toString());
1082                    }
1083                }
1084            }
1085            result.append("\n  ]");
1086        }
1087        if (this.nameConstraints != null) {
1088        }
1089        if (this.policies != null) {
1090            result.append("\n  policies: " + policies.toString());
1091        }
1092        if (this.pathToNames != null) {
1093            result.append("\n  pathToNames:  \n  [");
1094            int size = pathToNames.size();
1095            for (int i = 0; i < size; i++) {
1096                result.append("\n    "
1097                    + ((GeneralName)pathToNames.get(i)).toString());
1098            }
1099        }
1100        result.append("\n]");
1101        return result.toString();
1102    }
1103
1104    private String getBytesAsString(byte[] data) {
1105        String result = "";
1106        for (int i=0; i<data.length; i++) {
1107            String tail = Integer.toHexString(0x00ff & data[i]);
1108            if (tail.length() == 1) {
1109                tail = "0" + tail;
1110            }
1111            result += tail + " ";
1112        }
1113        return result;
1114    }
1115
1116    private byte[] getExtensionValue(X509Certificate cert, String oid) {
1117        try {
1118            byte[] bytes = cert.getExtensionValue(oid);
1119            if (bytes == null) {
1120                return null;
1121            }
1122            return (byte[]) ASN1OctetString.getInstance().decode(bytes);
1123        } catch (IOException e) {
1124            return null;
1125        }
1126    }
1127
1128    /**
1129     * Returns whether the specified certificate matches all the criteria
1130     * collected in this instance.
1131     *
1132     * @param certificate
1133     *            the certificate to check.
1134     * @return {@code true} if the certificate matches all the criteria,
1135     *         otherwise {@code false}.
1136     */
1137    public boolean match(Certificate certificate) {
1138        if (! (certificate instanceof X509Certificate)) {
1139            return false;
1140        }
1141
1142        X509Certificate cert = (X509Certificate) certificate;
1143        if ((certificateEquals != null) &&
1144            !certificateEquals.equals(cert)) {
1145            return false;
1146        }
1147        if ((serialNumber != null) &&
1148            !serialNumber.equals(cert.getSerialNumber())) {
1149            return false;
1150        }
1151        if ((issuer != null) &&
1152            !issuer.equals(cert.getIssuerX500Principal())) {
1153            return false;
1154        }
1155        if ((subject != null) &&
1156            !subject.equals(cert.getSubjectX500Principal())) {
1157            return false;
1158        }
1159        if ((subjectKeyIdentifier != null) &&
1160            !Arrays.equals(subjectKeyIdentifier,
1161            // Here and later all of the extension OIDs
1162            // are taken from rfc 3280 (http://www.ietf.org/rfc/rfc3280.txt)
1163                           getExtensionValue(cert, "2.5.29.14"))) {
1164            return false;
1165        }
1166        if ((authorityKeyIdentifier != null) &&
1167            !Arrays.equals(authorityKeyIdentifier,
1168                           getExtensionValue(cert, "2.5.29.35"))) {
1169            return false;
1170        }
1171        if (certificateValid != null) {
1172            try {
1173                cert.checkValidity(certificateValid);
1174            } catch(CertificateExpiredException e) {
1175                return false;
1176            } catch(CertificateNotYetValidException e) {
1177                return false;
1178            }
1179        }
1180        if (privateKeyValid != null) {
1181            try {
1182                byte[] bytes = getExtensionValue(cert, "2.5.29.16");
1183                if (bytes == null) {
1184                    return false;
1185                }
1186                PrivateKeyUsagePeriod pkup = (PrivateKeyUsagePeriod)
1187                                    PrivateKeyUsagePeriod.ASN1.decode(bytes);
1188                Date notBefore = pkup.getNotBefore();
1189                Date notAfter = pkup.getNotAfter();
1190                if ((notBefore == null) && (notAfter == null)) {
1191                    return false;
1192                }
1193                if ((notBefore != null)
1194                    && notBefore.compareTo(privateKeyValid) > 0) {
1195                    return false;
1196                }
1197                if ((notAfter != null)
1198                    && notAfter.compareTo(privateKeyValid) < 0) {
1199                    return false;
1200                }
1201            } catch (IOException e) {
1202                return false;
1203            }
1204        }
1205        if (subjectPublicKeyAlgID  != null) {
1206            try {
1207                byte[] encoding = cert.getPublicKey().getEncoded();
1208                AlgorithmIdentifier ai = ((SubjectPublicKeyInfo)
1209                        SubjectPublicKeyInfo.ASN1.decode(encoding))
1210                        .getAlgorithmIdentifier();
1211                if (!subjectPublicKeyAlgID.equals(ai.getAlgorithm())) {
1212                    return false;
1213                }
1214            } catch (IOException e) {
1215                e.printStackTrace();
1216                return false;
1217            }
1218        }
1219        if (subjectPublicKey != null) {
1220            if (!Arrays.equals(subjectPublicKey,
1221                               cert.getPublicKey().getEncoded())) {
1222                return false;
1223            }
1224        }
1225        if (keyUsage != null) {
1226            boolean[] ku = cert.getKeyUsage();
1227            if (ku != null) {
1228                int i = 0;
1229                int min_length = (ku.length < keyUsage.length) ? ku.length
1230                        : keyUsage.length;
1231                for (; i < min_length; i++) {
1232                    if (keyUsage[i] && !ku[i]) {
1233                        // the specified keyUsage allows,
1234                        // but certificate does not.
1235                        return false;
1236                    }
1237                }
1238                for (; i<keyUsage.length; i++) {
1239                    if (keyUsage[i]) {
1240                        return false;
1241                    }
1242                }
1243            }
1244        }
1245        if (extendedKeyUsage != null) {
1246            try {
1247                List keyUsage = cert.getExtendedKeyUsage();
1248                if (keyUsage != null) {
1249                    if (!keyUsage.containsAll(extendedKeyUsage)) {
1250                        return false;
1251                    }
1252                }
1253            } catch (CertificateParsingException e) {
1254                return false;
1255            }
1256        }
1257        if (pathLen != -1) {
1258            int p_len = cert.getBasicConstraints();
1259            if ((pathLen < 0) && (p_len >= 0)) {
1260                // need end-entity but got CA
1261                return false;
1262            }
1263            if ((pathLen > 0) && (pathLen > p_len)) {
1264                // allowed _pathLen is small
1265                return false;
1266            }
1267        }
1268        if (subjectAltNames != null) {
1269            PASSED:
1270            try {
1271                byte[] bytes = getExtensionValue(cert, "2.5.29.17");
1272                if (bytes == null) {
1273                    return false;
1274                }
1275                List sans = ((GeneralNames) GeneralNames.ASN1.decode(bytes))
1276                            .getNames();
1277                if ((sans == null) || (sans.size() == 0)) {
1278                    return false;
1279                }
1280                boolean[][] map = new boolean[9][];
1281                // initialize the check map
1282                for (int i=0; i<9; i++) {
1283                    map[i] = (subjectAltNames[i] == null)
1284                                ? new boolean[0]
1285                                : new boolean[subjectAltNames[i].size()];
1286                }
1287                Iterator it = sans.iterator();
1288                while (it.hasNext()) {
1289                    GeneralName name = (GeneralName) it.next();
1290                    int tag = name.getTag();
1291                    for (int i=0; i<map[tag].length; i++) {
1292                        if (((GeneralName) subjectAltNames[tag].get(i))
1293                                                            .equals(name)) {
1294                            if (!matchAllNames) {
1295                                break PASSED;
1296                            }
1297                            map[tag][i] = true;
1298                        }
1299                    }
1300                }
1301                if (!matchAllNames) {
1302                    // there was not any match
1303                    return false;
1304                }
1305                // else check the map
1306                for (int tag=0; tag<9; tag++) {
1307                    for (int name=0; name<map[tag].length; name++) {
1308                        if (!map[tag][name]) {
1309                            return false;
1310                        }
1311                    }
1312                }
1313            } catch (IOException e) {
1314                e.printStackTrace();
1315                return false;
1316            }
1317        }
1318        if (nameConstraints != null) {
1319            if (!nameConstraints.isAcceptable(cert)) {
1320                return false;
1321            }
1322        }
1323        if (policies != null) {
1324            byte[] bytes = getExtensionValue(cert, "2.5.29.32");
1325            if (bytes == null) {
1326                return false;
1327            }
1328            if (policies.size() == 0) {
1329                // if certificate has such extension than it has at least
1330                // one policy in it.
1331                return true;
1332            }
1333            PASSED:
1334            try {
1335                List policyInformations = ((CertificatePolicies)
1336                        CertificatePolicies.ASN1.decode(bytes))
1337                        .getPolicyInformations();
1338                Iterator it = policyInformations.iterator();
1339                while (it.hasNext()) {
1340                    if (policies.contains(((PolicyInformation) it.next())
1341                                          .getPolicyIdentifier())) {
1342                        break PASSED;
1343                    }
1344                }
1345                return false;
1346            } catch (IOException e) {
1347                // the extension is invalid
1348                return false;
1349            }
1350        }
1351        if (pathToNames != null) {
1352            byte[] bytes = getExtensionValue(cert, "2.5.29.30");
1353            if (bytes != null) {
1354                NameConstraints nameConstraints;
1355                try {
1356                    nameConstraints =
1357                        (NameConstraints) NameConstraints.ASN1.decode(bytes);
1358                } catch (IOException e) {
1359                    // the extension is invalid;
1360                    return false;
1361                }
1362                if (!nameConstraints.isAcceptable(pathToNames)) {
1363                    return false;
1364                }
1365            }
1366        }
1367        return true;
1368    }
1369
1370    /**
1371     * Clones this {@code X509CertSelector} instance.
1372     *
1373     * @return the cloned instance.
1374     */
1375    public Object clone() {
1376        X509CertSelector result;
1377
1378        try {
1379            result = (X509CertSelector) super.clone();
1380        } catch (CloneNotSupportedException e) {
1381            return null;
1382        }
1383
1384        if (this.subjectKeyIdentifier != null) {
1385            result.subjectKeyIdentifier =
1386                new byte[this.subjectKeyIdentifier.length];
1387            System.arraycopy(this.subjectKeyIdentifier, 0,
1388                    result.subjectKeyIdentifier, 0,
1389                    this.subjectKeyIdentifier.length);
1390        }
1391        if (this.authorityKeyIdentifier != null) {
1392            result.authorityKeyIdentifier =
1393                new byte[this.authorityKeyIdentifier.length];
1394            System.arraycopy(this.authorityKeyIdentifier, 0,
1395                    result.authorityKeyIdentifier, 0,
1396                    this.authorityKeyIdentifier.length);
1397        }
1398        if (this.subjectPublicKey != null) {
1399            result.subjectPublicKey = new byte[this.subjectPublicKey.length];
1400            System.arraycopy(this.subjectPublicKey, 0, result.subjectPublicKey,
1401                    0, this.subjectPublicKey.length);
1402        }
1403        if (this.keyUsage != null) {
1404            result.keyUsage = new boolean[this.keyUsage.length];
1405            System.arraycopy(this.keyUsage, 0, result.keyUsage, 0,
1406                    this.keyUsage.length);
1407        }
1408        result.extendedKeyUsage = (this.extendedKeyUsage == null)
1409            ? null
1410            : new HashSet(this.extendedKeyUsage);
1411        if (this.subjectAltNames != null) {
1412            result.subjectAltNames = new ArrayList[9];
1413            for (int i=0; i<9; i++) {
1414                if (this.subjectAltNames[i] != null) {
1415                    result.subjectAltNames[i] =
1416                        new ArrayList(this.subjectAltNames[i]);
1417                }
1418            }
1419        }
1420        result.policies = (this.policies == null)
1421            ? null
1422            : new HashSet(this.policies);
1423        result.pathToNames = (this.pathToNames == null)
1424            ? null
1425            : new ArrayList(this.pathToNames);
1426        return result;
1427    }
1428}
1429