blob: 0460fd696eaa8efdaa47ca1d044a06d251547b5b [file] [log] [blame]
/*
* 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;
}
}