|
1 /* |
|
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 |
|
24 import java.io.InputStream; |
|
25 import java.io.IOException; |
|
26 import java.security.Key; |
|
27 import java.security.KeyStore; |
|
28 import java.security.KeyStoreException; |
|
29 import java.security.PublicKey; |
|
30 import java.security.cert.Certificate; |
|
31 import java.security.cert.CertificateFactory; |
|
32 import java.security.cert.CertSelector; |
|
33 import java.security.cert.X509Certificate; |
|
34 import java.security.cert.X509CertSelector; |
|
35 import java.util.*; |
|
36 import javax.security.auth.x500.X500Principal; |
|
37 import javax.xml.crypto.*; |
|
38 import javax.xml.crypto.dsig.*; |
|
39 import javax.xml.crypto.dom.*; |
|
40 import javax.xml.crypto.dsig.keyinfo.*; |
|
41 |
|
42 import 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 */ |
|
60 class 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 } |