1 /* |
|
2 * Copyright (c) 2011, 2015, Oracle and/or its affiliates. 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. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package apple.security; |
|
27 |
|
28 import java.io.*; |
|
29 import java.security.*; |
|
30 import java.security.cert.*; |
|
31 import java.security.cert.Certificate; |
|
32 import java.security.spec.*; |
|
33 import java.util.*; |
|
34 |
|
35 import javax.crypto.*; |
|
36 import javax.crypto.spec.*; |
|
37 import javax.security.auth.x500.*; |
|
38 |
|
39 import sun.security.pkcs.*; |
|
40 import sun.security.pkcs.EncryptedPrivateKeyInfo; |
|
41 import sun.security.util.*; |
|
42 import sun.security.x509.*; |
|
43 |
|
44 /** |
|
45 * This class provides the keystore implementation referred to as "KeychainStore". |
|
46 * It uses the current user's keychain as its backing storage, and does NOT support |
|
47 * a file-based implementation. |
|
48 */ |
|
49 |
|
50 public final class KeychainStore extends KeyStoreSpi { |
|
51 |
|
52 // Private keys and their supporting certificate chains |
|
53 // If a key came from the keychain it has a SecKeyRef and one or more |
|
54 // SecCertificateRef. When we delete the key we have to delete all of the corresponding |
|
55 // native objects. |
|
56 class KeyEntry { |
|
57 Date date; // the creation date of this entry |
|
58 byte[] protectedPrivKey; |
|
59 char[] password; |
|
60 long keyRef; // SecKeyRef for this key |
|
61 Certificate chain[]; |
|
62 long chainRefs[]; // SecCertificateRefs for this key's chain. |
|
63 }; |
|
64 |
|
65 // Trusted certificates |
|
66 class TrustedCertEntry { |
|
67 Date date; // the creation date of this entry |
|
68 |
|
69 Certificate cert; |
|
70 long certRef; // SecCertificateRef for this key |
|
71 }; |
|
72 |
|
73 /** |
|
74 * Entries that have been deleted. When something calls engineStore we'll |
|
75 * remove them from the keychain. |
|
76 */ |
|
77 private Hashtable<String, Object> deletedEntries = new Hashtable<>(); |
|
78 |
|
79 /** |
|
80 * Entries that have been added. When something calls engineStore we'll |
|
81 * add them to the keychain. |
|
82 */ |
|
83 private Hashtable<String, Object> addedEntries = new Hashtable<>(); |
|
84 |
|
85 /** |
|
86 * Private keys and certificates are stored in a hashtable. |
|
87 * Hash entries are keyed by alias names. |
|
88 */ |
|
89 private Hashtable<String, Object> entries = new Hashtable<>(); |
|
90 |
|
91 /** |
|
92 * Algorithm identifiers and corresponding OIDs for the contents of the PKCS12 bag we get from the Keychain. |
|
93 */ |
|
94 private static final int keyBag[] = {1, 2, 840, 113549, 1, 12, 10, 1, 2}; |
|
95 private static final int pbeWithSHAAnd3KeyTripleDESCBC[] = {1, 2, 840, 113549, 1, 12, 1, 3}; |
|
96 private static ObjectIdentifier PKCS8ShroudedKeyBag_OID; |
|
97 private static ObjectIdentifier pbeWithSHAAnd3KeyTripleDESCBC_OID; |
|
98 |
|
99 /** |
|
100 * Constnats used in PBE decryption. |
|
101 */ |
|
102 private static final int iterationCount = 1024; |
|
103 private static final int SALT_LEN = 20; |
|
104 |
|
105 static { |
|
106 AccessController.doPrivileged( |
|
107 new PrivilegedAction<Void>() { |
|
108 public Void run() { |
|
109 System.loadLibrary("osx"); |
|
110 return null; |
|
111 } |
|
112 }); |
|
113 try { |
|
114 PKCS8ShroudedKeyBag_OID = new ObjectIdentifier(keyBag); |
|
115 pbeWithSHAAnd3KeyTripleDESCBC_OID = new ObjectIdentifier(pbeWithSHAAnd3KeyTripleDESCBC); |
|
116 } catch (IOException ioe) { |
|
117 // should not happen |
|
118 } |
|
119 } |
|
120 |
|
121 private static void permissionCheck() { |
|
122 SecurityManager sec = System.getSecurityManager(); |
|
123 |
|
124 if (sec != null) { |
|
125 sec.checkPermission(new RuntimePermission("useKeychainStore")); |
|
126 } |
|
127 } |
|
128 |
|
129 |
|
130 /** |
|
131 * Verify the Apple provider in the constructor. |
|
132 * |
|
133 * @exception SecurityException if fails to verify |
|
134 * its own integrity |
|
135 */ |
|
136 public KeychainStore() { } |
|
137 |
|
138 /** |
|
139 * Returns the key associated with the given alias, using the given |
|
140 * password to recover it. |
|
141 * |
|
142 * @param alias the alias name |
|
143 * @param password the password for recovering the key. This password is |
|
144 * used internally as the key is exported in a PKCS12 format. |
|
145 * |
|
146 * @return the requested key, or null if the given alias does not exist |
|
147 * or does not identify a <i>key entry</i>. |
|
148 * |
|
149 * @exception NoSuchAlgorithmException if the algorithm for recovering the |
|
150 * key cannot be found |
|
151 * @exception UnrecoverableKeyException if the key cannot be recovered |
|
152 * (e.g., the given password is wrong). |
|
153 */ |
|
154 public Key engineGetKey(String alias, char[] password) |
|
155 throws NoSuchAlgorithmException, UnrecoverableKeyException |
|
156 { |
|
157 permissionCheck(); |
|
158 |
|
159 // An empty password is rejected by MacOS API, no private key data |
|
160 // is exported. If no password is passed (as is the case when |
|
161 // this implementation is used as browser keystore in various |
|
162 // deployment scenarios like Webstart, JFX and applets), create |
|
163 // a dummy password so MacOS API is happy. |
|
164 if (password == null || password.length == 0) { |
|
165 // Must not be a char array with only a 0, as this is an empty |
|
166 // string. |
|
167 if (random == null) { |
|
168 random = new SecureRandom(); |
|
169 } |
|
170 password = Long.toString(random.nextLong()).toCharArray(); |
|
171 } |
|
172 |
|
173 Object entry = entries.get(alias.toLowerCase()); |
|
174 |
|
175 if (entry == null || !(entry instanceof KeyEntry)) { |
|
176 return null; |
|
177 } |
|
178 |
|
179 // This call gives us a PKCS12 bag, with the key inside it. |
|
180 byte[] exportedKeyInfo = _getEncodedKeyData(((KeyEntry)entry).keyRef, password); |
|
181 if (exportedKeyInfo == null) { |
|
182 return null; |
|
183 } |
|
184 |
|
185 PrivateKey returnValue = null; |
|
186 |
|
187 try { |
|
188 byte[] pkcs8KeyData = fetchPrivateKeyFromBag(exportedKeyInfo); |
|
189 byte[] encryptedKey; |
|
190 AlgorithmParameters algParams; |
|
191 ObjectIdentifier algOid; |
|
192 try { |
|
193 // get the encrypted private key |
|
194 EncryptedPrivateKeyInfo encrInfo = new EncryptedPrivateKeyInfo(pkcs8KeyData); |
|
195 encryptedKey = encrInfo.getEncryptedData(); |
|
196 |
|
197 // parse Algorithm parameters |
|
198 DerValue val = new DerValue(encrInfo.getAlgorithm().encode()); |
|
199 DerInputStream in = val.toDerInputStream(); |
|
200 algOid = in.getOID(); |
|
201 algParams = parseAlgParameters(in); |
|
202 |
|
203 } catch (IOException ioe) { |
|
204 UnrecoverableKeyException uke = |
|
205 new UnrecoverableKeyException("Private key not stored as " |
|
206 + "PKCS#8 EncryptedPrivateKeyInfo: " + ioe); |
|
207 uke.initCause(ioe); |
|
208 throw uke; |
|
209 } |
|
210 |
|
211 // Use JCE to decrypt the data using the supplied password. |
|
212 SecretKey skey = getPBEKey(password); |
|
213 Cipher cipher = Cipher.getInstance(algOid.toString()); |
|
214 cipher.init(Cipher.DECRYPT_MODE, skey, algParams); |
|
215 byte[] decryptedPrivateKey = cipher.doFinal(encryptedKey); |
|
216 PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(decryptedPrivateKey); |
|
217 |
|
218 // Parse the key algorithm and then use a JCA key factory to create the private key. |
|
219 DerValue val = new DerValue(decryptedPrivateKey); |
|
220 DerInputStream in = val.toDerInputStream(); |
|
221 |
|
222 // Ignore this -- version should be 0. |
|
223 int i = in.getInteger(); |
|
224 |
|
225 // Get the Algorithm ID next |
|
226 DerValue[] value = in.getSequence(2); |
|
227 AlgorithmId algId = new AlgorithmId(value[0].getOID()); |
|
228 String algName = algId.getName(); |
|
229 |
|
230 // Get a key factory for this algorithm. It's likely to be 'RSA'. |
|
231 KeyFactory kfac = KeyFactory.getInstance(algName); |
|
232 returnValue = kfac.generatePrivate(kspec); |
|
233 } catch (Exception e) { |
|
234 UnrecoverableKeyException uke = |
|
235 new UnrecoverableKeyException("Get Key failed: " + |
|
236 e.getMessage()); |
|
237 uke.initCause(e); |
|
238 throw uke; |
|
239 } |
|
240 |
|
241 return returnValue; |
|
242 } |
|
243 |
|
244 private native byte[] _getEncodedKeyData(long secKeyRef, char[] password); |
|
245 |
|
246 /** |
|
247 * Returns the certificate chain associated with the given alias. |
|
248 * |
|
249 * @param alias the alias name |
|
250 * |
|
251 * @return the certificate chain (ordered with the user's certificate first |
|
252 * and the root certificate authority last), or null if the given alias |
|
253 * does not exist or does not contain a certificate chain (i.e., the given |
|
254 * alias identifies either a <i>trusted certificate entry</i> or a |
|
255 * <i>key entry</i> without a certificate chain). |
|
256 */ |
|
257 public Certificate[] engineGetCertificateChain(String alias) { |
|
258 permissionCheck(); |
|
259 |
|
260 Object entry = entries.get(alias.toLowerCase()); |
|
261 |
|
262 if (entry != null && entry instanceof KeyEntry) { |
|
263 if (((KeyEntry)entry).chain == null) { |
|
264 return null; |
|
265 } else { |
|
266 return ((KeyEntry)entry).chain.clone(); |
|
267 } |
|
268 } else { |
|
269 return null; |
|
270 } |
|
271 } |
|
272 |
|
273 /** |
|
274 * Returns the certificate associated with the given alias. |
|
275 * |
|
276 * <p>If the given alias name identifies a |
|
277 * <i>trusted certificate entry</i>, the certificate associated with that |
|
278 * entry is returned. If the given alias name identifies a |
|
279 * <i>key entry</i>, the first element of the certificate chain of that |
|
280 * entry is returned, or null if that entry does not have a certificate |
|
281 * chain. |
|
282 * |
|
283 * @param alias the alias name |
|
284 * |
|
285 * @return the certificate, or null if the given alias does not exist or |
|
286 * does not contain a certificate. |
|
287 */ |
|
288 public Certificate engineGetCertificate(String alias) { |
|
289 permissionCheck(); |
|
290 |
|
291 Object entry = entries.get(alias.toLowerCase()); |
|
292 |
|
293 if (entry != null) { |
|
294 if (entry instanceof TrustedCertEntry) { |
|
295 return ((TrustedCertEntry)entry).cert; |
|
296 } else { |
|
297 KeyEntry ke = (KeyEntry)entry; |
|
298 if (ke.chain == null || ke.chain.length == 0) { |
|
299 return null; |
|
300 } |
|
301 return ke.chain[0]; |
|
302 } |
|
303 } else { |
|
304 return null; |
|
305 } |
|
306 } |
|
307 |
|
308 /** |
|
309 * Returns the creation date of the entry identified by the given alias. |
|
310 * |
|
311 * @param alias the alias name |
|
312 * |
|
313 * @return the creation date of this entry, or null if the given alias does |
|
314 * not exist |
|
315 */ |
|
316 public Date engineGetCreationDate(String alias) { |
|
317 permissionCheck(); |
|
318 |
|
319 Object entry = entries.get(alias.toLowerCase()); |
|
320 |
|
321 if (entry != null) { |
|
322 if (entry instanceof TrustedCertEntry) { |
|
323 return new Date(((TrustedCertEntry)entry).date.getTime()); |
|
324 } else { |
|
325 return new Date(((KeyEntry)entry).date.getTime()); |
|
326 } |
|
327 } else { |
|
328 return null; |
|
329 } |
|
330 } |
|
331 |
|
332 /** |
|
333 * Assigns the given key to the given alias, protecting it with the given |
|
334 * password. |
|
335 * |
|
336 * <p>If the given key is of type <code>java.security.PrivateKey</code>, |
|
337 * it must be accompanied by a certificate chain certifying the |
|
338 * corresponding public key. |
|
339 * |
|
340 * <p>If the given alias already exists, the keystore information |
|
341 * associated with it is overridden by the given key (and possibly |
|
342 * certificate chain). |
|
343 * |
|
344 * @param alias the alias name |
|
345 * @param key the key to be associated with the alias |
|
346 * @param password the password to protect the key |
|
347 * @param chain the certificate chain for the corresponding public |
|
348 * key (only required if the given key is of type |
|
349 * <code>java.security.PrivateKey</code>). |
|
350 * |
|
351 * @exception KeyStoreException if the given key cannot be protected, or |
|
352 * this operation fails for some other reason |
|
353 */ |
|
354 public void engineSetKeyEntry(String alias, Key key, char[] password, |
|
355 Certificate[] chain) |
|
356 throws KeyStoreException |
|
357 { |
|
358 permissionCheck(); |
|
359 |
|
360 synchronized(entries) { |
|
361 try { |
|
362 KeyEntry entry = new KeyEntry(); |
|
363 entry.date = new Date(); |
|
364 |
|
365 if (key instanceof PrivateKey) { |
|
366 if ((key.getFormat().equals("PKCS#8")) || |
|
367 (key.getFormat().equals("PKCS8"))) { |
|
368 entry.protectedPrivKey = encryptPrivateKey(key.getEncoded(), password); |
|
369 entry.password = password.clone(); |
|
370 } else { |
|
371 throw new KeyStoreException("Private key is not encoded as PKCS#8"); |
|
372 } |
|
373 } else { |
|
374 throw new KeyStoreException("Key is not a PrivateKey"); |
|
375 } |
|
376 |
|
377 // clone the chain |
|
378 if (chain != null) { |
|
379 if ((chain.length > 1) && !validateChain(chain)) { |
|
380 throw new KeyStoreException("Certificate chain does not validate"); |
|
381 } |
|
382 |
|
383 entry.chain = chain.clone(); |
|
384 entry.chainRefs = new long[entry.chain.length]; |
|
385 } |
|
386 |
|
387 String lowerAlias = alias.toLowerCase(); |
|
388 if (entries.get(lowerAlias) != null) { |
|
389 deletedEntries.put(lowerAlias, entries.get(lowerAlias)); |
|
390 } |
|
391 |
|
392 entries.put(lowerAlias, entry); |
|
393 addedEntries.put(lowerAlias, entry); |
|
394 } catch (Exception nsae) { |
|
395 KeyStoreException ke = new KeyStoreException("Key protection algorithm not found: " + nsae); |
|
396 ke.initCause(nsae); |
|
397 throw ke; |
|
398 } |
|
399 } |
|
400 } |
|
401 |
|
402 /** |
|
403 * Assigns the given key (that has already been protected) to the given |
|
404 * alias. |
|
405 * |
|
406 * <p>If the protected key is of type |
|
407 * <code>java.security.PrivateKey</code>, it must be accompanied by a |
|
408 * certificate chain certifying the corresponding public key. If the |
|
409 * underlying keystore implementation is of type <code>jks</code>, |
|
410 * <code>key</code> must be encoded as an |
|
411 * <code>EncryptedPrivateKeyInfo</code> as defined in the PKCS #8 standard. |
|
412 * |
|
413 * <p>If the given alias already exists, the keystore information |
|
414 * associated with it is overridden by the given key (and possibly |
|
415 * certificate chain). |
|
416 * |
|
417 * @param alias the alias name |
|
418 * @param key the key (in protected format) to be associated with the alias |
|
419 * @param chain the certificate chain for the corresponding public |
|
420 * key (only useful if the protected key is of type |
|
421 * <code>java.security.PrivateKey</code>). |
|
422 * |
|
423 * @exception KeyStoreException if this operation fails. |
|
424 */ |
|
425 public void engineSetKeyEntry(String alias, byte[] key, |
|
426 Certificate[] chain) |
|
427 throws KeyStoreException |
|
428 { |
|
429 permissionCheck(); |
|
430 |
|
431 synchronized(entries) { |
|
432 // key must be encoded as EncryptedPrivateKeyInfo as defined in |
|
433 // PKCS#8 |
|
434 KeyEntry entry = new KeyEntry(); |
|
435 try { |
|
436 EncryptedPrivateKeyInfo privateKey = new EncryptedPrivateKeyInfo(key); |
|
437 entry.protectedPrivKey = privateKey.getEncoded(); |
|
438 } catch (IOException ioe) { |
|
439 throw new KeyStoreException("key is not encoded as " |
|
440 + "EncryptedPrivateKeyInfo"); |
|
441 } |
|
442 |
|
443 entry.date = new Date(); |
|
444 |
|
445 if ((chain != null) && |
|
446 (chain.length != 0)) { |
|
447 entry.chain = chain.clone(); |
|
448 entry.chainRefs = new long[entry.chain.length]; |
|
449 } |
|
450 |
|
451 String lowerAlias = alias.toLowerCase(); |
|
452 if (entries.get(lowerAlias) != null) { |
|
453 deletedEntries.put(lowerAlias, entries.get(alias)); |
|
454 } |
|
455 entries.put(lowerAlias, entry); |
|
456 addedEntries.put(lowerAlias, entry); |
|
457 } |
|
458 } |
|
459 |
|
460 /** |
|
461 * Assigns the given certificate to the given alias. |
|
462 * |
|
463 * <p>If the given alias already exists in this keystore and identifies a |
|
464 * <i>trusted certificate entry</i>, the certificate associated with it is |
|
465 * overridden by the given certificate. |
|
466 * |
|
467 * @param alias the alias name |
|
468 * @param cert the certificate |
|
469 * |
|
470 * @exception KeyStoreException if the given alias already exists and does |
|
471 * not identify a <i>trusted certificate entry</i>, or this operation |
|
472 * fails for some other reason. |
|
473 */ |
|
474 public void engineSetCertificateEntry(String alias, Certificate cert) |
|
475 throws KeyStoreException |
|
476 { |
|
477 permissionCheck(); |
|
478 |
|
479 synchronized(entries) { |
|
480 |
|
481 Object entry = entries.get(alias.toLowerCase()); |
|
482 if ((entry != null) && (entry instanceof KeyEntry)) { |
|
483 throw new KeyStoreException |
|
484 ("Cannot overwrite key entry with certificate"); |
|
485 } |
|
486 |
|
487 // This will be slow, but necessary. Enumerate the values and then see if the cert matches the one in the trusted cert entry. |
|
488 // Security framework doesn't support the same certificate twice in a keychain. |
|
489 Collection<Object> allValues = entries.values(); |
|
490 |
|
491 for (Object value : allValues) { |
|
492 if (value instanceof TrustedCertEntry) { |
|
493 TrustedCertEntry tce = (TrustedCertEntry)value; |
|
494 if (tce.cert.equals(cert)) { |
|
495 throw new KeyStoreException("Keychain does not support mulitple copies of same certificate."); |
|
496 } |
|
497 } |
|
498 } |
|
499 |
|
500 TrustedCertEntry trustedCertEntry = new TrustedCertEntry(); |
|
501 trustedCertEntry.cert = cert; |
|
502 trustedCertEntry.date = new Date(); |
|
503 String lowerAlias = alias.toLowerCase(); |
|
504 if (entries.get(lowerAlias) != null) { |
|
505 deletedEntries.put(lowerAlias, entries.get(lowerAlias)); |
|
506 } |
|
507 entries.put(lowerAlias, trustedCertEntry); |
|
508 addedEntries.put(lowerAlias, trustedCertEntry); |
|
509 } |
|
510 } |
|
511 |
|
512 /** |
|
513 * Deletes the entry identified by the given alias from this keystore. |
|
514 * |
|
515 * @param alias the alias name |
|
516 * |
|
517 * @exception KeyStoreException if the entry cannot be removed. |
|
518 */ |
|
519 public void engineDeleteEntry(String alias) |
|
520 throws KeyStoreException |
|
521 { |
|
522 permissionCheck(); |
|
523 |
|
524 synchronized(entries) { |
|
525 Object entry = entries.remove(alias.toLowerCase()); |
|
526 deletedEntries.put(alias.toLowerCase(), entry); |
|
527 } |
|
528 } |
|
529 |
|
530 /** |
|
531 * Lists all the alias names of this keystore. |
|
532 * |
|
533 * @return enumeration of the alias names |
|
534 */ |
|
535 public Enumeration<String> engineAliases() { |
|
536 permissionCheck(); |
|
537 return entries.keys(); |
|
538 } |
|
539 |
|
540 /** |
|
541 * Checks if the given alias exists in this keystore. |
|
542 * |
|
543 * @param alias the alias name |
|
544 * |
|
545 * @return true if the alias exists, false otherwise |
|
546 */ |
|
547 public boolean engineContainsAlias(String alias) { |
|
548 permissionCheck(); |
|
549 return entries.containsKey(alias.toLowerCase()); |
|
550 } |
|
551 |
|
552 /** |
|
553 * Retrieves the number of entries in this keystore. |
|
554 * |
|
555 * @return the number of entries in this keystore |
|
556 */ |
|
557 public int engineSize() { |
|
558 permissionCheck(); |
|
559 return entries.size(); |
|
560 } |
|
561 |
|
562 /** |
|
563 * Returns true if the entry identified by the given alias is a |
|
564 * <i>key entry</i>, and false otherwise. |
|
565 * |
|
566 * @return true if the entry identified by the given alias is a |
|
567 * <i>key entry</i>, false otherwise. |
|
568 */ |
|
569 public boolean engineIsKeyEntry(String alias) { |
|
570 permissionCheck(); |
|
571 Object entry = entries.get(alias.toLowerCase()); |
|
572 if ((entry != null) && (entry instanceof KeyEntry)) { |
|
573 return true; |
|
574 } else { |
|
575 return false; |
|
576 } |
|
577 } |
|
578 |
|
579 /** |
|
580 * Returns true if the entry identified by the given alias is a |
|
581 * <i>trusted certificate entry</i>, and false otherwise. |
|
582 * |
|
583 * @return true if the entry identified by the given alias is a |
|
584 * <i>trusted certificate entry</i>, false otherwise. |
|
585 */ |
|
586 public boolean engineIsCertificateEntry(String alias) { |
|
587 permissionCheck(); |
|
588 Object entry = entries.get(alias.toLowerCase()); |
|
589 if ((entry != null) && (entry instanceof TrustedCertEntry)) { |
|
590 return true; |
|
591 } else { |
|
592 return false; |
|
593 } |
|
594 } |
|
595 |
|
596 /** |
|
597 * Returns the (alias) name of the first keystore entry whose certificate |
|
598 * matches the given certificate. |
|
599 * |
|
600 * <p>This method attempts to match the given certificate with each |
|
601 * keystore entry. If the entry being considered |
|
602 * is a <i>trusted certificate entry</i>, the given certificate is |
|
603 * compared to that entry's certificate. If the entry being considered is |
|
604 * a <i>key entry</i>, the given certificate is compared to the first |
|
605 * element of that entry's certificate chain (if a chain exists). |
|
606 * |
|
607 * @param cert the certificate to match with. |
|
608 * |
|
609 * @return the (alias) name of the first entry with matching certificate, |
|
610 * or null if no such entry exists in this keystore. |
|
611 */ |
|
612 public String engineGetCertificateAlias(Certificate cert) { |
|
613 permissionCheck(); |
|
614 Certificate certElem; |
|
615 |
|
616 for (Enumeration<String> e = entries.keys(); e.hasMoreElements(); ) { |
|
617 String alias = e.nextElement(); |
|
618 Object entry = entries.get(alias); |
|
619 if (entry instanceof TrustedCertEntry) { |
|
620 certElem = ((TrustedCertEntry)entry).cert; |
|
621 } else { |
|
622 KeyEntry ke = (KeyEntry)entry; |
|
623 if (ke.chain == null || ke.chain.length == 0) { |
|
624 continue; |
|
625 } |
|
626 certElem = ke.chain[0]; |
|
627 } |
|
628 if (certElem.equals(cert)) { |
|
629 return alias; |
|
630 } |
|
631 } |
|
632 return null; |
|
633 } |
|
634 |
|
635 /** |
|
636 * Stores this keystore to the given output stream, and protects its |
|
637 * integrity with the given password. |
|
638 * |
|
639 * @param stream Ignored. the output stream to which this keystore is written. |
|
640 * @param password the password to generate the keystore integrity check |
|
641 * |
|
642 * @exception IOException if there was an I/O problem with data |
|
643 * @exception NoSuchAlgorithmException if the appropriate data integrity |
|
644 * algorithm could not be found |
|
645 * @exception CertificateException if any of the certificates included in |
|
646 * the keystore data could not be stored |
|
647 */ |
|
648 public void engineStore(OutputStream stream, char[] password) |
|
649 throws IOException, NoSuchAlgorithmException, CertificateException |
|
650 { |
|
651 permissionCheck(); |
|
652 |
|
653 // Delete items that do have a keychain item ref. |
|
654 for (Enumeration<String> e = deletedEntries.keys(); e.hasMoreElements(); ) { |
|
655 String alias = e.nextElement(); |
|
656 Object entry = deletedEntries.get(alias); |
|
657 if (entry instanceof TrustedCertEntry) { |
|
658 if (((TrustedCertEntry)entry).certRef != 0) { |
|
659 _removeItemFromKeychain(((TrustedCertEntry)entry).certRef); |
|
660 _releaseKeychainItemRef(((TrustedCertEntry)entry).certRef); |
|
661 } |
|
662 } else { |
|
663 Certificate certElem; |
|
664 KeyEntry keyEntry = (KeyEntry)entry; |
|
665 |
|
666 if (keyEntry.chain != null) { |
|
667 for (int i = 0; i < keyEntry.chain.length; i++) { |
|
668 if (keyEntry.chainRefs[i] != 0) { |
|
669 _removeItemFromKeychain(keyEntry.chainRefs[i]); |
|
670 _releaseKeychainItemRef(keyEntry.chainRefs[i]); |
|
671 } |
|
672 } |
|
673 |
|
674 if (keyEntry.keyRef != 0) { |
|
675 _removeItemFromKeychain(keyEntry.keyRef); |
|
676 _releaseKeychainItemRef(keyEntry.keyRef); |
|
677 } |
|
678 } |
|
679 } |
|
680 } |
|
681 |
|
682 // Add all of the certs or keys in the added entries. |
|
683 // No need to check for 0 refs, as they are in the added list. |
|
684 for (Enumeration<String> e = addedEntries.keys(); e.hasMoreElements(); ) { |
|
685 String alias = e.nextElement(); |
|
686 Object entry = addedEntries.get(alias); |
|
687 if (entry instanceof TrustedCertEntry) { |
|
688 TrustedCertEntry tce = (TrustedCertEntry)entry; |
|
689 Certificate certElem; |
|
690 certElem = tce.cert; |
|
691 tce.certRef = addCertificateToKeychain(alias, certElem); |
|
692 } else { |
|
693 KeyEntry keyEntry = (KeyEntry)entry; |
|
694 |
|
695 if (keyEntry.chain != null) { |
|
696 for (int i = 0; i < keyEntry.chain.length; i++) { |
|
697 keyEntry.chainRefs[i] = addCertificateToKeychain(alias, keyEntry.chain[i]); |
|
698 } |
|
699 |
|
700 keyEntry.keyRef = _addItemToKeychain(alias, false, keyEntry.protectedPrivKey, keyEntry.password); |
|
701 } |
|
702 } |
|
703 } |
|
704 |
|
705 // Clear the added and deletedEntries hashtables here, now that we're done with the updates. |
|
706 // For the deleted entries, we freed up the native references above. |
|
707 deletedEntries.clear(); |
|
708 addedEntries.clear(); |
|
709 } |
|
710 |
|
711 private long addCertificateToKeychain(String alias, Certificate cert) { |
|
712 byte[] certblob = null; |
|
713 long returnValue = 0; |
|
714 |
|
715 try { |
|
716 certblob = cert.getEncoded(); |
|
717 returnValue = _addItemToKeychain(alias, true, certblob, null); |
|
718 } catch (Exception e) { |
|
719 e.printStackTrace(); |
|
720 } |
|
721 |
|
722 return returnValue; |
|
723 } |
|
724 |
|
725 private native long _addItemToKeychain(String alias, boolean isCertificate, byte[] datablob, char[] password); |
|
726 private native int _removeItemFromKeychain(long certRef); |
|
727 private native void _releaseKeychainItemRef(long keychainItemRef); |
|
728 |
|
729 /** |
|
730 * Loads the keystore from the Keychain. |
|
731 * |
|
732 * @param stream Ignored - here for API compatibility. |
|
733 * @param password Ignored - if user needs to unlock keychain Security |
|
734 * framework will post any dialogs. |
|
735 * |
|
736 * @exception IOException if there is an I/O or format problem with the |
|
737 * keystore data |
|
738 * @exception NoSuchAlgorithmException if the algorithm used to check |
|
739 * the integrity of the keystore cannot be found |
|
740 * @exception CertificateException if any of the certificates in the |
|
741 * keystore could not be loaded |
|
742 */ |
|
743 public void engineLoad(InputStream stream, char[] password) |
|
744 throws IOException, NoSuchAlgorithmException, CertificateException |
|
745 { |
|
746 permissionCheck(); |
|
747 |
|
748 // Release any stray keychain references before clearing out the entries. |
|
749 synchronized(entries) { |
|
750 for (Enumeration<String> e = entries.keys(); e.hasMoreElements(); ) { |
|
751 String alias = e.nextElement(); |
|
752 Object entry = entries.get(alias); |
|
753 if (entry instanceof TrustedCertEntry) { |
|
754 if (((TrustedCertEntry)entry).certRef != 0) { |
|
755 _releaseKeychainItemRef(((TrustedCertEntry)entry).certRef); |
|
756 } |
|
757 } else { |
|
758 KeyEntry keyEntry = (KeyEntry)entry; |
|
759 |
|
760 if (keyEntry.chain != null) { |
|
761 for (int i = 0; i < keyEntry.chain.length; i++) { |
|
762 if (keyEntry.chainRefs[i] != 0) { |
|
763 _releaseKeychainItemRef(keyEntry.chainRefs[i]); |
|
764 } |
|
765 } |
|
766 |
|
767 if (keyEntry.keyRef != 0) { |
|
768 _releaseKeychainItemRef(keyEntry.keyRef); |
|
769 } |
|
770 } |
|
771 } |
|
772 } |
|
773 |
|
774 entries.clear(); |
|
775 _scanKeychain(); |
|
776 } |
|
777 } |
|
778 |
|
779 private native void _scanKeychain(); |
|
780 |
|
781 /** |
|
782 * Callback method from _scanKeychain. If a trusted certificate is found, this method will be called. |
|
783 */ |
|
784 private void createTrustedCertEntry(String alias, long keychainItemRef, long creationDate, byte[] derStream) { |
|
785 TrustedCertEntry tce = new TrustedCertEntry(); |
|
786 |
|
787 try { |
|
788 CertificateFactory cf = CertificateFactory.getInstance("X.509"); |
|
789 InputStream input = new ByteArrayInputStream(derStream); |
|
790 X509Certificate cert = (X509Certificate) cf.generateCertificate(input); |
|
791 input.close(); |
|
792 tce.cert = cert; |
|
793 tce.certRef = keychainItemRef; |
|
794 |
|
795 // Make a creation date. |
|
796 if (creationDate != 0) |
|
797 tce.date = new Date(creationDate); |
|
798 else |
|
799 tce.date = new Date(); |
|
800 |
|
801 int uniqueVal = 1; |
|
802 String originalAlias = alias; |
|
803 |
|
804 while (entries.containsKey(alias.toLowerCase())) { |
|
805 alias = originalAlias + " " + uniqueVal; |
|
806 uniqueVal++; |
|
807 } |
|
808 |
|
809 entries.put(alias.toLowerCase(), tce); |
|
810 } catch (Exception e) { |
|
811 // The certificate will be skipped. |
|
812 System.err.println("KeychainStore Ignored Exception: " + e); |
|
813 } |
|
814 } |
|
815 |
|
816 /** |
|
817 * Callback method from _scanKeychain. If an identity is found, this method will be called to create Java certificate |
|
818 * and private key objects from the keychain data. |
|
819 */ |
|
820 private void createKeyEntry(String alias, long creationDate, long secKeyRef, long[] secCertificateRefs, byte[][] rawCertData) |
|
821 throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException { |
|
822 KeyEntry ke = new KeyEntry(); |
|
823 |
|
824 // First, store off the private key information. This is the easy part. |
|
825 ke.protectedPrivKey = null; |
|
826 ke.keyRef = secKeyRef; |
|
827 |
|
828 // Make a creation date. |
|
829 if (creationDate != 0) |
|
830 ke.date = new Date(creationDate); |
|
831 else |
|
832 ke.date = new Date(); |
|
833 |
|
834 // Next, create X.509 Certificate objects from the raw data. This is complicated |
|
835 // because a certificate's public key may be too long for Java's default encryption strength. |
|
836 List<CertKeychainItemPair> createdCerts = new ArrayList<>(); |
|
837 |
|
838 try { |
|
839 CertificateFactory cf = CertificateFactory.getInstance("X.509"); |
|
840 |
|
841 for (int i = 0; i < rawCertData.length; i++) { |
|
842 try { |
|
843 InputStream input = new ByteArrayInputStream(rawCertData[i]); |
|
844 X509Certificate cert = (X509Certificate) cf.generateCertificate(input); |
|
845 input.close(); |
|
846 |
|
847 // We successfully created the certificate, so track it and its corresponding SecCertificateRef. |
|
848 createdCerts.add(new CertKeychainItemPair(secCertificateRefs[i], cert)); |
|
849 } catch (CertificateException e) { |
|
850 // The certificate will be skipped. |
|
851 System.err.println("KeychainStore Ignored Exception: " + e); |
|
852 } |
|
853 } |
|
854 } catch (CertificateException e) { |
|
855 e.printStackTrace(); |
|
856 } catch (IOException ioe) { |
|
857 ioe.printStackTrace(); // How would this happen? |
|
858 } |
|
859 |
|
860 // We have our certificates in the List, so now extract them into an array of |
|
861 // Certificates and SecCertificateRefs. |
|
862 CertKeychainItemPair[] objArray = createdCerts.toArray(new CertKeychainItemPair[0]); |
|
863 Certificate[] certArray = new Certificate[objArray.length]; |
|
864 long[] certRefArray = new long[objArray.length]; |
|
865 |
|
866 for (int i = 0; i < objArray.length; i++) { |
|
867 CertKeychainItemPair addedItem = objArray[i]; |
|
868 certArray[i] = addedItem.mCert; |
|
869 certRefArray[i] = addedItem.mCertificateRef; |
|
870 } |
|
871 |
|
872 ke.chain = certArray; |
|
873 ke.chainRefs = certRefArray; |
|
874 |
|
875 // If we don't have already have an item with this item's alias |
|
876 // create a new one for it. |
|
877 int uniqueVal = 1; |
|
878 String originalAlias = alias; |
|
879 |
|
880 while (entries.containsKey(alias.toLowerCase())) { |
|
881 alias = originalAlias + " " + uniqueVal; |
|
882 uniqueVal++; |
|
883 } |
|
884 |
|
885 entries.put(alias.toLowerCase(), ke); |
|
886 } |
|
887 |
|
888 private class CertKeychainItemPair { |
|
889 long mCertificateRef; |
|
890 Certificate mCert; |
|
891 |
|
892 CertKeychainItemPair(long inCertRef, Certificate cert) { |
|
893 mCertificateRef = inCertRef; |
|
894 mCert = cert; |
|
895 } |
|
896 } |
|
897 |
|
898 /* |
|
899 * Validate Certificate Chain |
|
900 */ |
|
901 private boolean validateChain(Certificate[] certChain) |
|
902 { |
|
903 for (int i = 0; i < certChain.length-1; i++) { |
|
904 X500Principal issuerDN = |
|
905 ((X509Certificate)certChain[i]).getIssuerX500Principal(); |
|
906 X500Principal subjectDN = |
|
907 ((X509Certificate)certChain[i+1]).getSubjectX500Principal(); |
|
908 if (!(issuerDN.equals(subjectDN))) |
|
909 return false; |
|
910 } |
|
911 return true; |
|
912 } |
|
913 |
|
914 @SuppressWarnings("deprecation") |
|
915 private byte[] fetchPrivateKeyFromBag(byte[] privateKeyInfo) throws IOException, NoSuchAlgorithmException, CertificateException |
|
916 { |
|
917 byte[] returnValue = null; |
|
918 DerValue val = new DerValue(new ByteArrayInputStream(privateKeyInfo)); |
|
919 DerInputStream s = val.toDerInputStream(); |
|
920 int version = s.getInteger(); |
|
921 |
|
922 if (version != 3) { |
|
923 throw new IOException("PKCS12 keystore not in version 3 format"); |
|
924 } |
|
925 |
|
926 /* |
|
927 * Read the authSafe. |
|
928 */ |
|
929 byte[] authSafeData; |
|
930 ContentInfo authSafe = new ContentInfo(s); |
|
931 ObjectIdentifier contentType = authSafe.getContentType(); |
|
932 |
|
933 if (contentType.equals(ContentInfo.DATA_OID)) { |
|
934 authSafeData = authSafe.getData(); |
|
935 } else /* signed data */ { |
|
936 throw new IOException("public key protected PKCS12 not supported"); |
|
937 } |
|
938 |
|
939 DerInputStream as = new DerInputStream(authSafeData); |
|
940 DerValue[] safeContentsArray = as.getSequence(2); |
|
941 int count = safeContentsArray.length; |
|
942 |
|
943 /* |
|
944 * Spin over the ContentInfos. |
|
945 */ |
|
946 for (int i = 0; i < count; i++) { |
|
947 byte[] safeContentsData; |
|
948 ContentInfo safeContents; |
|
949 DerInputStream sci; |
|
950 byte[] eAlgId = null; |
|
951 |
|
952 sci = new DerInputStream(safeContentsArray[i].toByteArray()); |
|
953 safeContents = new ContentInfo(sci); |
|
954 contentType = safeContents.getContentType(); |
|
955 safeContentsData = null; |
|
956 |
|
957 if (contentType.equals(ContentInfo.DATA_OID)) { |
|
958 safeContentsData = safeContents.getData(); |
|
959 } else if (contentType.equals(ContentInfo.ENCRYPTED_DATA_OID)) { |
|
960 // The password was used to export the private key from the keychain. |
|
961 // The Keychain won't export the key with encrypted data, so we don't need |
|
962 // to worry about it. |
|
963 continue; |
|
964 } else { |
|
965 throw new IOException("public key protected PKCS12" + |
|
966 " not supported"); |
|
967 } |
|
968 DerInputStream sc = new DerInputStream(safeContentsData); |
|
969 returnValue = extractKeyData(sc); |
|
970 } |
|
971 |
|
972 return returnValue; |
|
973 } |
|
974 |
|
975 @SuppressWarnings("deprecation") |
|
976 private byte[] extractKeyData(DerInputStream stream) |
|
977 throws IOException, NoSuchAlgorithmException, CertificateException |
|
978 { |
|
979 byte[] returnValue = null; |
|
980 DerValue[] safeBags = stream.getSequence(2); |
|
981 int count = safeBags.length; |
|
982 |
|
983 /* |
|
984 * Spin over the SafeBags. |
|
985 */ |
|
986 for (int i = 0; i < count; i++) { |
|
987 ObjectIdentifier bagId; |
|
988 DerInputStream sbi; |
|
989 DerValue bagValue; |
|
990 Object bagItem = null; |
|
991 |
|
992 sbi = safeBags[i].toDerInputStream(); |
|
993 bagId = sbi.getOID(); |
|
994 bagValue = sbi.getDerValue(); |
|
995 if (!bagValue.isContextSpecific((byte)0)) { |
|
996 throw new IOException("unsupported PKCS12 bag value type " |
|
997 + bagValue.tag); |
|
998 } |
|
999 bagValue = bagValue.data.getDerValue(); |
|
1000 if (bagId.equals(PKCS8ShroudedKeyBag_OID)) { |
|
1001 // got what we were looking for. Return it. |
|
1002 returnValue = bagValue.toByteArray(); |
|
1003 } else { |
|
1004 // log error message for "unsupported PKCS12 bag type" |
|
1005 System.out.println("Unsupported bag type '" + bagId + "'"); |
|
1006 } |
|
1007 } |
|
1008 |
|
1009 return returnValue; |
|
1010 } |
|
1011 |
|
1012 /* |
|
1013 * Generate PBE Algorithm Parameters |
|
1014 */ |
|
1015 private AlgorithmParameters getAlgorithmParameters(String algorithm) |
|
1016 throws IOException |
|
1017 { |
|
1018 AlgorithmParameters algParams = null; |
|
1019 |
|
1020 // create PBE parameters from salt and iteration count |
|
1021 PBEParameterSpec paramSpec = |
|
1022 new PBEParameterSpec(getSalt(), iterationCount); |
|
1023 try { |
|
1024 algParams = AlgorithmParameters.getInstance(algorithm); |
|
1025 algParams.init(paramSpec); |
|
1026 } catch (Exception e) { |
|
1027 IOException ioe = |
|
1028 new IOException("getAlgorithmParameters failed: " + |
|
1029 e.getMessage()); |
|
1030 ioe.initCause(e); |
|
1031 throw ioe; |
|
1032 } |
|
1033 return algParams; |
|
1034 } |
|
1035 |
|
1036 // the source of randomness |
|
1037 private SecureRandom random; |
|
1038 |
|
1039 /* |
|
1040 * Generate random salt |
|
1041 */ |
|
1042 private byte[] getSalt() |
|
1043 { |
|
1044 // Generate a random salt. |
|
1045 byte[] salt = new byte[SALT_LEN]; |
|
1046 if (random == null) { |
|
1047 random = new SecureRandom(); |
|
1048 } |
|
1049 salt = random.generateSeed(SALT_LEN); |
|
1050 return salt; |
|
1051 } |
|
1052 |
|
1053 /* |
|
1054 * parse Algorithm Parameters |
|
1055 */ |
|
1056 private AlgorithmParameters parseAlgParameters(DerInputStream in) |
|
1057 throws IOException |
|
1058 { |
|
1059 AlgorithmParameters algParams = null; |
|
1060 try { |
|
1061 DerValue params; |
|
1062 if (in.available() == 0) { |
|
1063 params = null; |
|
1064 } else { |
|
1065 params = in.getDerValue(); |
|
1066 if (params.tag == DerValue.tag_Null) { |
|
1067 params = null; |
|
1068 } |
|
1069 } |
|
1070 if (params != null) { |
|
1071 algParams = AlgorithmParameters.getInstance("PBE"); |
|
1072 algParams.init(params.toByteArray()); |
|
1073 } |
|
1074 } catch (Exception e) { |
|
1075 IOException ioe = |
|
1076 new IOException("parseAlgParameters failed: " + |
|
1077 e.getMessage()); |
|
1078 ioe.initCause(e); |
|
1079 throw ioe; |
|
1080 } |
|
1081 return algParams; |
|
1082 } |
|
1083 |
|
1084 /* |
|
1085 * Generate PBE key |
|
1086 */ |
|
1087 private SecretKey getPBEKey(char[] password) throws IOException |
|
1088 { |
|
1089 SecretKey skey = null; |
|
1090 |
|
1091 try { |
|
1092 PBEKeySpec keySpec = new PBEKeySpec(password); |
|
1093 SecretKeyFactory skFac = SecretKeyFactory.getInstance("PBE"); |
|
1094 skey = skFac.generateSecret(keySpec); |
|
1095 } catch (Exception e) { |
|
1096 IOException ioe = new IOException("getSecretKey failed: " + |
|
1097 e.getMessage()); |
|
1098 ioe.initCause(e); |
|
1099 throw ioe; |
|
1100 } |
|
1101 return skey; |
|
1102 } |
|
1103 |
|
1104 /* |
|
1105 * Encrypt private key using Password-based encryption (PBE) |
|
1106 * as defined in PKCS#5. |
|
1107 * |
|
1108 * NOTE: Currently pbeWithSHAAnd3-KeyTripleDES-CBC algorithmID is |
|
1109 * used to derive the key and IV. |
|
1110 * |
|
1111 * @return encrypted private key encoded as EncryptedPrivateKeyInfo |
|
1112 */ |
|
1113 private byte[] encryptPrivateKey(byte[] data, char[] password) |
|
1114 throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException |
|
1115 { |
|
1116 byte[] key = null; |
|
1117 |
|
1118 try { |
|
1119 // create AlgorithmParameters |
|
1120 AlgorithmParameters algParams = |
|
1121 getAlgorithmParameters("PBEWithSHA1AndDESede"); |
|
1122 |
|
1123 // Use JCE |
|
1124 SecretKey skey = getPBEKey(password); |
|
1125 Cipher cipher = Cipher.getInstance("PBEWithSHA1AndDESede"); |
|
1126 cipher.init(Cipher.ENCRYPT_MODE, skey, algParams); |
|
1127 byte[] encryptedKey = cipher.doFinal(data); |
|
1128 |
|
1129 // wrap encrypted private key in EncryptedPrivateKeyInfo |
|
1130 // as defined in PKCS#8 |
|
1131 AlgorithmId algid = |
|
1132 new AlgorithmId(pbeWithSHAAnd3KeyTripleDESCBC_OID, algParams); |
|
1133 EncryptedPrivateKeyInfo encrInfo = |
|
1134 new EncryptedPrivateKeyInfo(algid, encryptedKey); |
|
1135 key = encrInfo.getEncoded(); |
|
1136 } catch (Exception e) { |
|
1137 UnrecoverableKeyException uke = |
|
1138 new UnrecoverableKeyException("Encrypt Private Key failed: " |
|
1139 + e.getMessage()); |
|
1140 uke.initCause(e); |
|
1141 throw uke; |
|
1142 } |
|
1143 |
|
1144 return key; |
|
1145 } |
|
1146 |
|
1147 |
|
1148 } |
|
1149 |
|