diff -r fd16c54261b3 -r 90ce3da70b43 jdk/test/javax/xml/crypto/dsig/X509KeySelector.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/javax/xml/crypto/dsig/X509KeySelector.java Sat Dec 01 00:00:00 2007 +0000 @@ -0,0 +1,372 @@ +/* + * Copyright 2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +import java.io.InputStream; +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.dom.*; +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. + * + * @author Sean Mullan + */ +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 + */ + X509KeySelector(KeyStore keyStore) throws KeyStoreException { + this(keyStore, true); + } + + 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; + } +}