/*
* Copyright 2006-2009 The Apache Software Foundation.
*
* Licensed 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.
*
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
*/
package javax.xml.crypto.test.dsig;
import java.io.IOException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.CertSelector;
import java.security.cert.X509Certificate;
import java.security.cert.X509CertSelector;
import java.util.*;
import javax.security.auth.x500.X500Principal;
import javax.xml.crypto.*;
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dsig.keyinfo.*;
import org.jcp.xml.dsig.internal.dom.DOMRetrievalMethod;
/**
* A KeySelector
that returns {@link PublicKey}s. If the
* selector is created as trusted, it only returns public keys of trusted
* {@link X509Certificate}s stored in a {@link KeyStore}. Otherwise, it
* returns trusted or untrusted public keys (it doesn't care as long
* as it finds one).
*
*
This KeySelector
uses the specified KeyStore
* to find a trusted X509Certificate
that matches information
* specified in the {@link KeyInfo} passed to the {@link #select} method.
* The public key from the first match is returned. If no match,
* null
is returned. See the select
method for more
* information.
*
*
NOTE!: This X509KeySelector requires J2SE 1.4 because it uses the
* java.security.cert.X509CertSelector & javax.security.auth.x500.X500Principal
* classes to parse X.500 DNs and match on certificate attributes.
*
* @author Sean Mullan
*/
public class X509KeySelector extends KeySelector {
private KeyStore ks;
private boolean trusted = true;
/**
* Creates a trusted X509KeySelector
.
*
* @param keyStore the keystore
* @throws KeyStoreException if the keystore has not been initialized
* @throws NullPointerException if keyStore
is
* null
*/
public X509KeySelector(KeyStore keyStore) throws KeyStoreException {
this(keyStore, true);
}
public X509KeySelector(KeyStore keyStore, boolean trusted)
throws KeyStoreException {
if (keyStore == null) {
throw new NullPointerException("keyStore is null");
}
this.trusted = trusted;
this.ks = keyStore;
// test to see if KeyStore has been initialized
this.ks.size();
}
/**
* Finds a key from the keystore satisfying the specified constraints.
*
*
This method compares data contained in {@link KeyInfo} entries
* with information stored in the KeyStore
. The implementation
* iterates over the KeyInfo types and returns the first {@link PublicKey}
* of an X509Certificate in the keystore that is compatible with the
* specified AlgorithmMethod according to the following rules for each
* keyinfo type:
*
* X509Data X509Certificate: if it contains a KeyUsage
* extension that asserts the digitalSignature
bit and
* matches an X509Certificate
in the KeyStore
.
* X509Data X509IssuerSerial: if the serial number and issuer DN match an
* X509Certificate
in the KeyStore
.
* X509Data X509SubjectName: if the subject DN matches an
* X509Certificate
in the KeyStore
.
* X509Data X509SKI: if the subject key identifier matches an
* X509Certificate
in the KeyStore
.
* KeyName: if the keyname matches an alias in the KeyStore
.
* RetrievalMethod: supports rawX509Certificate and X509Data types. If
* rawX509Certificate type, it must match an X509Certificate
* in the KeyStore
.
*
* @param keyInfo a KeyInfo
(may be null
)
* @param purpose the key's purpose
* @param method the algorithm method that this key is to be used for.
* Only keys that are compatible with the algorithm and meet the
* constraints of the specified algorithm should be returned.
* @param an XMLCryptoContext
that may contain additional
* useful information for finding an appropriate key
* @return a key selector result
* @throws KeySelectorException if an exceptional condition occurs while
* attempting to find a key. Note that an inability to find a key is not
* considered an exception (null
should be
* returned in that case). However, an error condition (ex: network
* communications failure) that prevented the KeySelector
* from finding a potential key should be considered an exception.
* @throws ClassCastException if the data type of method
* is not supported by this key selector
*/
public KeySelectorResult select(KeyInfo keyInfo,
KeySelector.Purpose purpose, AlgorithmMethod method,
XMLCryptoContext context) throws KeySelectorException {
SignatureMethod sm = (SignatureMethod) method;
try {
// return null if keyinfo is null or keystore is empty
if (keyInfo == null || ks.size() == 0) {
return new SimpleKeySelectorResult(null);
}
// Iterate through KeyInfo types
Iterator i = keyInfo.getContent().iterator();
while (i.hasNext()) {
XMLStructure kiType = (XMLStructure) i.next();
// check X509Data
if (kiType instanceof X509Data) {
X509Data xd = (X509Data) kiType;
KeySelectorResult ksr = x509DataSelect(xd, sm);
if (ksr != null) {
return ksr;
}
// check KeyName
} else if (kiType instanceof KeyName) {
KeyName kn = (KeyName) kiType;
Certificate cert = ks.getCertificate(kn.getName());
if (cert != null && algEquals(sm.getAlgorithm(),
cert.getPublicKey().getAlgorithm())) {
return new SimpleKeySelectorResult(cert.getPublicKey());
}
// check RetrievalMethod
} else if (kiType instanceof RetrievalMethod) {
RetrievalMethod rm = (RetrievalMethod) kiType;
try {
KeySelectorResult ksr = null;
if (rm.getType().equals
(X509Data.RAW_X509_CERTIFICATE_TYPE)) {
OctetStreamData data = (OctetStreamData)
rm.dereference(context);
CertificateFactory cf =
CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)
cf.generateCertificate(data.getOctetStream());
ksr = certSelect(cert, sm);
} else if (rm.getType().equals(X509Data.TYPE)) {
X509Data xd = (X509Data) ((DOMRetrievalMethod) rm).
dereferenceAsXMLStructure(context);
ksr = x509DataSelect(xd, sm);
} else {
// skip; keyinfo type is not supported
continue;
}
if (ksr != null) {
return ksr;
}
} catch (Exception e) {
throw new KeySelectorException(e);
}
}
}
} catch (KeyStoreException kse) {
// throw exception if keystore is uninitialized
throw new KeySelectorException(kse);
}
// return null since no match could be found
return new SimpleKeySelectorResult(null);
}
/**
* Searches the specified keystore for a certificate that matches the
* criteria specified in the CertSelector.
*
* @return a KeySelectorResult containing the cert's public key if there
* is a match; otherwise null
*/
private KeySelectorResult keyStoreSelect(CertSelector cs)
throws KeyStoreException {
Enumeration aliases = ks.aliases();
while (aliases.hasMoreElements()) {
String alias = (String) aliases.nextElement();
Certificate cert = ks.getCertificate(alias);
if (cert != null && cs.match(cert)) {
return new SimpleKeySelectorResult(cert.getPublicKey());
}
}
return null;
}
/**
* Searches the specified keystore for a certificate that matches the
* specified X509Certificate and contains a public key that is compatible
* with the specified SignatureMethod.
*
* @return a KeySelectorResult containing the cert's public key if there
* is a match; otherwise null
*/
private KeySelectorResult certSelect(X509Certificate xcert,
SignatureMethod sm) throws KeyStoreException {
// skip non-signer certs
boolean[] keyUsage = xcert.getKeyUsage();
if (keyUsage != null && keyUsage[0] == false) {
return null;
}
String alias = ks.getCertificateAlias(xcert);
if (alias != null) {
PublicKey pk = ks.getCertificate(alias).getPublicKey();
// make sure algorithm is compatible with method
if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) {
return new SimpleKeySelectorResult(pk);
}
}
return null;
}
/**
* Returns an OID of a public-key algorithm compatible with the specified
* signature algorithm URI.
*/
private String getPKAlgorithmOID(String algURI) {
if (algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) {
return "1.2.840.10040.4.1";
} else if (algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
return "1.2.840.113549.1.1";
} else {
return null;
}
}
/**
* A simple KeySelectorResult containing a public key.
*/
private static class SimpleKeySelectorResult implements KeySelectorResult {
private final Key key;
SimpleKeySelectorResult(Key key) { this.key = key; }
public Key getKey() { return key; }
}
/**
* Checks if a JCA/JCE public key algorithm name is compatible with
* the specified signature algorithm URI.
*/
//@@@FIXME: this should also work for key types other than DSA/RSA
private boolean algEquals(String algURI, String algName) {
if (algName.equalsIgnoreCase("DSA") &&
algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) {
return true;
} else if (algName.equalsIgnoreCase("RSA") &&
algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
return true;
} else {
return false;
}
}
/**
* Searches the specified keystore for a certificate that matches an
* entry of the specified X509Data and contains a public key that is
* compatible with the specified SignatureMethod.
*
* @return a KeySelectorResult containing the cert's public key if there
* is a match; otherwise null
*/
private KeySelectorResult x509DataSelect(X509Data xd, SignatureMethod sm)
throws KeyStoreException, KeySelectorException {
// convert signature algorithm to compatible public-key alg OID
String algOID = getPKAlgorithmOID(sm.getAlgorithm());
X509CertSelector subjectcs = new X509CertSelector();
try {
subjectcs.setSubjectPublicKeyAlgID(algOID);
} catch (IOException ioe) {
throw new KeySelectorException(ioe);
}
Collection certs = new ArrayList();
Iterator xi = xd.getContent().iterator();
while (xi.hasNext()) {
Object o = xi.next();
// check X509IssuerSerial
if (o instanceof X509IssuerSerial) {
X509IssuerSerial xis = (X509IssuerSerial) o;
try {
subjectcs.setSerialNumber(xis.getSerialNumber());
String issuer = new X500Principal(xis.getIssuerName()).getName();
// strip off newline
if (issuer.endsWith("\n")) {
issuer = new String
(issuer.toCharArray(), 0, issuer.length()-1);
}
subjectcs.setIssuer(issuer);
} catch (IOException ioe) {
throw new KeySelectorException(ioe);
}
// check X509SubjectName
} else if (o instanceof String) {
String sn = (String) o;
try {
String subject = new X500Principal(sn).getName();
// strip off newline
if (subject.endsWith("\n")) {
subject = new String
(subject.toCharArray(), 0, subject.length()-1);
}
subjectcs.setSubject(subject);
} catch (IOException ioe) {
throw new KeySelectorException(ioe);
}
// check X509SKI
} else if (o instanceof byte[]) {
byte[] ski = (byte[]) o;
// DER-encode ski - required by X509CertSelector
byte[] encodedSki = new byte[ski.length+2];
encodedSki[0] = 0x04; // OCTET STRING tag value
encodedSki[1] = (byte) ski.length; // length
System.arraycopy(ski, 0, encodedSki, 2, ski.length);
subjectcs.setSubjectKeyIdentifier(encodedSki);
} else if (o instanceof X509Certificate) {
certs.add((X509Certificate) o);
// check X509CRL
// not supported: should use CertPath API
} else {
// skip all other entries
continue;
}
}
KeySelectorResult ksr = keyStoreSelect(subjectcs);
if (ksr != null) {
return ksr;
}
if (!certs.isEmpty() && !trusted) {
// try to find public key in certs in X509Data
Iterator i = certs.iterator();
while (i.hasNext()) {
X509Certificate cert = (X509Certificate) i.next();
if (subjectcs.match(cert)) {
return new SimpleKeySelectorResult(cert.getPublicKey());
}
}
}
return null;
}
}