blob: 40df3e4a3a9209c64c134b2c34f17b0c11f06bc7 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2005 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
20 * CA 95054 USA or visit www.sun.com if you need additional information or
21 * have any questions.
22 */
23
24import java.io.InputStream;
25import java.io.IOException;
26import java.security.Key;
27import java.security.KeyStore;
28import java.security.KeyStoreException;
29import java.security.PublicKey;
30import java.security.cert.Certificate;
31import java.security.cert.CertificateFactory;
32import java.security.cert.CertSelector;
33import java.security.cert.X509Certificate;
34import java.security.cert.X509CertSelector;
35import java.util.*;
36import javax.security.auth.x500.X500Principal;
37import javax.xml.crypto.*;
38import javax.xml.crypto.dsig.*;
39import javax.xml.crypto.dom.*;
40import javax.xml.crypto.dsig.keyinfo.*;
41
42import org.jcp.xml.dsig.internal.dom.DOMRetrievalMethod;
43
44/**
45 * A <code>KeySelector</code> that returns {@link PublicKey}s. If the
46 * selector is created as trusted, it only returns public keys of trusted
47 * {@link X509Certificate}s stored in a {@link KeyStore}. Otherwise, it
48 * returns trusted or untrusted public keys (it doesn't care as long
49 * as it finds one).
50 *
51 * <p>This <code>KeySelector</code> uses the specified <code>KeyStore</code>
52 * to find a trusted <code>X509Certificate</code> that matches information
53 * specified in the {@link KeyInfo} passed to the {@link #select} method.
54 * The public key from the first match is returned. If no match,
55 * <code>null</code> is returned. See the <code>select</code> method for more
56 * information.
57 *
58 * @author Sean Mullan
59 */
60class X509KeySelector extends KeySelector {
61
62 private KeyStore ks;
63 private boolean trusted = true;
64
65 /**
66 * Creates a trusted <code>X509KeySelector</code>.
67 *
68 * @param keyStore the keystore
69 * @throws KeyStoreException if the keystore has not been initialized
70 * @throws NullPointerException if <code>keyStore</code> is
71 * <code>null</code>
72 */
73 X509KeySelector(KeyStore keyStore) throws KeyStoreException {
74 this(keyStore, true);
75 }
76
77 X509KeySelector(KeyStore keyStore, boolean trusted)
78 throws KeyStoreException {
79 if (keyStore == null) {
80 throw new NullPointerException("keyStore is null");
81 }
82 this.trusted = trusted;
83 this.ks = keyStore;
84 // test to see if KeyStore has been initialized
85 this.ks.size();
86 }
87
88 /**
89 * Finds a key from the keystore satisfying the specified constraints.
90 *
91 * <p>This method compares data contained in {@link KeyInfo} entries
92 * with information stored in the <code>KeyStore</code>. The implementation
93 * iterates over the KeyInfo types and returns the first {@link PublicKey}
94 * of an X509Certificate in the keystore that is compatible with the
95 * specified AlgorithmMethod according to the following rules for each
96 * keyinfo type:
97 *
98 * X509Data X509Certificate: if it contains a <code>KeyUsage</code>
99 * extension that asserts the <code>digitalSignature</code> bit and
100 * matches an <code>X509Certificate</code> in the <code>KeyStore</code>.
101 * X509Data X509IssuerSerial: if the serial number and issuer DN match an
102 * <code>X509Certificate</code> in the <code>KeyStore</code>.
103 * X509Data X509SubjectName: if the subject DN matches an
104 * <code>X509Certificate</code> in the <code>KeyStore</code>.
105 * X509Data X509SKI: if the subject key identifier matches an
106 * <code>X509Certificate</code> in the <code>KeyStore</code>.
107 * KeyName: if the keyname matches an alias in the <code>KeyStore</code>.
108 * RetrievalMethod: supports rawX509Certificate and X509Data types. If
109 * rawX509Certificate type, it must match an <code>X509Certificate</code>
110 * in the <code>KeyStore</code>.
111 *
112 * @param keyInfo a <code>KeyInfo</code> (may be <code>null</code>)
113 * @param purpose the key's purpose
114 * @param method the algorithm method that this key is to be used for.
115 * Only keys that are compatible with the algorithm and meet the
116 * constraints of the specified algorithm should be returned.
117 * @param an <code>XMLCryptoContext</code> that may contain additional
118 * useful information for finding an appropriate key
119 * @return a key selector result
120 * @throws KeySelectorException if an exceptional condition occurs while
121 * attempting to find a key. Note that an inability to find a key is not
122 * considered an exception (<code>null</code> should be
123 * returned in that case). However, an error condition (ex: network
124 * communications failure) that prevented the <code>KeySelector</code>
125 * from finding a potential key should be considered an exception.
126 * @throws ClassCastException if the data type of <code>method</code>
127 * is not supported by this key selector
128 */
129 public KeySelectorResult select(KeyInfo keyInfo,
130 KeySelector.Purpose purpose, AlgorithmMethod method,
131 XMLCryptoContext context) throws KeySelectorException {
132
133 SignatureMethod sm = (SignatureMethod) method;
134
135 try {
136 // return null if keyinfo is null or keystore is empty
137 if (keyInfo == null || ks.size() == 0) {
138 return new SimpleKeySelectorResult(null);
139 }
140
141 // Iterate through KeyInfo types
142 Iterator i = keyInfo.getContent().iterator();
143 while (i.hasNext()) {
144 XMLStructure kiType = (XMLStructure) i.next();
145 // check X509Data
146 if (kiType instanceof X509Data) {
147 X509Data xd = (X509Data) kiType;
148 KeySelectorResult ksr = x509DataSelect(xd, sm);
149 if (ksr != null) {
150 return ksr;
151 }
152 // check KeyName
153 } else if (kiType instanceof KeyName) {
154 KeyName kn = (KeyName) kiType;
155 Certificate cert = ks.getCertificate(kn.getName());
156 if (cert != null && algEquals(sm.getAlgorithm(),
157 cert.getPublicKey().getAlgorithm())) {
158 return new SimpleKeySelectorResult(cert.getPublicKey());
159 }
160 // check RetrievalMethod
161 } else if (kiType instanceof RetrievalMethod) {
162 RetrievalMethod rm = (RetrievalMethod) kiType;
163 try {
164 KeySelectorResult ksr = null;
165 if (rm.getType().equals
166 (X509Data.RAW_X509_CERTIFICATE_TYPE)) {
167 OctetStreamData data = (OctetStreamData)
168 rm.dereference(context);
169 CertificateFactory cf =
170 CertificateFactory.getInstance("X.509");
171 X509Certificate cert = (X509Certificate)
172 cf.generateCertificate(data.getOctetStream());
173 ksr = certSelect(cert, sm);
174 } else if (rm.getType().equals(X509Data.TYPE)) {
175 X509Data xd = (X509Data) ((DOMRetrievalMethod) rm).
176 dereferenceAsXMLStructure(context);
177 ksr = x509DataSelect(xd, sm);
178 } else {
179 // skip; keyinfo type is not supported
180 continue;
181 }
182 if (ksr != null) {
183 return ksr;
184 }
185 } catch (Exception e) {
186 throw new KeySelectorException(e);
187 }
188 }
189 }
190 } catch (KeyStoreException kse) {
191 // throw exception if keystore is uninitialized
192 throw new KeySelectorException(kse);
193 }
194
195 // return null since no match could be found
196 return new SimpleKeySelectorResult(null);
197 }
198
199 /**
200 * Searches the specified keystore for a certificate that matches the
201 * criteria specified in the CertSelector.
202 *
203 * @return a KeySelectorResult containing the cert's public key if there
204 * is a match; otherwise null
205 */
206 private KeySelectorResult keyStoreSelect(CertSelector cs)
207 throws KeyStoreException {
208 Enumeration aliases = ks.aliases();
209 while (aliases.hasMoreElements()) {
210 String alias = (String) aliases.nextElement();
211 Certificate cert = ks.getCertificate(alias);
212 if (cert != null && cs.match(cert)) {
213 return new SimpleKeySelectorResult(cert.getPublicKey());
214 }
215 }
216 return null;
217 }
218
219 /**
220 * Searches the specified keystore for a certificate that matches the
221 * specified X509Certificate and contains a public key that is compatible
222 * with the specified SignatureMethod.
223 *
224 * @return a KeySelectorResult containing the cert's public key if there
225 * is a match; otherwise null
226 */
227 private KeySelectorResult certSelect(X509Certificate xcert,
228 SignatureMethod sm) throws KeyStoreException {
229 // skip non-signer certs
230 boolean[] keyUsage = xcert.getKeyUsage();
231 if (keyUsage != null && keyUsage[0] == false) {
232 return null;
233 }
234 String alias = ks.getCertificateAlias(xcert);
235 if (alias != null) {
236 PublicKey pk = ks.getCertificate(alias).getPublicKey();
237 // make sure algorithm is compatible with method
238 if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) {
239 return new SimpleKeySelectorResult(pk);
240 }
241 }
242 return null;
243 }
244
245 /**
246 * Returns an OID of a public-key algorithm compatible with the specified
247 * signature algorithm URI.
248 */
249 private String getPKAlgorithmOID(String algURI) {
250 if (algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) {
251 return "1.2.840.10040.4.1";
252 } else if (algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
253 return "1.2.840.113549.1.1";
254 } else {
255 return null;
256 }
257 }
258
259 /**
260 * A simple KeySelectorResult containing a public key.
261 */
262 private static class SimpleKeySelectorResult implements KeySelectorResult {
263 private final Key key;
264 SimpleKeySelectorResult(Key key) { this.key = key; }
265 public Key getKey() { return key; }
266 }
267
268 /**
269 * Checks if a JCA/JCE public key algorithm name is compatible with
270 * the specified signature algorithm URI.
271 */
272 //@@@FIXME: this should also work for key types other than DSA/RSA
273 private boolean algEquals(String algURI, String algName) {
274 if (algName.equalsIgnoreCase("DSA") &&
275 algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) {
276 return true;
277 } else if (algName.equalsIgnoreCase("RSA") &&
278 algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
279 return true;
280 } else {
281 return false;
282 }
283 }
284
285 /**
286 * Searches the specified keystore for a certificate that matches an
287 * entry of the specified X509Data and contains a public key that is
288 * compatible with the specified SignatureMethod.
289 *
290 * @return a KeySelectorResult containing the cert's public key if there
291 * is a match; otherwise null
292 */
293 private KeySelectorResult x509DataSelect(X509Data xd, SignatureMethod sm)
294 throws KeyStoreException, KeySelectorException {
295
296 // convert signature algorithm to compatible public-key alg OID
297 String algOID = getPKAlgorithmOID(sm.getAlgorithm());
298 X509CertSelector subjectcs = new X509CertSelector();
299 try {
300 subjectcs.setSubjectPublicKeyAlgID(algOID);
301 } catch (IOException ioe) {
302 throw new KeySelectorException(ioe);
303 }
304 Collection certs = new ArrayList();
305
306 Iterator xi = xd.getContent().iterator();
307 while (xi.hasNext()) {
308 Object o = xi.next();
309 // check X509IssuerSerial
310 if (o instanceof X509IssuerSerial) {
311 X509IssuerSerial xis = (X509IssuerSerial) o;
312 try {
313 subjectcs.setSerialNumber(xis.getSerialNumber());
314 String issuer = new X500Principal(xis.getIssuerName()).getName();
315 // strip off newline
316 if (issuer.endsWith("\n")) {
317 issuer = new String
318 (issuer.toCharArray(), 0, issuer.length()-1);
319 }
320 subjectcs.setIssuer(issuer);
321 } catch (IOException ioe) {
322 throw new KeySelectorException(ioe);
323 }
324 // check X509SubjectName
325 } else if (o instanceof String) {
326 String sn = (String) o;
327 try {
328 String subject = new X500Principal(sn).getName();
329 // strip off newline
330 if (subject.endsWith("\n")) {
331 subject = new String
332 (subject.toCharArray(), 0, subject.length()-1);
333 }
334 subjectcs.setSubject(subject);
335 } catch (IOException ioe) {
336 throw new KeySelectorException(ioe);
337 }
338 // check X509SKI
339 } else if (o instanceof byte[]) {
340 byte[] ski = (byte[]) o;
341 // DER-encode ski - required by X509CertSelector
342 byte[] encodedSki = new byte[ski.length+2];
343 encodedSki[0] = 0x04; // OCTET STRING tag value
344 encodedSki[1] = (byte) ski.length; // length
345 System.arraycopy(ski, 0, encodedSki, 2, ski.length);
346 subjectcs.setSubjectKeyIdentifier(encodedSki);
347 } else if (o instanceof X509Certificate) {
348 certs.add((X509Certificate) o);
349 // check X509CRL
350 // not supported: should use CertPath API
351 } else {
352 // skip all other entries
353 continue;
354 }
355 }
356 KeySelectorResult ksr = keyStoreSelect(subjectcs);
357 if (ksr != null) {
358 return ksr;
359 }
360 if (!certs.isEmpty() && !trusted) {
361 // try to find public key in certs in X509Data
362 Iterator i = certs.iterator();
363 while (i.hasNext()) {
364 X509Certificate cert = (X509Certificate) i.next();
365 if (subjectcs.match(cert)) {
366 return new SimpleKeySelectorResult(cert.getPublicKey());
367 }
368 }
369 }
370 return null;
371 }
372}