1 /* X509CertSelector.java -- selects X.509 certificates by criteria.
2 Copyright (C) 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package java.security.cert;
41 import gnu.classpath.SystemProperties;
42 import gnu.java.security.OID;
44 import java.io.IOException;
45 import java.math.BigInteger;
46 import java.security.KeyFactory;
47 import java.security.PublicKey;
48 import java.security.spec.X509EncodedKeySpec;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collection;
52 import java.util.Collections;
53 import java.util.Date;
54 import java.util.HashSet;
55 import java.util.Iterator;
56 import java.util.LinkedList;
57 import java.util.List;
60 import javax.security.auth.x500.X500Principal;
63 * A concrete implementation of {@link CertSelector} for X.509 certificates,
64 * which allows a number of criteria to be set when accepting certificates,
65 * from validity dates, to issuer and subject distinguished names, to some
66 * of the various X.509 extensions.
68 * <p>Use of this class requires extensive knowledge of the Internet
69 * Engineering Task Force's Public Key Infrastructure (X.509). The primary
70 * document describing this standard is <a
71 * href="http://www.ietf.org/rfc/rfc3280.txt">RFC 3280: Internet X.509
72 * Public Key Infrastructure Certificate and Certificate Revocation List
75 * <p>Note that this class is not thread-safe. If multiple threads will
76 * use or modify this class then they need to synchronize on the object.
78 * @author Casey Marshall (csm@gnu.org)
80 public class X509CertSelector implements CertSelector, Cloneable
83 // Constants and fields.
84 // -------------------------------------------------------------------------
86 private static final String AUTH_KEY_ID = "2.5.29.35";
87 private static final String SUBJECT_KEY_ID = "2.5.29.14";
88 private static final String NAME_CONSTRAINTS_ID = "2.5.29.30";
90 private int basicConstraints;
91 private X509Certificate cert;
92 private BigInteger serialNo;
93 private X500Principal issuer;
94 private X500Principal subject;
95 private byte[] subjectKeyId;
96 private byte[] authKeyId;
97 private boolean[] keyUsage;
98 private Date certValid;
100 private PublicKey subjectKey;
101 private X509EncodedKeySpec subjectKeySpec;
102 private Set keyPurposeSet;
103 private List altNames;
104 private boolean matchAllNames;
105 private byte[] nameConstraints;
109 // ------------------------------------------------------------------------
112 * Creates a new X.509 certificate selector. The new selector will be
113 * empty, and will accept any certificate (provided that it is an
114 * {@link X509Certificate}).
116 public X509CertSelector()
118 basicConstraints = -1;
122 // ------------------------------------------------------------------------
125 * Returns the certificate criterion, or <code>null</code> if this value
128 * @return The certificate.
130 public X509Certificate getCertificate()
136 * Sets the certificate criterion. If set, only certificates that are
137 * equal to the certificate passed here will be accepted.
139 * @param cert The certificate.
141 public void setCertificate(X509Certificate cert)
147 * Returns the serial number criterion, or <code>null</code> if this
150 * @return The serial number.
152 public BigInteger getSerialNumber()
158 * Sets the serial number of the desired certificate. Only certificates that
159 * contain this serial number are accepted.
161 * @param serialNo The serial number.
163 public void setSerialNumber(BigInteger serialNo)
165 this.serialNo = serialNo;
169 * Returns the issuer criterion as a string, or <code>null</code> if this
172 * @return The issuer.
174 public String getIssuerAsString()
177 return issuer.getName();
183 * Returns the issuer criterion as a sequence of DER bytes, or
184 * <code>null</code> if this value was not set.
186 * @return The issuer.
188 public byte[] getIssuerAsBytes() throws IOException
191 return issuer.getEncoded();
197 * Sets the issuer, specified as a string representation of the issuer's
198 * distinguished name. Only certificates issued by this issuer will
201 * @param name The string representation of the issuer's distinguished name.
202 * @throws IOException If the given name is incorrectly formatted.
204 public void setIssuer(String name) throws IOException
210 issuer = new X500Principal(name);
212 catch (IllegalArgumentException iae)
214 throw new IOException(iae.getMessage());
222 * Sets the issuer, specified as the DER encoding of the issuer's
223 * distinguished name. Only certificates issued by this issuer will
226 * @param name The DER encoding of the issuer's distinguished name.
227 * @throws IOException If the given name is incorrectly formatted.
229 public void setIssuer(byte[] name) throws IOException
235 issuer = new X500Principal(name);
237 catch (IllegalArgumentException iae)
239 throw new IOException(iae.getMessage());
247 * Returns the subject criterion as a string, of <code>null</code> if
248 * this value was not set.
250 * @return The subject.
252 public String getSubjectAsString()
255 return subject.getName();
261 * Returns the subject criterion as a sequence of DER bytes, or
262 * <code>null</code> if this value is not set.
264 * @return The subject.
266 public byte[] getSubjectAsBytes() throws IOException
269 return subject.getEncoded();
275 * Sets the subject, specified as a string representation of the
276 * subject's distinguished name. Only certificates with the given
277 * subject will be accepted.
279 * @param name The string representation of the subject's distinguished name.
280 * @throws IOException If the given name is incorrectly formatted.
282 public void setSubject(String name) throws IOException
288 subject = new X500Principal(name);
290 catch (IllegalArgumentException iae)
292 throw new IOException(iae.getMessage());
300 * Sets the subject, specified as the DER encoding of the subject's
301 * distinguished name. Only certificates with the given subject will
304 * @param name The DER encoding of the subject's distinguished name.
305 * @throws IOException If the given name is incorrectly formatted.
307 public void setSubject(byte[] name) throws IOException
313 subject = new X500Principal(name);
315 catch (IllegalArgumentException iae)
317 throw new IOException(iae.getMessage());
325 * Returns the subject key identifier criterion, or <code>null</code> if
326 * this value was not set. Note that the byte array is cloned to prevent
329 * @return The subject key identifier.
331 public byte[] getSubjectKeyIdentifier()
333 if (subjectKeyId != null)
334 return (byte[]) subjectKeyId.clone();
340 * Sets the subject key identifier criterion, or <code>null</code> to clear
341 * this criterion. Note that the byte array is cloned to prevent modification.
343 * @param subjectKeyId The subject key identifier.
345 public void setSubjectKeyIdentifier(byte[] subjectKeyId)
347 this.subjectKeyId = subjectKeyId != null ? (byte[]) subjectKeyId.clone() :
352 * Returns the authority key identifier criterion, or <code>null</code> if
353 * this value was not set. Note that the byte array is cloned to prevent
356 * @return The authority key identifier.
358 public byte[] getAuthorityKeyIdentifier()
360 if (authKeyId != null)
361 return (byte[]) authKeyId.clone();
367 * Sets the authority key identifier criterion, or <code>null</code> to clear
368 * this criterion. Note that the byte array is cloned to prevent modification.
370 * @param authKeyId The authority key identifier.
372 public void setAuthorityKeyIdentifier(byte[] authKeyId)
374 this.authKeyId = authKeyId != null ? (byte[]) authKeyId.clone() : null;
378 * Returns the date at which certificates must be valid, or <code>null</code>
379 * if this criterion was not set.
381 * @return The target certificate valitity date.
383 public Date getCertificateValid()
385 if (certValid != null)
386 return (Date) certValid.clone();
392 * Sets the date at which certificates must be valid. Specify
393 * <code>null</code> to clear this criterion.
395 * @param certValid The certificate validity date.
397 public void setCertificateValid(Date certValid)
399 this.certValid = certValid != null ? (Date) certValid.clone() : null;
403 * This method, and its related X.509 certificate extension — the
404 * private key usage period — is not supported under the Internet
405 * PKI for X.509 certificates (PKIX), described in RFC 3280. As such, this
406 * method is not supported either.
408 * <p>Do not use this method. It is not deprecated, as it is not deprecated
409 * in the Java standard, but it is basically a no-operation and simply
410 * returns <code>null</code>.
414 public Date getPrivateKeyValid()
420 * This method, and its related X.509 certificate extension — the
421 * private key usage period — is not supported under the Internet
422 * PKI for X.509 certificates (PKIX), described in RFC 3280. As such, this
423 * method is not supported either.
425 * <p>Do not use this method. It is not deprecated, as it is not deprecated
426 * in the Java standard, but it is basically a no-operation.
428 * @param UNUSED Is silently ignored.
430 public void setPrivateKeyValid(Date UNUSED)
435 * Returns the public key algorithm ID that matching certificates must have,
436 * or <code>null</code> if this criterion was not set.
438 * @return The public key algorithm ID.
440 public String getSubjectPublicKeyAlgID()
442 return String.valueOf(sigId);
446 * Sets the public key algorithm ID that matching certificates must have.
447 * Specify <code>null</code> to clear this criterion.
449 * @param sigId The public key ID.
450 * @throws IOException If the specified ID is not a valid object identifier.
452 public void setSubjectPublicKeyAlgID(String sigId) throws IOException
458 OID oid = new OID(sigId);
459 int[] comp = oid.getIDs();
461 throw new IOException("malformed OID: " + sigId);
464 catch (IllegalArgumentException iae)
466 IOException ioe = new IOException("malformed OID: " + sigId);
476 * Returns the subject public key criterion, or <code>null</code> if this
479 * @return The subject public key.
481 public PublicKey getSubjectPublicKey()
487 * Sets the subject public key criterion as an opaque representation.
488 * Specify <code>null</code> to clear this criterion.
490 * @param key The public key.
492 public void setSubjectPublicKey(PublicKey key)
494 this.subjectKey = key;
497 subjectKeySpec = null;
502 KeyFactory enc = KeyFactory.getInstance("X.509");
503 subjectKeySpec = (X509EncodedKeySpec)
504 enc.getKeySpec(key, X509EncodedKeySpec.class);
509 subjectKeySpec = null;
514 * Sets the subject public key criterion as a DER-encoded key. Specify
515 * <code>null</code> to clear this value.
517 * @param key The DER-encoded key bytes.
518 * @throws IOException If the argument is not a valid DER-encoded key.
520 public void setSubjectPublicKey(byte[] key) throws IOException
525 subjectKeySpec = null;
530 subjectKeySpec = new X509EncodedKeySpec(key);
531 KeyFactory enc = KeyFactory.getInstance("X.509");
532 subjectKey = enc.generatePublic(subjectKeySpec);
537 subjectKeySpec = null;
538 IOException ioe = new IOException(x.getMessage());
545 * Returns the public key usage criterion, or <code>null</code> if this
546 * value is not set. Note that the array is cloned to prevent modification.
548 * @return The public key usage.
550 public boolean[] getKeyUsage()
552 if (keyUsage != null)
553 return (boolean[]) keyUsage.clone();
559 * Sets the public key usage criterion. Specify <code>null</code> to clear
562 * @param keyUsage The public key usage.
564 public void setKeyUsage(boolean[] keyUsage)
566 this.keyUsage = keyUsage != null ? (boolean[]) keyUsage.clone() : null;
570 * Returns the set of extended key purpose IDs, as an unmodifiable set
571 * of OID strings. Returns <code>null</code> if this criterion is not
574 * @return The set of key purpose OIDs (strings).
576 public Set getExtendedKeyUsage()
578 if (keyPurposeSet != null)
579 return Collections.unmodifiableSet(keyPurposeSet);
585 * Sets the extended key usage criterion, as a set of OID strings. Specify
586 * <code>null</code> to clear this value.
588 * @param keyPurposeSet The set of key purpose OIDs.
589 * @throws IOException If any element of the set is not a valid OID string.
591 public void setExtendedKeyUsage(Set keyPurposeSet) throws IOException
593 if (keyPurposeSet == null)
595 this.keyPurposeSet = null;
598 Set s = new HashSet();
599 for (Iterator it = keyPurposeSet.iterator(); it.hasNext(); )
601 Object o = it.next();
602 if (!(o instanceof String))
603 throw new IOException("not a string: " + o);
606 OID oid = new OID((String) o);
607 int[] comp = oid.getIDs();
609 throw new IOException("malformed OID: " + o);
611 catch (IllegalArgumentException iae)
613 IOException ioe = new IOException("malformed OID: " + o);
618 this.keyPurposeSet = s;
622 * Returns whether or not all specified alternative names must match.
623 * If false, a certificate is considered a match if <em>one</em> of the
624 * specified alternative names matches.
626 * @return true if all names must match.
628 public boolean getMatchAllSubjectAltNames()
630 return matchAllNames;
634 * Sets whether or not all subject alternative names must be matched.
635 * If false, then a certificate will be considered a match if one
636 * alternative name matches.
638 * @param matchAllNames Whether or not all alternative names must be
641 public void setMatchAllSubjectAltNames(boolean matchAllNames)
643 this.matchAllNames = matchAllNames;
647 * Sets the subject alternative names critertion. Each element of the
648 * argument must be a {@link java.util.List} that contains exactly two
649 * elements: the first an {@link Integer}, representing the type of
650 * name, and the second either a {@link String} or a byte array,
651 * representing the name itself.
653 * @param altNames The alternative names.
654 * @throws IOException If any element of the argument is invalid.
656 public void setSubjectAlternativeNames(Collection altNames)
659 if (altNames == null)
661 this.altNames = null;
664 List l = new ArrayList(altNames.size());
665 for (Iterator it = altNames.iterator(); it.hasNext(); )
667 Object o = it.next();
668 if (!(o instanceof List) || ((List) o).size() != 2 ||
669 !(((List) o).get(0) instanceof Integer) ||
670 !(((List) o).get(1) instanceof String) ||
671 !(((List) o).get(1) instanceof byte[]))
672 throw new IOException("illegal alternative name: " + o);
673 Integer i = (Integer) ((List) o).get(0);
674 if (i.intValue() < 0 || i.intValue() > 8)
675 throw new IOException("illegal alternative name: " + o +
677 l.add(new ArrayList((List) o));
683 * Add a name to the subject alternative names criterion.
685 * @param id The type of name this is. Must be in the range [0,8].
686 * @param name The name.
687 * @throws IOException If the id is out of range, or if the name
690 public void addSubjectAlternativeName(int id, String name)
693 if (id < 0 || id > 8 || name == null)
694 throw new IOException("illegal alternative name");
695 if (altNames == null)
696 altNames = new LinkedList();
697 ArrayList l = new ArrayList(2);
698 l.add(Integer.valueOf(id));
704 * Add a name, as DER-encoded bytes, to the subject alternative names
707 * @param id The type of name this is.
709 public void addSubjectAlternativeName(int id, byte[] name)
712 if (id < 0 || id > 8 || name == null)
713 throw new IOException("illegal alternative name");
714 if (altNames == null)
715 altNames = new LinkedList();
716 ArrayList l = new ArrayList(2);
717 l.add(Integer.valueOf(id));
723 * Returns the name constraints criterion, or <code>null</code> if this
724 * value is not set. Note that the byte array is cloned to prevent
727 * @return The name constraints.
729 public byte[] getNameConstraints()
731 if (nameConstraints != null)
732 return (byte[]) nameConstraints.clone();
738 * Sets the name constraints criterion; specify <code>null</code> to
739 * clear this criterion. Note that if non-null, the argument will be
740 * cloned to prevent modification.
742 * @param nameConstraints The new name constraints.
743 * @throws IOException If the argument is not a valid DER-encoded
746 public void setNameConstraints(byte[] nameConstraints)
749 // FIXME check if the argument is valid.
750 this.nameConstraints = nameConstraints != null
751 ? (byte[]) nameConstraints.clone() : null;
755 * Returns the basic constraints criterion, or -1 if this value is not set.
757 * @return The basic constraints.
759 public int getBasicConstraints()
761 return basicConstraints;
765 * Sets the basic constraints criterion. Specify -1 to clear this parameter.
767 * @param basicConstraints The new basic constraints value.
769 public void setBasicConstraints(int basicConstraints)
771 if (basicConstraints < -1)
772 basicConstraints = -1;
773 this.basicConstraints = basicConstraints;
776 // The last two criteria not yet implemented are certificate policies
777 // and path-to-names. Both of these are somewhat advanced extensions
778 // (you could probably count the applications that actually use them
779 // on one hand), and they both have no support in the X509Certificate
782 // Not having support in X509Certificate is not always a problem; for
783 // example, we can compare DER-encoded values as byte arrays for some
784 // extensions. We can't, however, compare them if they are specified
785 // in a set (as policies are). We need to parse the actual value in the
786 // certificate, and check it against the specified set.
789 // public void setPolicy(Set policy) throws IOException
791 // if (policy != null)
793 // for (Iterator it = policy.iterator(); it.hasNext(); )
796 // OID oid = new OID((String) it.next());
797 // int[] i = oid.getIDs();
799 // throw new IOException("invalid OID");
801 // catch (Exception x)
803 // throw new IOException("invalid OID");
806 // this.policy = policy != null ? new HashSet(policy) : null;
810 // public void setPathToNames(Collection names) throws IOException
812 // if (names == null)
814 // this.names = null;
817 // for (Iterator it = names.iterator(); it.hasNext(); )
821 // List l = (List) it.next();
822 // if (l.get(1) instanceof String)
823 // addPathToName(((Integer)l.get(0)).intValue(), (String)l.get(1));
825 // addPathToName(((Integer)l.get(0)).intValue(), (byte[])l.get(1));
827 // catch (Exception x)
829 // this.names = null;
830 // throw new IOException("invalid names");
836 // public void addPathToName(int id, String name) throws IOException
841 // public void addPathToName(int id, byte[] name) throws IOException
846 // public Collection getSubjectAlternativeNames()
852 // public Set getPolicy()
858 // public Collection getPathToNames()
864 * Match a certificate. This method will check the given certificate
865 * against all the enabled criteria of this selector, and will return
866 * <code>true</code> if the given certificate matches.
868 * @param certificate The certificate to check.
869 * @return true if the certificate matches all criteria.
871 public boolean match(Certificate certificate)
873 if (!(certificate instanceof X509Certificate))
875 X509Certificate cert = (X509Certificate) certificate;
876 if (this.cert != null)
880 byte[] e1 = this.cert.getEncoded();
881 byte[] e2 = cert.getEncoded();
882 if (!Arrays.equals(e1, e2))
885 catch (CertificateEncodingException cee)
890 if (serialNo != null)
892 if (!serialNo.equals(cert.getSerialNumber()))
895 if (certValid != null)
899 cert.checkValidity(certValid);
901 catch (CertificateException ce)
908 if (!issuer.equals(cert.getIssuerX500Principal()))
913 if (!subject.equals(cert.getSubjectX500Principal()))
918 if (!sigId.toString().equals(cert.getSigAlgOID()))
921 if (subjectKeyId != null)
923 byte[] b = cert.getExtensionValue(SUBJECT_KEY_ID);
924 if (!Arrays.equals(b, subjectKeyId))
927 if (authKeyId != null)
929 byte[] b = cert.getExtensionValue(AUTH_KEY_ID);
930 if (!Arrays.equals(b, authKeyId))
933 if (keyUsage != null)
935 boolean[] b = cert.getKeyUsage();
936 if (!Arrays.equals(b, keyUsage))
939 if (basicConstraints >= 0)
941 if (cert.getBasicConstraints() != basicConstraints)
944 if (keyPurposeSet != null)
949 kp = cert.getExtendedKeyUsage();
951 catch (CertificateParsingException cpe)
957 for (Iterator it = keyPurposeSet.iterator(); it.hasNext(); )
959 if (!kp.contains(it.next()))
963 if (altNames != null)
965 Collection an = null;
968 an = cert.getSubjectAlternativeNames();
970 catch (CertificateParsingException cpe)
977 for (Iterator it = altNames.iterator(); it.hasNext(); )
979 List l = (List) it.next();
980 Integer id = (Integer) l.get(0);
983 if (l.get(1) instanceof String)
984 s = (String) l.get(1);
985 else if (l.get(1) instanceof byte[])
986 b = (byte[]) l.get(1);
989 for (Iterator it2 = an.iterator(); it2.hasNext(); )
991 Object o = it2.next();
992 if (!(o instanceof List))
997 if (!id.equals(l2.get(0)))
999 if (s != null && (l2.get(1) instanceof String) &&
1000 s.equals(l2.get(1)))
1002 else if (b != null && (l2.get(1) instanceof byte[]) &&
1003 Arrays.equals(b, (byte[]) l2.get(1)))
1006 if (match == 0 || (matchAllNames && match != altNames.size()))
1010 if (nameConstraints != null)
1012 byte[] nc = cert.getExtensionValue(NAME_CONSTRAINTS_ID);
1013 if (!Arrays.equals(nameConstraints, nc))
1017 // FIXME check policies.
1018 // FIXME check path-to-names.
1023 public String toString()
1025 StringBuffer str = new StringBuffer(X509CertSelector.class.getName());
1026 String nl = SystemProperties.getProperty("line.separator");
1027 String eol = ";" + nl;
1028 str.append(" {").append(nl);
1030 str.append(" certificate = ").append(cert).append(eol);
1031 if (basicConstraints >= 0)
1032 str.append(" basic constraints = ").append(basicConstraints).append(eol);
1033 if (serialNo != null)
1034 str.append(" serial number = ").append(serialNo).append(eol);
1035 if (certValid != null)
1036 str.append(" valid date = ").append(certValid).append(eol);
1038 str.append(" issuer = ").append(issuer).append(eol);
1039 if (subject != null)
1040 str.append(" subject = ").append(subject).append(eol);
1042 str.append(" signature OID = ").append(sigId).append(eol);
1043 if (subjectKey != null)
1044 str.append(" subject public key = ").append(subjectKey).append(eol);
1045 if (subjectKeyId != null)
1047 str.append(" subject key ID = ");
1048 for (int i = 0; i < subjectKeyId.length; i++)
1050 str.append(Character.forDigit((subjectKeyId[i] & 0xF0) >>> 8, 16));
1051 str.append(Character.forDigit((subjectKeyId[i] & 0x0F), 16));
1052 if (i < subjectKeyId.length - 1)
1057 if (authKeyId != null)
1059 str.append(" authority key ID = ");
1060 for (int i = 0; i < authKeyId.length; i++)
1062 str.append(Character.forDigit((authKeyId[i] & 0xF0) >>> 8, 16));
1063 str.append(Character.forDigit((authKeyId[i] & 0x0F), 16));
1064 if (i < authKeyId.length - 1)
1069 if (keyUsage != null)
1071 str.append(" key usage = ");
1072 for (int i = 0; i < keyUsage.length; i++)
1073 str.append(keyUsage[i] ? '1' : '0');
1076 if (keyPurposeSet != null)
1077 str.append(" key purpose = ").append(keyPurposeSet).append(eol);
1078 if (altNames != null)
1079 str.append(" alternative names = ").append(altNames).append(eol);
1080 if (nameConstraints != null)
1081 str.append(" name constraints = <blob of data>").append(eol);
1082 str.append("}").append(nl);
1083 return str.toString();
1086 public Object clone()
1090 return super.clone();
1092 catch (CloneNotSupportedException shouldNotHappen)
1094 throw new Error(shouldNotHappen);
1099 // -------------------------------------------------------------------------
1101 private static boolean checkOid(int[] oid)
1103 return (oid != null && oid.length > 2 &&
1104 (oid[0] >= 0 && oid[0] <= 2) && (oid[1] >= 0 && oid[1] <= 39));