| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package java.security.cert; |
| |
| import java.io.IOException; |
| import java.math.BigInteger; |
| import java.security.PublicKey; |
| import java.security.cert.CertSelector; |
| import java.security.cert.X509Certificate; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import javax.security.auth.x500.X500Principal; |
| |
| import org.apache.harmony.security.asn1.ASN1OctetString; |
| import org.apache.harmony.security.internal.nls.Messages; |
| import org.apache.harmony.security.x509.AlgorithmIdentifier; |
| import org.apache.harmony.security.x509.CertificatePolicies; |
| import org.apache.harmony.security.x509.GeneralName; |
| import org.apache.harmony.security.x509.GeneralNames; |
| import org.apache.harmony.security.x509.NameConstraints; |
| import org.apache.harmony.security.x509.PolicyInformation; |
| import org.apache.harmony.security.x509.PrivateKeyUsagePeriod; |
| import org.apache.harmony.security.x509.SubjectPublicKeyInfo; |
| |
| |
| |
| /** |
| * A certificate selector ({@code CertSelector} for selecting {@code |
| * X509Certificate}s that match the specified criteria. |
| */ |
| public class X509CertSelector implements CertSelector { |
| |
| // match criteria |
| private X509Certificate certificateEquals; |
| private BigInteger serialNumber; |
| private X500Principal issuer; |
| private X500Principal subject; |
| private byte[] subjectKeyIdentifier; |
| private byte[] authorityKeyIdentifier; |
| private Date certificateValid; |
| private String subjectPublicKeyAlgID; |
| private Date privateKeyValid; |
| private byte[] subjectPublicKey; |
| private boolean[] keyUsage; |
| private Set extendedKeyUsage; |
| private boolean matchAllNames = true; |
| private int pathLen = -1; |
| private List[] subjectAltNames; |
| private NameConstraints nameConstraints; |
| private Set policies; |
| private ArrayList pathToNames; |
| |
| // needed to avoid needless encoding/decoding work |
| private PublicKey subjectPublicKeyImpl; |
| private String issuerName; |
| private byte[] issuerBytes; |
| |
| /** |
| * Creates a new {@code X509CertSelector}. |
| */ |
| public X509CertSelector() {} |
| |
| /** |
| * Sets the certificate that a matching certificate must be equal to. |
| * |
| * @param certificate |
| * the certificate to match, or null to not check this criteria. |
| */ |
| public void setCertificate(X509Certificate certificate) { |
| certificateEquals = certificate; |
| } |
| |
| /** |
| * Returns the certificate that a matching certificate must be equal to. |
| * |
| * @return the certificate to match, or null if this criteria is not |
| * checked. |
| */ |
| public X509Certificate getCertificate() { |
| return certificateEquals; |
| } |
| |
| /** |
| * Sets the serial number that a certificate must match. |
| * |
| * @param serialNumber |
| * the serial number to match, or {@code null} to not check the |
| * serial number. |
| */ |
| public void setSerialNumber(BigInteger serialNumber) { |
| this.serialNumber = serialNumber; |
| } |
| |
| /** |
| * Returns the serial number that a certificate must match. |
| * |
| * @return the serial number to match, or {@code null} if the serial number |
| * is not to be checked. |
| */ |
| public BigInteger getSerialNumber() { |
| return serialNumber; |
| } |
| |
| /** |
| * Sets the issuer that a certificate must match. |
| * |
| * @param issuer |
| * the issuer to match, or {@code null} if the issuer is not to |
| * be checked. |
| */ |
| public void setIssuer(X500Principal issuer) { |
| this.issuer = issuer; |
| this.issuerName = null; |
| this.issuerBytes = null; |
| } |
| |
| /** |
| * Returns the issuer that a certificate must match. |
| * |
| * @return the issuer that a certificate must match, or {@code null} if the |
| * issuer is not to be checked. |
| */ |
| public X500Principal getIssuer() { |
| return issuer; |
| } |
| |
| /** |
| * <b>Do not use</b>, use {@link #getIssuer()} or |
| * {@link #getIssuerAsBytes()} instead. Sets the issuer that a certificate |
| * must match. |
| * |
| * @param issuerName |
| * the issuer in a RFC 2253 format string, or {@code null} to not |
| * check the issuer. |
| * @throws IOException |
| * if parsing the issuer fails. |
| */ |
| public void setIssuer(String issuerName) throws IOException { |
| if (issuerName == null) { |
| this.issuer = null; |
| this.issuerName = null; |
| this.issuerBytes = null; |
| return; |
| } |
| try { |
| this.issuer = new X500Principal(issuerName); |
| this.issuerName = issuerName; |
| this.issuerBytes = null; |
| } catch (IllegalArgumentException e) { |
| throw new IOException(e.getMessage()); |
| } |
| } |
| |
| /** |
| * <b>Do not use</b>, use {@link #getIssuer()} or |
| * {@link #getIssuerAsBytes()} instead. Returns the issuer that a |
| * certificate must match in a RFC 2253 format string. |
| * |
| * @return the issuer in a RFC 2253 format string, or {@code null} if the |
| * issuer is not to be checked. |
| */ |
| public String getIssuerAsString() { |
| if (issuer == null) { |
| return null; |
| } |
| if (issuerName == null) { |
| issuerName = issuer.getName(); |
| } |
| return issuerName; |
| } |
| |
| /** |
| * Sets the issuer that a certificate must match. |
| * |
| * @param issuerDN |
| * the distinguished issuer name in ASN.1 DER encoded format, or |
| * {@code null} to not check the issuer. |
| * @throws IOException |
| * if decoding the issuer fail. |
| */ |
| public void setIssuer(byte[] issuerDN) throws IOException { |
| if (issuerDN == null) { |
| issuer = null; |
| return; |
| } |
| try { |
| issuer = new X500Principal(issuerDN); |
| this.issuerName = null; |
| this.issuerBytes = new byte[issuerDN.length]; |
| System.arraycopy(issuerDN, 0, this.issuerBytes, 0, issuerDN.length); |
| } catch (IllegalArgumentException e) { |
| throw new IOException(e.getMessage()); |
| } |
| } |
| |
| /** |
| * Returns the issuer that a certificate must match. |
| * |
| * @return the distinguished issuer name in ASN.1 DER encoded format, or |
| * {@code null} if the issuer is not to be checked. |
| * @throws IOException |
| * if encoding the issuer fails. |
| */ |
| public byte[] getIssuerAsBytes() throws IOException { |
| if (issuer == null) { |
| return null; |
| } |
| if (issuerBytes == null) { |
| issuerBytes = issuer.getEncoded(); |
| } |
| byte[] result = new byte[issuerBytes.length]; |
| System.arraycopy(issuerBytes, 0, result, 0, issuerBytes.length); |
| return result; |
| } |
| |
| /** |
| * Set the subject that a certificate must match. |
| * |
| * @param subject |
| * the subject distinguished name or {@code null} to not check |
| * the subject. |
| */ |
| public void setSubject(X500Principal subject) { |
| this.subject = subject; |
| } |
| |
| /** |
| * Returns the subject that a certificate must match. |
| * |
| * @return the subject distinguished name, or null if the subject is not to |
| * be checked. |
| */ |
| public X500Principal getSubject() { |
| return subject; |
| } |
| |
| /** |
| * <b>Do not use</b>, use {@link #setSubject(byte[])} or |
| * {@link #setSubject(X500Principal)} instead. Returns the subject that a |
| * certificate must match. |
| * |
| * @param subjectDN |
| * the subject distinguished name in RFC 2253 format or {@code |
| * null} to not check the subject. |
| * @throws IOException |
| * if decoding the subject fails. |
| */ |
| public void setSubject(String subjectDN) throws IOException { |
| if (subjectDN == null) { |
| subject = null; |
| return; |
| } |
| try { |
| subject = new X500Principal(subjectDN); |
| } catch (IllegalArgumentException e) { |
| throw new IOException(e.getMessage()); |
| } |
| } |
| |
| /** |
| * <b>Do not use</b>, use {@link #getSubject()} or |
| * {@link #getSubjectAsBytes()} instead. Returns the subject that a |
| * certificate must match. |
| * |
| * @return the subject distinguished name in RFC 2253 format, or {@code |
| * null} if the subject is not to be checked. |
| */ |
| public String getSubjectAsString() { |
| if (subject == null) { |
| return null; |
| } |
| return subject.getName(); |
| } |
| |
| /** |
| * Sets the subject that a certificate must match. |
| * |
| * @param subjectDN |
| * the subject distinguished name in ASN.1 DER format, or {@code |
| * null} to not check the subject. |
| * @throws IOException |
| * if decoding the subject fails. |
| */ |
| public void setSubject(byte[] subjectDN) throws IOException { |
| if (subjectDN == null) { |
| subject = null; |
| return; |
| } |
| try { |
| subject = new X500Principal(subjectDN); |
| } catch (IllegalArgumentException e) { |
| throw new IOException(e.getMessage()); |
| } |
| } |
| |
| /** |
| * Returns the subject that a certificate must match. |
| * |
| * @return the subject distinguished name in ASN.1 DER format, or {@code |
| * null} if the subject is not to be checked. |
| * @throws IOException |
| * if encoding the subject fails. |
| */ |
| public byte[] getSubjectAsBytes() throws IOException { |
| if (subject == null) { |
| return null; |
| } |
| return subject.getEncoded(); |
| } |
| |
| /** |
| * Sets the criterion for the {@literal SubjectKeyIdentifier} extension. |
| * <p> |
| * The {@code subjectKeyIdentifier} should be a single DER encoded value. |
| * |
| * @param subjectKeyIdentifier |
| * the subject key identifier or {@code null} to disable this |
| * check. |
| */ |
| public void setSubjectKeyIdentifier(byte[] subjectKeyIdentifier) { |
| if (subjectKeyIdentifier == null) { |
| this.subjectKeyIdentifier = null; |
| return; |
| } |
| this.subjectKeyIdentifier = new byte[subjectKeyIdentifier.length]; |
| System.arraycopy(subjectKeyIdentifier, 0, this.subjectKeyIdentifier, 0, |
| subjectKeyIdentifier.length); |
| } |
| |
| /** |
| * Returns the criterion for the {@literal SubjectKeyIdentifier} extension. |
| * |
| * @return the subject key identifier or {@code null} if it is not to be |
| * checked. |
| */ |
| public byte[] getSubjectKeyIdentifier() { |
| if (subjectKeyIdentifier == null) { |
| return null; |
| } |
| byte[] res = new byte[subjectKeyIdentifier.length]; |
| System.arraycopy(subjectKeyIdentifier, 0, res, 0, res.length); |
| return res; |
| } |
| |
| /** |
| * Sets the criterion for the {@literal AuthorityKeyIdentifier} extension. |
| * |
| * @param authorityKeyIdentifier |
| * the authority key identifier, or {@code null} to disable this |
| * check. |
| */ |
| public void setAuthorityKeyIdentifier(byte[] authorityKeyIdentifier) { |
| if (authorityKeyIdentifier == null) { |
| this.authorityKeyIdentifier = null; |
| return; |
| } |
| this.authorityKeyIdentifier = new byte[authorityKeyIdentifier.length]; |
| System.arraycopy(authorityKeyIdentifier, 0, |
| this.authorityKeyIdentifier, 0, |
| authorityKeyIdentifier.length); |
| } |
| |
| /** |
| * Returns the criterion for the {@literal AuthorityKeyIdentifier} |
| * extension. |
| * |
| * @return the authority key identifier, or {@code null} if it is not to be |
| * checked. |
| */ |
| public byte[] getAuthorityKeyIdentifier() { |
| if (authorityKeyIdentifier == null) { |
| return null; |
| } |
| byte[] res = new byte[authorityKeyIdentifier.length]; |
| System.arraycopy(authorityKeyIdentifier, 0, res, 0, res.length); |
| return res; |
| } |
| |
| /** |
| * Sets the criterion for the validity date of the certificate. |
| * <p> |
| * The certificate must be valid at the specified date. |
| * @param certificateValid |
| * the validity date or {@code null} to not check the date. |
| */ |
| public void setCertificateValid(Date certificateValid) { |
| this.certificateValid = (certificateValid == null) |
| ? null |
| : (Date) certificateValid.clone(); |
| } |
| |
| /** |
| * Returns the criterion for the validity date of the certificate. |
| * |
| * @return the validity date or {@code null} if the date is not to be |
| * checked. |
| */ |
| public Date getCertificateValid() { |
| return (certificateValid == null) |
| ? null |
| : (Date) certificateValid.clone(); |
| } |
| |
| /** |
| * Sets the criterion for the validity date of the private key. |
| * <p> |
| * The private key must be valid at the specified date. |
| * |
| * @param privateKeyValid |
| * the validity date or {@code null} to not check the date. |
| */ |
| public void setPrivateKeyValid(Date privateKeyValid) { |
| if (privateKeyValid == null) { |
| this.privateKeyValid = null; |
| return; |
| } |
| this.privateKeyValid = (Date) privateKeyValid.clone(); |
| } |
| |
| /** |
| * Returns the criterion for the validity date of the private key. |
| * <p> |
| * The private key must be valid at the specified date. |
| * |
| * @return the validity date or {@code null} if the date is not to be |
| * checked. |
| */ |
| public Date getPrivateKeyValid() { |
| if (privateKeyValid != null) { |
| return (Date) privateKeyValid.clone(); |
| } |
| return null; |
| } |
| |
| private void checkOID(String oid) throws IOException { |
| int beg = 0; |
| int end = oid.indexOf('.', beg); |
| try { |
| int comp = Integer.parseInt(oid.substring(beg, end)); |
| beg = end + 1; |
| if ((comp < 0) || (comp > 2)) { |
| throw new IOException(Messages.getString("security.56", oid)); //$NON-NLS-1$ |
| } |
| end = oid.indexOf('.', beg); |
| comp = Integer.parseInt(oid.substring(beg, end)); |
| if ((comp < 0) || (comp > 39)) { |
| throw new IOException(Messages.getString("security.56", oid)); //$NON-NLS-1$ |
| } |
| } catch (IndexOutOfBoundsException e) { |
| throw new IOException(Messages.getString("security.56", oid)); //$NON-NLS-1$ |
| } catch (NumberFormatException e) { |
| throw new IOException(Messages.getString("security.56", oid)); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Sets the criterion for the subject public key signature algorithm. |
| * <p> |
| * The certificate must contain a subject public key with the algorithm |
| * specified. |
| * |
| * @param oid |
| * the OID (object identifier) of the signature algorithm or |
| * {@code null} to not check the OID. |
| * @throws IOException |
| * if the specified object identifier is invalid. |
| */ |
| public void setSubjectPublicKeyAlgID(String oid) throws IOException { |
| if (oid == null) { |
| subjectPublicKeyAlgID = null; |
| return; |
| } |
| checkOID(oid); |
| subjectPublicKeyAlgID = oid; |
| } |
| |
| /** |
| * Returns the criterion for the subject public key signature algorithm. |
| * |
| * @return the OID (object identifier) or the signature algorithm or {@code |
| * null} if it's not to be checked. |
| */ |
| public String getSubjectPublicKeyAlgID() { |
| return subjectPublicKeyAlgID; |
| } |
| |
| /** |
| * Sets the criterion for the subject public key. |
| * |
| * @param key |
| * the subject public key or {@code null} to not check the key. |
| */ |
| public void setSubjectPublicKey(PublicKey key) { |
| subjectPublicKey = (key == null) ? null : key.getEncoded(); |
| subjectPublicKeyImpl = key; |
| } |
| |
| /** |
| * Sets the criterion for the subject public key. |
| * |
| * @param key |
| * the subject public key in ASN.1 DER encoded format or {@code null} to |
| * not check the key. |
| * @throws IOException |
| * if decoding the the public key fails. |
| */ |
| public void setSubjectPublicKey(byte[] key) throws IOException { |
| if (key == null) { |
| subjectPublicKey = null; |
| subjectPublicKeyImpl = null; |
| return; |
| } |
| subjectPublicKey = new byte[key.length]; |
| System.arraycopy(key, 0, subjectPublicKey, 0, key.length); |
| subjectPublicKeyImpl = |
| ((SubjectPublicKeyInfo) SubjectPublicKeyInfo.ASN1.decode(key)) |
| .getPublicKey(); |
| } |
| |
| /** |
| * Returns the criterion for the subject public key. |
| * |
| * @return the subject public key or {@code null} if the key is not to be |
| * checked. |
| */ |
| public PublicKey getSubjectPublicKey() { |
| return subjectPublicKeyImpl; |
| } |
| |
| /** |
| * Sets the criterion for the {@literal KeyUsage} extension. |
| * |
| * @param keyUsage |
| * the boolean array in the format as returned by |
| * {@link X509Certificate#getKeyUsage()}, or {@code null} to not |
| * check the key usage. |
| */ |
| public void setKeyUsage(boolean[] keyUsage) { |
| if (keyUsage == null) { |
| this.keyUsage = null; |
| return; |
| } |
| this.keyUsage = new boolean[keyUsage.length]; |
| System.arraycopy(keyUsage, 0, this.keyUsage, 0, keyUsage.length); |
| } |
| |
| /** |
| * Returns the criterion for the {@literal KeyUsage} extension. |
| * |
| * @return the boolean array in the format as returned by |
| * {@link X509Certificate#getKeyUsage()}, or {@code null} if the key |
| * usage is not to be checked. |
| */ |
| public boolean[] getKeyUsage() { |
| if (keyUsage == null) { |
| return null; |
| } |
| boolean[] result = new boolean[keyUsage.length]; |
| System.arraycopy(keyUsage, 0, result, 0, keyUsage.length); |
| return result; |
| } |
| |
| /** |
| * Sets the criterion for the {@literal ExtendedKeyUsage} extension. |
| * |
| * @param keyUsage |
| * the set of key usage OIDs, or {@code null} to not check it. |
| * @throws IOException |
| * if one of the OIDs is invalid. |
| */ |
| public void setExtendedKeyUsage(Set<String> keyUsage) |
| throws IOException { |
| extendedKeyUsage = null; |
| if ((keyUsage == null) || (keyUsage.size() == 0)) { |
| return; |
| } |
| HashSet key_u = new HashSet(); |
| Iterator it = keyUsage.iterator(); |
| while (it.hasNext()) { |
| String usage = (String) it.next(); |
| checkOID(usage); |
| key_u.add(usage); |
| } |
| extendedKeyUsage = Collections.unmodifiableSet(key_u); |
| } |
| |
| /** |
| * Returns the criterion for the {@literal ExtendedKeyUsage} extension. |
| * |
| * @return the set of key usage OIDs, or {@code null} if it's not to be |
| * checked. |
| */ |
| public Set<String> getExtendedKeyUsage() { |
| return extendedKeyUsage; |
| } |
| |
| /** |
| * Sets the flag for the matching behavior for subject alternative names. |
| * <p> |
| * The flag indicates whether a certificate must contain all or at least one |
| * of the subject alternative names specified by {@link |
| * #setSubjectAlternativeNames} or {@link #addSubjectAlternativeName}. |
| * |
| * @param matchAllNames |
| * {@code true} if a certificate must contain all of the |
| * specified subject alternative names, otherwise {@code false}. |
| */ |
| public void setMatchAllSubjectAltNames(boolean matchAllNames) { |
| this.matchAllNames = matchAllNames; |
| } |
| |
| /** |
| * Returns the flag for the matching behavior for subject alternative names. |
| * <p> |
| * The flag indicates whether a certificate must contain all or at least one |
| * of the subject alternative names specified by {@link |
| * #setSubjectAlternativeNames} or {@link #addSubjectAlternativeName}. |
| * |
| * @return {@code true} if a certificate must contain all of the specified |
| * subject alternative names, otherwise {@code false}. |
| */ |
| public boolean getMatchAllSubjectAltNames() { |
| return matchAllNames; |
| } |
| |
| /** |
| * Sets the criterion for subject alternative names. |
| * <p> |
| * the certificate must contain all or at least one of the specified subject |
| * alternative names. The behavior is specified by |
| * {@link #getMatchAllSubjectAltNames}. |
| * <p> |
| * The specified parameter {@code names} is a collection with an entry for |
| * each name to be included in the criterion. The name is specified as a |
| * {@code List}, the first entry must be an {@code Integer} specifying the |
| * name type (0-8), the second entry must be a {@code String} or a byte |
| * array specifying the name (in string or ASN.1 DER encoded form) |
| * |
| * @param names |
| * the names collection or {@code null} to not perform this check. |
| * @throws IOException |
| * if the decoding of a name fails. |
| */ |
| public void setSubjectAlternativeNames(Collection<List<?>> names) |
| throws IOException { |
| subjectAltNames = null; |
| if ((names == null) || (names.size() == 0)) { |
| return; |
| } |
| Iterator it = names.iterator(); |
| while (it.hasNext()) { |
| List name = (List) it.next(); |
| int tag = ((Integer) name.get(0)).intValue(); |
| Object value = name.get(1); |
| if (value instanceof String) { |
| addSubjectAlternativeName(tag, (String) value); |
| } else if (value instanceof byte[]) { |
| addSubjectAlternativeName(tag, (byte[]) value); |
| } else { |
| throw new IOException(Messages.getString("security.57")); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| /** |
| * Adds a subject alternative name to the respective criterion. |
| * |
| * @param tag |
| * the type of the name |
| * @param name |
| * the name in string format. |
| * @throws IOException |
| * if parsing the name fails. |
| */ |
| public void addSubjectAlternativeName(int tag, String name) |
| throws IOException { |
| GeneralName alt_name = new GeneralName(tag, name); |
| // create only if there was not any errors |
| if (subjectAltNames == null) { |
| subjectAltNames = new ArrayList[9]; |
| } |
| if (subjectAltNames[tag] == null) { |
| subjectAltNames[tag] = new ArrayList(); |
| } |
| subjectAltNames[tag].add(alt_name); |
| } |
| |
| /** |
| * Adds a subject alternative name to the respective criterion. |
| * |
| * @param tag |
| * the type of the name. |
| * @param name |
| * the name in ASN.1 DER encoded form. |
| * @throws IOException |
| * if the decoding of the name fails. |
| */ |
| public void addSubjectAlternativeName(int tag, byte[] name) |
| throws IOException { |
| GeneralName alt_name = new GeneralName(tag, name); |
| // create only if there was not any errors |
| if (subjectAltNames == null) { |
| subjectAltNames = new ArrayList[9]; |
| } |
| if (subjectAltNames[tag] == null) { |
| subjectAltNames[tag] = new ArrayList(); |
| } |
| subjectAltNames[tag].add(alt_name); |
| } |
| |
| /** |
| * Returns the criterion for subject alternative names. |
| * <p> |
| * the certificate must contain all or at least one of the specified subject |
| * alternative names. The behavior is specified by |
| * {@link #getMatchAllSubjectAltNames}. |
| * <p> |
| * The subject alternative names is a collection with an entry for each name |
| * included in the criterion. The name is specified as a {@code List}, the |
| * first entry is an {@code Integer} specifying the name type (0-8), the |
| * second entry is byte array specifying the name in ASN.1 DER encoded form) |
| * |
| * @return the names collection or {@code null} if none specified. |
| */ |
| public Collection<List<?>> getSubjectAlternativeNames() { |
| if (subjectAltNames == null) { |
| return null; |
| } |
| ArrayList result = new ArrayList(); |
| for (int tag=0; tag<9; tag++) { |
| if (subjectAltNames[tag] != null) { |
| for (int name=0; name<subjectAltNames[tag].size(); name++) { |
| Object neim = subjectAltNames[tag].get(name); |
| if (neim instanceof byte[]) { |
| byte[] arr_neim = (byte[]) neim; |
| neim = new byte[arr_neim.length]; |
| System.arraycopy(arr_neim, 0, neim, 0, arr_neim.length); |
| } |
| List list = new ArrayList(2); |
| list.add(Integer.valueOf(tag)); // android-changed |
| list.add(neim); |
| result.add(list); |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Sets the criterion for the name constraints. |
| * <p> |
| * The certificate must constraint subject and subject alternative names |
| * that match the specified name constraints. |
| * <p> |
| * The name constraints in ASN.1: |
| * |
| * <pre> |
| * NameConstraints ::= SEQUENCE { |
| * permittedSubtrees [0] GeneralSubtrees OPTIONAL, |
| * excludedSubtrees [1] GeneralSubtrees OPTIONAL } |
| * |
| * GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree |
| * |
| * GeneralSubtree ::= SEQUENCE { |
| * base GeneralName, |
| * minimum [0] BaseDistance DEFAULT 0, |
| * maximum [1] BaseDistance OPTIONAL } |
| * |
| * BaseDistance ::= INTEGER (0..MAX) |
| * |
| * GeneralName ::= CHOICE { |
| * otherName [0] OtherName, |
| * rfc822Name [1] IA5String, |
| * dNSName [2] IA5String, |
| * x400Address [3] ORAddress, |
| * directoryName [4] Name, |
| * ediPartyName [5] EDIPartyName, |
| * uniformResourceIdentifier [6] IA5String, |
| * iPAddress [7] OCTET STRING, |
| * registeredID [8] OBJECT IDENTIFIER} |
| * |
| * </pre> |
| * |
| * @param bytes |
| * the name constraints in ASN.1 DER encoded format, or null to |
| * not check any constraints. |
| * @throws IOException |
| * if decoding the name constraints fail. |
| */ |
| public void setNameConstraints(byte[] bytes) throws IOException { |
| this.nameConstraints = (bytes == null) |
| ? null |
| : (NameConstraints) NameConstraints.ASN1.decode(bytes); |
| } |
| |
| /** |
| * Returns the criterion for the name constraints. |
| * |
| * @return the name constraints or {@code null} if none specified. |
| * @see #setNameConstraints |
| */ |
| public byte[] getNameConstraints() { |
| return (nameConstraints == null) |
| ? null |
| : nameConstraints.getEncoded(); |
| } |
| |
| /** |
| * Sets the criterion for the basic constraints extension. |
| * <p> |
| * A value greater than or equal to zero indicates that a certificate must |
| * include a basic constraints extension with a path length of a least that |
| * value. A value of {@code -2} indicates that only end-entity certificates |
| * are accepted. A value of {@code -1} indicates that no check is done. |
| * |
| * @param pathLen |
| * the value specifying the criterion. |
| * @throws IllegalArgumentException |
| * if {@code pathLen} is less than {@code -2}. |
| */ |
| public void setBasicConstraints(int pathLen) { |
| if (pathLen < -2) { |
| throw new IllegalArgumentException(Messages.getString("security.58")); //$NON-NLS-1$ |
| } |
| this.pathLen = pathLen; |
| } |
| |
| /** |
| * Returns the criterion for the basic constraints extension. |
| * <p> |
| * A value greater than or equal to zero indicates that a certificate must |
| * include a basic constraints extension with a path length of a least that |
| * value. A value of {@code -2} indicates that only end-entity certificates |
| * are accepted. A value of {@code -1} indicates that no check is done. |
| * |
| * @return the value of the criterion. |
| */ |
| public int getBasicConstraints() { |
| return pathLen; |
| } |
| |
| /** |
| * Sets the criterion for the policy constraint. |
| * <p> |
| * The certificate must have at least one of the specified certificate |
| * policy extensions. For an empty set the certificate must have at least |
| * some policies in its policy extension. |
| * |
| * @param policies |
| * the certificate policy OIDs, an empty set, or {@code null} to |
| * not perform this check. |
| * @throws IOException |
| * if parsing the specified OIDs fails. |
| */ |
| public void setPolicy(Set<String> policies) throws IOException { |
| if (policies == null) { |
| this.policies = null; |
| return; |
| } |
| HashSet pols = new HashSet(policies.size()); |
| Iterator it = policies.iterator(); |
| while (it.hasNext()) { |
| String certPolicyId = (String) it.next(); |
| checkOID(certPolicyId); |
| pols.add(certPolicyId); |
| } |
| this.policies = Collections.unmodifiableSet(pols); |
| } |
| |
| /** |
| * Returns the criterion for the policy constraint. |
| * <p> |
| * The certificate must have at least one of the certificate policy |
| * extensions. For an empty set the certificate must have at least some |
| * policies in its policy extension. |
| * |
| * @return the certificate policy OIDs, an empty set, or {@code null} if not |
| * to be checked. |
| */ |
| public Set<String> getPolicy() { |
| return policies; |
| } |
| |
| /** |
| * Sets the criterion for the pathToNames constraint. |
| * <p> |
| * This allows to specify the complete set of names, a certificate's name |
| * constraints must permit. |
| * <p> |
| * The specified parameter {@code names} is a collection with an entry for |
| * each name to be included in the criterion. The name is specified as a |
| * {@code List}, the first entry must be an {@code Integer} specifying the |
| * name type (0-8), the second entry must be a {@code String} or a byte |
| * array specifying the name (in string or ASN.1 DER encoded form) |
| * |
| * @param names |
| * the names collection or {@code null} to not perform this |
| * check. |
| * @throws IOException |
| * if decoding fails. |
| */ |
| public void setPathToNames(Collection<List<?>> names) |
| throws IOException { |
| pathToNames = null; |
| if ((names == null) || (names.size() == 0)) { |
| return; |
| } |
| Iterator it = names.iterator(); |
| while (it.hasNext()) { |
| List name = (List) it.next(); |
| int tag = ((Integer) name.get(0)).intValue(); |
| Object value = name.get(1); |
| if (value instanceof String) { |
| addPathToName(tag, (String) value); |
| } else if (value instanceof byte[]) { |
| addPathToName(tag, (byte[]) value); |
| } else { |
| throw new IOException(Messages.getString("security.57")); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| /** |
| * Adds a {@literal "pathToName"} to the respective criterion. |
| * |
| * @param type |
| * the type of the name. |
| * @param name |
| * the name in string format. |
| * @throws IOException |
| * if parsing fails. |
| * @see #setPathToNames |
| */ |
| public void addPathToName(int type, String name) throws IOException { |
| GeneralName path_name = new GeneralName(type, name); |
| // create only if there was not any errors |
| if (pathToNames == null) { |
| pathToNames = new ArrayList(); |
| } |
| pathToNames.add(path_name); |
| } |
| |
| /** |
| * Adds a {@literal "pathToName"} to the respective criterion. |
| * |
| * @param type |
| * the type of the name |
| * @param name |
| * the name in ASN.1 DER encoded form. |
| * @throws IOException |
| * if decoding fails. |
| * @see #setPathToNames |
| */ |
| public void addPathToName(int type, byte[] name) throws IOException { |
| GeneralName path_name= new GeneralName(type, name); |
| // create only if there was not any errors |
| if (pathToNames == null) { |
| pathToNames = new ArrayList(); |
| } |
| pathToNames.add(path_name); |
| } |
| |
| /** |
| * Returns the criterion for the pathToNames constraint. |
| * <p> |
| * The constraint is a collection with an entry for each name to be included |
| * in the criterion. The name is specified as a {@code List}, the first |
| * entry is an {@code Integer} specifying the name type (0-8), the second |
| * entry is a byte array specifying the name in ASN.1 DER encoded form. |
| * |
| * @return the pathToNames constraint or {@code null} if none specified. |
| */ |
| public Collection<List<?>> getPathToNames() { |
| if (pathToNames == null) { |
| return null; |
| } |
| ArrayList result = new ArrayList(); |
| Iterator it = pathToNames.iterator(); |
| while (it.hasNext()) { |
| GeneralName name = (GeneralName) it.next(); |
| result.add(name.getAsList()); |
| } |
| return result; |
| } |
| |
| /** |
| * Returns a string representation of this {@code X509CertSelector} |
| * instance. |
| * |
| * @return a string representation of this {@code X509CertSelector} |
| * instance. |
| */ |
| public String toString() { |
| // For convenient reading of the string representation |
| // all of the fields named according to the rfc 3280 |
| // (http://www.ietf.org/rfc/rfc3280.txt). |
| |
| StringBuilder result = new StringBuilder(); |
| result.append("X509CertSelector: \n["); //$NON-NLS-1$ |
| if (this.certificateEquals != null) { |
| result.append("\n certificateEquals: " + certificateEquals); //$NON-NLS-1$ |
| } |
| if (this.serialNumber != null) { |
| //FIXME: needs DRL's BigInteger.toString implementation |
| //result.append("\n serialNumber: " + serialNumber); |
| } |
| if (this.issuer != null) { |
| result.append("\n issuer: " + issuer); //$NON-NLS-1$ |
| } |
| if (this.subject != null) { |
| result.append("\n subject: " + subject); //$NON-NLS-1$ |
| } |
| if (this.subjectKeyIdentifier != null) { |
| result.append("\n subjectKeyIdentifier: " //$NON-NLS-1$ |
| + getBytesAsString(subjectKeyIdentifier)); |
| } |
| if (this.authorityKeyIdentifier != null) { |
| result.append("\n authorityKeyIdentifier: " //$NON-NLS-1$ |
| + getBytesAsString(authorityKeyIdentifier)); |
| } |
| if (this.certificateValid != null) { |
| result.append("\n certificateValid: " + certificateValid); //$NON-NLS-1$ |
| } |
| if (this.subjectPublicKeyAlgID != null) { |
| result.append("\n subjectPublicKeyAlgID: " //$NON-NLS-1$ |
| + subjectPublicKeyAlgID); |
| } |
| if (this.privateKeyValid != null) { |
| result.append("\n privateKeyValid: " + privateKeyValid); //$NON-NLS-1$ |
| } |
| if (this.subjectPublicKey != null) { |
| result.append("\n subjectPublicKey: " //$NON-NLS-1$ |
| + getBytesAsString(subjectPublicKey)); |
| } |
| if (this.keyUsage != null) { |
| result.append("\n keyUsage: \n ["); //$NON-NLS-1$ |
| String[] kuNames = new String[] { |
| "digitalSignature", "nonRepudiation", "keyEncipherment", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| "dataEncipherment", "keyAgreement", "keyCertSign", "cRLSign", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| "encipherOnly", "decipherOnly" //$NON-NLS-1$ //$NON-NLS-2$ |
| }; |
| for (int i=0; i<9; i++) { |
| if (keyUsage[i]) { |
| result.append("\n " + kuNames[i]); //$NON-NLS-1$ |
| } |
| } |
| result.append("\n ]"); //$NON-NLS-1$ |
| } |
| if (this.extendedKeyUsage != null) { |
| result.append("\n extendedKeyUsage: " //$NON-NLS-1$ |
| + extendedKeyUsage.toString()); |
| } |
| result.append("\n matchAllNames: " + matchAllNames); //$NON-NLS-1$ |
| result.append("\n pathLen: " + pathLen); //$NON-NLS-1$ |
| if (this.subjectAltNames != null) { |
| result.append("\n subjectAltNames: \n ["); //$NON-NLS-1$ |
| for (int i=0; i<9; i++) { |
| List names = this.subjectAltNames[i]; |
| if (names != null) { |
| int size = names.size(); |
| for (int j=0; j<size; j++) { |
| result.append("\n " //$NON-NLS-1$ |
| + ((GeneralName)names.get(j)).toString()); |
| } |
| } |
| } |
| result.append("\n ]"); //$NON-NLS-1$ |
| } |
| if (this.nameConstraints != null) { |
| } |
| if (this.policies != null) { |
| result.append("\n policies: " + policies.toString()); //$NON-NLS-1$ |
| } |
| if (this.pathToNames != null) { |
| result.append("\n pathToNames: \n ["); //$NON-NLS-1$ |
| int size = pathToNames.size(); |
| for (int i = 0; i < size; i++) { |
| result.append("\n " //$NON-NLS-1$ |
| + ((GeneralName)pathToNames.get(i)).toString()); |
| } |
| } |
| result.append("\n]"); //$NON-NLS-1$ |
| return result.toString(); |
| } |
| |
| private String getBytesAsString(byte[] data) { |
| String result = ""; //$NON-NLS-1$ |
| for (int i=0; i<data.length; i++) { |
| String tail = Integer.toHexString(0x00ff & data[i]); |
| if (tail.length() == 1) { |
| tail = "0" + tail; //$NON-NLS-1$ |
| } |
| result += tail + " "; //$NON-NLS-1$ |
| } |
| return result; |
| } |
| |
| private byte[] getExtensionValue(X509Certificate cert, String oid) { |
| try { |
| byte[] bytes = cert.getExtensionValue(oid); |
| if (bytes == null) { |
| return null; |
| } |
| return (byte[]) ASN1OctetString.getInstance().decode(bytes); |
| } catch (IOException e) { |
| return null; |
| } |
| } |
| |
| /** |
| * Returns whether the specified certificate matches all the criteria |
| * collected in this instance. |
| * |
| * @param certificate |
| * the certificate to check. |
| * @return {@code true} if the certificate matches all the criteria, |
| * otherwise {@code false}. |
| */ |
| public boolean match(Certificate certificate) { |
| if (! (certificate instanceof X509Certificate)) { |
| return false; |
| } |
| |
| X509Certificate cert = (X509Certificate) certificate; |
| if ((certificateEquals != null) && |
| !certificateEquals.equals(cert)) { |
| return false; |
| } |
| if ((serialNumber != null) && |
| !serialNumber.equals(cert.getSerialNumber())) { |
| return false; |
| } |
| if ((issuer != null) && |
| !issuer.equals(cert.getIssuerX500Principal())) { |
| return false; |
| } |
| if ((subject != null) && |
| !subject.equals(cert.getSubjectX500Principal())) { |
| return false; |
| } |
| if ((subjectKeyIdentifier != null) && |
| !Arrays.equals(subjectKeyIdentifier, |
| // Here and later all of the extension OIDs |
| // are taken from rfc 3280 (http://www.ietf.org/rfc/rfc3280.txt) |
| getExtensionValue(cert, "2.5.29.14"))) { //$NON-NLS-1$ |
| return false; |
| } |
| if ((authorityKeyIdentifier != null) && |
| !Arrays.equals(authorityKeyIdentifier, |
| getExtensionValue(cert, "2.5.29.35"))) { //$NON-NLS-1$ |
| return false; |
| } |
| if (certificateValid != null) { |
| try { |
| cert.checkValidity(certificateValid); |
| } catch(CertificateExpiredException e) { |
| return false; |
| } catch(CertificateNotYetValidException e) { |
| return false; |
| } |
| } |
| if (privateKeyValid != null) { |
| try { |
| byte[] bytes = getExtensionValue(cert, "2.5.29.16"); //$NON-NLS-1$ |
| if (bytes == null) { |
| return false; |
| } |
| PrivateKeyUsagePeriod pkup = (PrivateKeyUsagePeriod) |
| PrivateKeyUsagePeriod.ASN1.decode(bytes); |
| Date notBefore = pkup.getNotBefore(); |
| Date notAfter = pkup.getNotAfter(); |
| if ((notBefore == null) && (notAfter == null)) { |
| return false; |
| } |
| if ((notBefore != null) |
| && notBefore.compareTo(privateKeyValid) > 0) { |
| return false; |
| } |
| if ((notAfter != null) |
| && notAfter.compareTo(privateKeyValid) < 0) { |
| return false; |
| } |
| } catch (IOException e) { |
| return false; |
| } |
| } |
| if (subjectPublicKeyAlgID != null) { |
| try { |
| byte[] encoding = cert.getPublicKey().getEncoded(); |
| AlgorithmIdentifier ai = ((SubjectPublicKeyInfo) |
| SubjectPublicKeyInfo.ASN1.decode(encoding)) |
| .getAlgorithmIdentifier(); |
| if (!subjectPublicKeyAlgID.equals(ai.getAlgorithm())) { |
| return false; |
| } |
| } catch (IOException e) { |
| e.printStackTrace(); |
| return false; |
| } |
| } |
| if (subjectPublicKey != null) { |
| if (!Arrays.equals(subjectPublicKey, |
| cert.getPublicKey().getEncoded())) { |
| return false; |
| } |
| } |
| if (keyUsage != null) { |
| boolean[] ku = cert.getKeyUsage(); |
| if (ku != null) { |
| int i = 0; |
| int min_length = (ku.length < keyUsage.length) ? ku.length |
| : keyUsage.length; |
| for (; i < min_length; i++) { |
| if (keyUsage[i] && !ku[i]) { |
| // the specified keyUsage allows, |
| // but certificate does not. |
| return false; |
| } |
| } |
| for (; i<keyUsage.length; i++) { |
| if (keyUsage[i]) { |
| return false; |
| } |
| } |
| } |
| } |
| if (extendedKeyUsage != null) { |
| try { |
| List keyUsage = cert.getExtendedKeyUsage(); |
| if (keyUsage != null) { |
| if (!keyUsage.containsAll(extendedKeyUsage)) { |
| return false; |
| } |
| } |
| } catch (CertificateParsingException e) { |
| return false; |
| } |
| } |
| if (pathLen != -1) { |
| int p_len = cert.getBasicConstraints(); |
| if ((pathLen < 0) && (p_len >= 0)) { |
| // need end-entity but got CA |
| return false; |
| } |
| if ((pathLen > 0) && (pathLen > p_len)) { |
| // allowed _pathLen is small |
| return false; |
| } |
| } |
| if (subjectAltNames != null) { |
| PASSED: |
| try { |
| byte[] bytes = getExtensionValue(cert, "2.5.29.17"); //$NON-NLS-1$ |
| if (bytes == null) { |
| return false; |
| } |
| List sans = ((GeneralNames) GeneralNames.ASN1.decode(bytes)) |
| .getNames(); |
| if ((sans == null) || (sans.size() == 0)) { |
| return false; |
| } |
| boolean[][] map = new boolean[9][]; |
| // initialize the check map |
| for (int i=0; i<9; i++) { |
| map[i] = (subjectAltNames[i] == null) |
| ? new boolean[0] |
| : new boolean[subjectAltNames[i].size()]; |
| } |
| Iterator it = sans.iterator(); |
| while (it.hasNext()) { |
| GeneralName name = (GeneralName) it.next(); |
| int tag = name.getTag(); |
| for (int i=0; i<map[tag].length; i++) { |
| if (((GeneralName) subjectAltNames[tag].get(i)) |
| .equals(name)) { |
| if (!matchAllNames) { |
| break PASSED; |
| } |
| map[tag][i] = true; |
| } |
| } |
| } |
| if (!matchAllNames) { |
| // there was not any match |
| return false; |
| } |
| // else check the map |
| for (int tag=0; tag<9; tag++) { |
| for (int name=0; name<map[tag].length; name++) { |
| if (!map[tag][name]) { |
| return false; |
| } |
| } |
| } |
| } catch (IOException e) { |
| e.printStackTrace(); |
| return false; |
| } |
| } |
| if (nameConstraints != null) { |
| if (!nameConstraints.isAcceptable(cert)) { |
| return false; |
| } |
| } |
| if (policies != null) { |
| byte[] bytes = getExtensionValue(cert, "2.5.29.32"); //$NON-NLS-1$ |
| if (bytes == null) { |
| return false; |
| } |
| if (policies.size() == 0) { |
| // if certificate has such extension than it has at least |
| // one policy in it. |
| return true; |
| } |
| PASSED: |
| try { |
| List policyInformations = ((CertificatePolicies) |
| CertificatePolicies.ASN1.decode(bytes)) |
| .getPolicyInformations(); |
| Iterator it = policyInformations.iterator(); |
| while (it.hasNext()) { |
| if (policies.contains(((PolicyInformation) it.next()) |
| .getPolicyIdentifier())) { |
| break PASSED; |
| } |
| } |
| return false; |
| } catch (IOException e) { |
| // the extension is invalid |
| return false; |
| } |
| } |
| if (pathToNames != null) { |
| byte[] bytes = getExtensionValue(cert, "2.5.29.30"); //$NON-NLS-1$ |
| if (bytes != null) { |
| NameConstraints nameConstraints; |
| try { |
| nameConstraints = |
| (NameConstraints) NameConstraints.ASN1.decode(bytes); |
| } catch (IOException e) { |
| // the extension is invalid; |
| return false; |
| } |
| if (!nameConstraints.isAcceptable(pathToNames)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Clones this {@code X509CertSelector} instance. |
| * |
| * @return the cloned instance. |
| */ |
| public Object clone() { |
| X509CertSelector result; |
| |
| try { |
| result = (X509CertSelector) super.clone(); |
| } catch (CloneNotSupportedException e) { |
| return null; |
| } |
| |
| if (this.subjectKeyIdentifier != null) { |
| result.subjectKeyIdentifier = |
| new byte[this.subjectKeyIdentifier.length]; |
| System.arraycopy(this.subjectKeyIdentifier, 0, |
| result.subjectKeyIdentifier, 0, |
| this.subjectKeyIdentifier.length); |
| } |
| if (this.authorityKeyIdentifier != null) { |
| result.authorityKeyIdentifier = |
| new byte[this.authorityKeyIdentifier.length]; |
| System.arraycopy(this.authorityKeyIdentifier, 0, |
| result.authorityKeyIdentifier, 0, |
| this.authorityKeyIdentifier.length); |
| } |
| if (this.subjectPublicKey != null) { |
| result.subjectPublicKey = new byte[this.subjectPublicKey.length]; |
| System.arraycopy(this.subjectPublicKey, 0, result.subjectPublicKey, |
| 0, this.subjectPublicKey.length); |
| } |
| if (this.keyUsage != null) { |
| result.keyUsage = new boolean[this.keyUsage.length]; |
| System.arraycopy(this.keyUsage, 0, result.keyUsage, 0, |
| this.keyUsage.length); |
| } |
| result.extendedKeyUsage = (this.extendedKeyUsage == null) |
| ? null |
| : new HashSet(this.extendedKeyUsage); |
| if (this.subjectAltNames != null) { |
| result.subjectAltNames = new ArrayList[9]; |
| for (int i=0; i<9; i++) { |
| if (this.subjectAltNames[i] != null) { |
| result.subjectAltNames[i] = |
| new ArrayList(this.subjectAltNames[i]); |
| } |
| } |
| } |
| result.policies = (this.policies == null) |
| ? null |
| : new HashSet(this.policies); |
| result.pathToNames = (this.pathToNames == null) |
| ? null |
| : new ArrayList(this.pathToNames); |
| return result; |
| } |
| } |