Source code: gnu/classpath/tools/keytool/ImportCmd.java
1 /* ImportCmd.java -- The import command handler of the keytool
2 Copyright (C) 2006 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package gnu.classpath.tools.keytool;
40
41 import gnu.classpath.SystemProperties;
42 import gnu.java.security.x509.X509CertPath;
43
44 import java.io.FileInputStream;
45 import java.io.IOException;
46 import java.security.Key;
47 import java.security.KeyStore;
48 import java.security.KeyStoreException;
49 import java.security.NoSuchAlgorithmException;
50 import java.security.PublicKey;
51 import java.security.UnrecoverableKeyException;
52 import java.security.cert.CertPathValidator;
53 import java.security.cert.CertPathValidatorException;
54 import java.security.cert.Certificate;
55 import java.security.cert.CertificateEncodingException;
56 import java.security.cert.CertificateException;
57 import java.security.cert.CertificateFactory;
58 import java.security.cert.PKIXCertPathValidatorResult;
59 import java.security.cert.PKIXParameters;
60 import java.security.interfaces.DSAParams;
61 import java.security.interfaces.DSAPublicKey;
62 import java.security.interfaces.RSAPublicKey;
63 import java.util.Collection;
64 import java.util.LinkedList;
65 import java.util.logging.Level;
66 import java.util.logging.Logger;
67
68 import javax.security.auth.callback.Callback;
69 import javax.security.auth.callback.ConfirmationCallback;
70 import javax.security.auth.callback.UnsupportedCallbackException;
71
72 /**
73 * The <code>-import</code> keytool command handler is used to read an X.509
74 * certificate, or a PKCS#7 Certificate Reply from a designated input source and
75 * incorporate the certificates into the key store.
76 * <p>
77 * If the <i>Alias</i> does not already exist in the key store, the tool treats
78 * the certificate read from the input source as a new Trusted Certificate. It
79 * then attempts to discover a chain-of-trust, starting from that certificate
80 * and ending at another <i>Trusted Certificate</i>, already stored in the key
81 * store. If the <code>-trustcacerts</code> option is present, an additional
82 * key store, of type <code>JKS</code> named <code>cacerts</code>, and assumed
83 * to be present in <code>${JAVA_HOME}/lib/security</code> will also be
84 * consulted if found --<code>${JAVA_HOME}</code> refers to the location of an
85 * installed Java Runtime Environment (JRE). If no chain-of-trust can be
86 * established, and unless the <code>-noprompt</code> option has been specified,
87 * the certificate is printed to STDOUT and the user is prompted for a
88 * confirmation.
89 * <p>
90 * If <i>Alias</i> exists in the key store, the tool will treat the
91 * certificate(s) read from the input source as a <i>Certificate Reply</i>,
92 * which can be a chain of certificates, that eventually would replace the chain
93 * of certificates associated with the <i>Key Entry</i> of that <i>Alias</i>.
94 * The substitution of the certificates only occurs if a chain-of-trust can be
95 * established between the bottom certificate of the chain read from the input
96 * file and the <i>Trusted Certificates</i> already present in the key store.
97 * Again, if the <code>-trustcacerts</code> option is specified, additional
98 * <i>Trusted Certificates</i> in the same <code>cacerts</code> key store will
99 * be considered. If no chain-of-trust can be established, the operation will
100 * abort.
101 * <p>
102 * Possible options for this command are:
103 * <p>
104 * <dl>
105 * <dt>-alias ALIAS</dt>
106 * <dd>Every entry, be it a <i>Key Entry</i> or a <i>Trusted
107 * Certificate</i>, in a key store is uniquely identified by a user-defined
108 * <i>Alias</i> string. Use this option to specify the <i>Alias</i> to use
109 * when referring to an entry in the key store. Unless specified otherwise,
110 * a default value of <code>mykey</code> shall be used when this option is
111 * omitted from the command line.
112 * <p></dd>
113 *
114 * <dt>-file FILE_NAME</dt>
115 * <dd>The fully qualified path of the file to read from. If omitted, the
116 * tool will process STDIN.
117 * <p></dd>
118 *
119 * <dt>-keypass PASSWORD</dt>
120 * <dd>Use this option to specify the password which the tool will use to
121 * protect the <i>Key Entry</i> associated with the designated <i>Alias</i>,
122 * when replacing this <i>Alias</i>' chain of certificates with that found
123 * in the certificate reply.
124 * <p>
125 * If this option is omitted, and the chain-of-trust for the certificate
126 * reply has been established, the tool will first attempt to unlock the
127 * <i>Key Entry</i> using the same password protecting the key store. If
128 * this fails, you will then be prompted to provide a password.
129 * <p></dd>
130 *
131 * <dt>-noprompt</dt>
132 * <dd>Use this option to prevent the tool from prompting the user.
133 * <p></dd>
134 *
135 * <dt>-trustcacerts</dt>
136 * <dd>Use this option to indicate to the tool that a key store, of type
137 * <code>JKS</code>, named <code>cacerts</code>, and usually located in
138 * <code>lib/security</code> in an installed Java Runtime Environment
139 * should be considered when trying to establish chain-of-trusts.
140 * <p></dd>
141 *
142 * <dt>-storetype STORE_TYP}</dt>
143 * <dd>Use this option to specify the type of the key store to use. The
144 * default value, if this option is omitted, is that of the property
145 * <code>keystore.type</code> in the security properties file, which is
146 * obtained by invoking the {@link java.security.KeyStore#getDefaultType()}
147 * static method.
148 * <p></dd>
149 *
150 * <dt>-keystore URL</dt>
151 * <dd>Use this option to specify the location of the key store to use.
152 * The default value is a file {@link java.net.URL} referencing the file
153 * named <code>.keystore</code> located in the path returned by the call to
154 * {@link java.lang.System#getProperty(String)} using <code>user.home</code>
155 * as argument.
156 * <p>
157 * If a URL was specified, but was found to be malformed --e.g. missing
158 * protocol element-- the tool will attempt to use the URL value as a file-
159 * name (with absolute or relative path-name) of a key store --as if the
160 * protocol was <code>file:</code>.
161 * <p></dd>
162 *
163 * <dt>-storepass PASSWORD</dt>
164 * <dd>Use this option to specify the password protecting the key store. If
165 * this option is omitted from the command line, you will be prompted to
166 * provide a password.
167 * <p></dd>
168 *
169 * <dt>-provider PROVIDER_CLASS_NAME</dt>
170 * <dd>A fully qualified class name of a Security Provider to add to the
171 * current list of Security Providers already installed in the JVM in-use.
172 * If a provider class is specified with this option, and was successfully
173 * added to the runtime --i.e. it was not already installed-- then the tool
174 * will attempt to removed this Security Provider before exiting.
175 * <p></dd>
176 *
177 * <dt>-v</dt>
178 * <dd>Use this option to enable more verbose output.</dd>
179 * </dl>
180 */
181 class ImportCmd extends Command
182 {
183 private static final Logger log = Logger.getLogger(ImportCmd.class.getName());
184 private String _alias;
185 private String _certFileName;
186 private String _password;
187 private boolean noPrompt;
188 private boolean trustCACerts;
189 private String _ksType;
190 private String _ksURL;
191 private String _ksPassword;
192 private String _providerClassName;
193 private CertificateFactory x509Factory;
194 private boolean imported;
195
196 // default 0-arguments constructor
197
198 // public setters -----------------------------------------------------------
199
200 /** @param alias the existing alias to use. */
201 public void setAlias(String alias)
202 {
203 this._alias = alias;
204 }
205
206 /** @param pathName the fully qualified path name of the file to process. */
207 public void setFile(String pathName)
208 {
209 this._certFileName = pathName;
210 }
211
212 /** @param password the existing (private) key password to use. */
213 public void setKeypass(String password)
214 {
215 this._password = password;
216 }
217
218 /**
219 * @param flag whether to prompt, or not, the user to verify certificate
220 * fingerprints.
221 */
222 public void setNoprompt(String flag)
223 {
224 this.noPrompt = Boolean.valueOf(flag).booleanValue();
225 }
226
227 /**
228 * @param flag whether to trust, or not, certificates found in the
229 * <code>cacerts</code> key store.
230 */
231 public void setTrustcacerts(String flag)
232 {
233 this.trustCACerts = Boolean.valueOf(flag).booleanValue();
234 }
235
236 /** @param type the key-store type to use. */
237 public void setStoretype(String type)
238 {
239 this._ksType = type;
240 }
241
242 /** @param url the key-store URL to use. */
243 public void setKeystore(String url)
244 {
245 this._ksURL = url;
246 }
247
248 /** @param password the key-store password to use. */
249 public void setStorepass(String password)
250 {
251 this._ksPassword = password;
252 }
253
254 /** @param className a security provider fully qualified class name to use. */
255 public void setProvider(String className)
256 {
257 this._providerClassName = className;
258 }
259
260 // life-cycle methods -------------------------------------------------------
261
262 int processArgs(String[] args, int i)
263 {
264 int limit = args.length;
265 String opt;
266 while (++i < limit)
267 {
268 opt = args[i];
269 log.finest("args[" + i + "]=" + opt); //$NON-NLS-1$ //$NON-NLS-2$
270 if (opt == null || opt.length() == 0)
271 continue;
272
273 if ("-alias".equals(opt)) // -alias ALIAS //$NON-NLS-1$
274 _alias = args[++i];
275 else if ("-file".equals(opt)) // -file FILE_NAME //$NON-NLS-1$
276 _certFileName = args[++i];
277 else if ("-keypass".equals(opt)) // -keypass PASSWORD //$NON-NLS-1$
278 _password = args[++i];
279 else if ("-noprompt".equals(opt)) //$NON-NLS-1$
280 noPrompt = true;
281 else if ("-trustcacerts".equals(opt)) //$NON-NLS-1$
282 trustCACerts = true;
283 else if ("-storetype".equals(opt)) // -storetype STORE_TYPE //$NON-NLS-1$
284 _ksType = args[++i];
285 else if ("-keystore".equals(opt)) // -keystore URL //$NON-NLS-1$
286 _ksURL = args[++i];
287 else if ("-storepass".equals(opt)) // -storepass PASSWORD //$NON-NLS-1$
288 _ksPassword = args[++i];
289 else if ("-provider".equals(opt)) // -provider PROVIDER_CLASS_NAME //$NON-NLS-1$
290 _providerClassName = args[++i];
291 else if ("-v".equals(opt)) //$NON-NLS-1$
292 verbose = true;
293 else
294 break;
295 }
296
297 return i;
298 }
299
300 void setup() throws Exception
301 {
302 setInputStreamParam(_certFileName);
303 setKeyStoreParams(_providerClassName, _ksType, _ksPassword, _ksURL);
304 setAliasParam(_alias);
305 setKeyPasswordNoPrompt(_password);
306
307 log.finer("-import handler will use the following options:"); //$NON-NLS-1$
308 log.finer(" -alias=" + alias); //$NON-NLS-1$
309 log.finer(" -file=" + _certFileName); //$NON-NLS-1$
310 log.finer(" -keypass=" + _password); //$NON-NLS-1$
311 log.finer(" -noprompt=" + noPrompt); //$NON-NLS-1$
312 log.finer(" -trustcacerts=" + trustCACerts); //$NON-NLS-1$
313 log.finer(" -storetype=" + storeType); //$NON-NLS-1$
314 log.finer(" -keystore=" + storeURL); //$NON-NLS-1$
315 log.finer(" -storepass=" + String.valueOf(storePasswordChars)); //$NON-NLS-1$
316 log.finer(" -provider=" + provider); //$NON-NLS-1$
317 log.finer(" -v=" + verbose); //$NON-NLS-1$
318 }
319
320 void start() throws CertificateException, KeyStoreException, IOException,
321 UnsupportedCallbackException, NoSuchAlgorithmException,
322 CertPathValidatorException, UnrecoverableKeyException
323 {
324 log.entering(this.getClass().getName(), "start"); //$NON-NLS-1$
325
326 x509Factory = CertificateFactory.getInstance("X.509"); //$NON-NLS-1$
327 // the alias will tell us whether we're dealing with
328 // a new trusted certificate or a certificate reply
329 if (! store.containsAlias(alias))
330 importNewTrustedCertificate();
331 else
332 {
333 ensureAliasIsKeyEntry();
334 importCertificateReply();
335 }
336
337 log.exiting(this.getClass().getName(), "start"); //$NON-NLS-1$
338 }
339
340 // own methods --------------------------------------------------------------
341
342 /**
343 * When importing a new trusted certificate, <i>alias</i> MUST NOT yet exist
344 * in the key store.
345 * <p>
346 * Before adding the certificate to the key store and associate it with the
347 * designated Alias, this method tries to verify it by attempting to construct
348 * a chain of trust from that certificate to a self-signed certificate
349 * (belonging to a root CA), using (already) trusted certificates that are
350 * available in the key store.
351 * <p>
352 * If the <code>-trustcacerts</code> option was detected on the command
353 * line, additional trusted certificates are considered for establishing the
354 * chain of trust. Those additional certificates are assumed to be in a key
355 * store, of type <code>JKS</code> named <code>cacerts</code> and usually
356 * located in <code>${JAVA_HOME}/lib/security</code>, where
357 * <code>${JAVA_HOME}</code> is the root folder location of a Java runtime.
358 * <p>
359 * If this method fails to establish a trust path from the certificate to be
360 * imported up to a trusted self-signed certificate, the certificate is
361 * printed to <code>STDOUT</code>, and the user is prompted to verify it,
362 * with the option of aborting the import operation. If however the option
363 * <code>-noprompt</code> was detected on the command line, no interaction
364 * with the user will take place and the import operation will abort.
365 *
366 * @throws CertificateException
367 * @throws KeyStoreException
368 * @throws NoSuchAlgorithmException
369 * @throws UnsupportedCallbackException
370 * @throws IOException
371 * @throws UnrecoverableKeyException
372 * @throws CertPathValidatorException
373 */
374 private void importNewTrustedCertificate() throws CertificateException,
375 KeyStoreException, NoSuchAlgorithmException, IOException,
376 UnsupportedCallbackException, CertPathValidatorException,
377 UnrecoverableKeyException
378 {
379 log.entering(this.getClass().getName(), "importNewTrustedCertificate"); //$NON-NLS-1$
380
381 Certificate certificate = x509Factory.generateCertificate(inStream);
382 log.finest("certificate = " + certificate); //$NON-NLS-1$
383 LinkedList orderedReply = new LinkedList();
384 orderedReply.addLast(certificate);
385
386 if (findTrustAndUpdate(orderedReply, ! noPrompt))
387 {
388 store.setCertificateEntry(alias, certificate);
389 System.out.println(Messages.getString("ImportCmd.29")); //$NON-NLS-1$
390 saveKeyStore();
391 }
392 else
393 System.out.println(Messages.getString("ImportCmd.28")); //$NON-NLS-1$
394
395 log.exiting(this.getClass().getName(), "importNewTrustedCertificate"); //$NON-NLS-1$
396 }
397
398 /**
399 * A certificate reply is a certificate, whose Owner is stored in the key
400 * store associated to the designated Alias, and now signed by supposedly a
401 * trusted CA (Certificate Authority). In other words, the Subject in this
402 * certificate reply is Alias's own and the Issuer is a CA.
403 * <p>
404 * When importing a certificate reply, the reply is validated using trusted
405 * certificates from the key store, and optionally (if the option
406 * <code>-trustcacerts</code> was detected on the command line) certificates
407 * found in the key store, of type <code>JKS</code> named <code>cacerts</code>
408 * located in <code>${JAVA_HOME}/lib/security</code>, where
409 * <code>${JAVA_HOME}</code> is the root folder location of a Java runtime.
410 *
411 * @throws CertificateException
412 * @throws UnsupportedCallbackException
413 * @throws IOException
414 * @throws KeyStoreException
415 * @throws CertPathValidatorException
416 * @throws NoSuchAlgorithmException
417 * @throws UnrecoverableKeyException
418 */
419 private void importCertificateReply() throws CertificateException,
420 IOException, UnsupportedCallbackException, KeyStoreException,
421 NoSuchAlgorithmException, CertPathValidatorException,
422 UnrecoverableKeyException
423 {
424 log.entering(this.getClass().getName(), "importCertificateReply"); //$NON-NLS-1$
425
426 Collection certificates = x509Factory.generateCertificates(inStream);
427 ensureReplyIsOurs(certificates);
428 // we now have established that the public keys are the same.
429 // find a chain-of-trust if one exists
430 if (certificates.size() == 1)
431 importCertificate((Certificate) certificates.iterator().next());
432 else
433 importChain(certificates);
434
435 log.exiting(this.getClass().getName(), "importCertificateReply"); //$NON-NLS-1$
436 }
437
438 /**
439 * If the reply is a single X.509 certificate, keytool attempts to establish a
440 * trust chain, starting at the certificate reply and ending at a self-signed
441 * certificate (belonging to a root CA). The certificate reply and the
442 * hierarchy of certificates used to authenticate the certificate reply form
443 * the new certificate chain of alias. If a trust chain cannot be established,
444 * the certificate reply is not imported. In this case, keytool does not print
445 * out the certificate, nor does it prompt the user to verify it. This is
446 * because it is very hard (if not impossible) for a user to determine the
447 * authenticity of the certificate reply.
448 *
449 * @param certificate the certificate reply to import into the key store.
450 * @throws NoSuchAlgorithmException
451 * @throws CertPathValidatorException
452 * @throws UnsupportedCallbackException
453 * @throws IOException
454 * @throws UnrecoverableKeyException
455 * @throws KeyStoreException
456 * @throws CertificateException
457 */
458 private void importCertificate(Certificate certificate)
459 throws NoSuchAlgorithmException, CertPathValidatorException,
460 KeyStoreException, UnrecoverableKeyException, IOException,
461 UnsupportedCallbackException, CertificateException
462 {
463 log.entering(this.getClass().getName(), "importCertificate", certificate); //$NON-NLS-1$
464
465 LinkedList reply = new LinkedList();
466 reply.addLast(certificate);
467
468 if (! findTrustAndUpdate(reply, false))
469 throw new CertPathValidatorException(Messages.getString("ImportCmd.34")); //$NON-NLS-1$
470
471 Certificate[] newChain = (Certificate[]) reply.toArray(new Certificate[0]);
472 Key privateKey = getAliasPrivateKey();
473 store.setKeyEntry(alias, privateKey, keyPasswordChars, newChain);
474 saveKeyStore();
475
476 log.exiting(this.getClass().getName(), "importCertificate"); //$NON-NLS-1$
477 }
478
479 /**
480 * If the reply is a PKCS#7 formatted certificate chain, the chain is first
481 * ordered (with the user certificate first and the self-signed root CA
482 * certificate last), before keytool attempts to match the root CA certificate
483 * provided in the reply with any of the trusted certificates in the key store
484 * or the "cacerts" keystore file (if the -trustcacerts option was specified).
485 * If no match can be found, the information of the root CA certificate is
486 * printed out, and the user is prompted to verify it, e.g., by comparing the
487 * displayed certificate fingerprints with the fingerprints obtained from some
488 * other (trusted) source of information, which might be the root CA itself.
489 * The user then has the option of aborting the import operation. If the
490 * -noprompt option is given, however, there will be no interaction with the
491 * user.
492 *
493 * @param chain the collection of certificates parsed from the user
494 * designated input.
495 * @throws UnsupportedCallbackException
496 * @throws IOException
497 * @throws UnrecoverableKeyException
498 * @throws KeyStoreException
499 * @throws CertPathValidatorException
500 * @throws NoSuchAlgorithmException
501 * @throws CertificateException
502 */
503 private void importChain(Collection chain) throws NoSuchAlgorithmException,
504 CertPathValidatorException, KeyStoreException, UnrecoverableKeyException,
505 IOException, UnsupportedCallbackException, CertificateException
506 {
507 log.entering(this.getClass().getName(), "importChain", chain); //$NON-NLS-1$
508
509 LinkedList reply = orderChain(chain);
510 if (findTrustAndUpdate(reply, ! noPrompt))
511 {
512 Certificate[] newChain = (Certificate[]) reply.toArray(new Certificate[0]);
513 Key privateKey = getAliasPrivateKey();
514 store.setKeyEntry(alias, privateKey, keyPasswordChars, newChain);
515 saveKeyStore();
516 }
517
518 log.exiting(this.getClass().getName(), "importChain"); //$NON-NLS-1$
519 }
520
521 /**
522 * Check to ensure that alias's public key is the subject of the first
523 * certificate in the passed certificate collection. Throws an exception if
524 * the public keys do not match.
525 *
526 * @param certificates a {@link Collection} of certificate replies (either a
527 * signle certificate reply, or a PKCS#7 certificate reply chain)
528 * usually sent by a CA as a response to a Certificate Signing
529 * Request (CSR).
530 * @throws IOException
531 * @throws UnsupportedCallbackException
532 * @throws KeyStoreException
533 */
534 private void ensureReplyIsOurs(Collection certificates) throws IOException,
535 UnsupportedCallbackException, KeyStoreException
536 {
537 log.entering(this.getClass().getName(), "ensureReplyIsOurs"); //$NON-NLS-1$
538
539 Certificate certificate = (Certificate) certificates.iterator().next();
540 log.finest("certificate = " + certificate); //$NON-NLS-1$
541 Certificate[] chain = store.getCertificateChain(alias);
542 if (chain == null)
543 throw new IllegalArgumentException(Messages.getFormattedString("ImportCmd.37", //$NON-NLS-1$
544 alias));
545 Certificate anchor = chain[0];
546 PublicKey anchorPublicKey = anchor.getPublicKey();
547 PublicKey certPublicKey = certificate.getPublicKey();
548 boolean sameKey;
549 if (anchorPublicKey instanceof DSAPublicKey)
550 {
551 DSAPublicKey pk1 = (DSAPublicKey) anchorPublicKey;
552 if (!(certPublicKey instanceof DSAPublicKey))
553 throw new IllegalArgumentException(Messages.getString("ImportCmd.38")); //$NON-NLS-1$
554
555 sameKey = areEqual(pk1, (DSAPublicKey) certPublicKey);
556 }
557 else if (anchorPublicKey instanceof RSAPublicKey)
558 {
559 RSAPublicKey pk1 = (RSAPublicKey) anchorPublicKey;
560 if (!(certPublicKey instanceof RSAPublicKey))
561 throw new IllegalArgumentException(Messages.getString("ImportCmd.38")); //$NON-NLS-1$
562
563 sameKey = areEqual(pk1, (RSAPublicKey) certPublicKey);
564 }
565 else
566 throw new IllegalArgumentException(
567 Messages.getFormattedString("ImportCmd.40", //$NON-NLS-1$
568 new String[] { alias,
569 anchorPublicKey.getClass().getName() }));
570 if (! sameKey)
571 throw new IllegalArgumentException(Messages.getString("ImportCmd.41")); //$NON-NLS-1$
572
573 log.exiting(this.getClass().getName(), "ensureReplyIsOurs"); //$NON-NLS-1$
574 }
575
576 private boolean areEqual(DSAPublicKey pk1, DSAPublicKey pk2)
577 {
578 if (pk1.getY().compareTo(pk2.getY()) != 0)
579 return false;
580
581 DSAParams p1 = pk1.getParams();
582 DSAParams p2 = pk2.getParams();
583 if (p1.getG().compareTo(p2.getG()) != 0)
584 return false;
585
586 if (p1.getP().compareTo(p2.getP()) != 0)
587 return false;
588
589 return p1.getQ().compareTo(p2.getQ()) == 0;
590 }
591
592 private boolean areEqual(RSAPublicKey pk1, RSAPublicKey pk2)
593 {
594 if (pk1.getPublicExponent().compareTo(pk2.getPublicExponent()) != 0)
595 return false;
596
597 return pk1.getModulus().compareTo(pk2.getModulus()) == 0;
598 }
599
600 /**
601 * @param chain
602 * @return the input collection, ordered with own certificate first, and CA's
603 * self-signed certificate last.
604 */
605 private LinkedList orderChain(Collection chain)
606 {
607 log.entering(this.getClass().getName(), "orderChain"); //$NON-NLS-1$
608
609 LinkedList result = new LinkedList();
610
611
612 log.entering(this.getClass().getName(), "orderChain", result); //$NON-NLS-1$
613 return result;
614 }
615
616 /**
617 * Given an ordered list of certificates, this method attempts to validate the
618 * chain, and if successful, updates the key store entry for the designated
619 * alias. The list of certificates is expected to be ordered as a chain, where
620 * the first is the alias's own certificate and the last being a self-signed
621 * CA certificate.
622 * <p>
623 * if <code>promptUser</code> is <code>true</code>, then even if no
624 * anchor trust certificate is found, the user is prompted to approve, or not,
625 * the import operation. On the other hand if the <code>promptUser</code>
626 * parameter is <code>false</code> then this method will throw an exception
627 * if no trust anchor is to be found.
628 *
629 * @param reply an ordered certificate path, where the last entry is the CA's
630 * self-signed certificate.
631 * @param promptUser a boolean flag indicating whether or not to prompt the
632 * user for explicit trust in a CA certificate.
633 * @return <code>true</code> if the validation succeeds; or <code>false</code>
634 * otherwise.
635 * @throws NoSuchAlgorithmException
636 * @throws CertPathValidatorException
637 * @throws UnsupportedCallbackException
638 * @throws IOException
639 * @throws UnrecoverableKeyException
640 * @throws KeyStoreException
641 * @throws CertificateEncodingException
642 */
643 private boolean findTrustAndUpdate(LinkedList reply, boolean promptUser)
644 throws IOException, NoSuchAlgorithmException, CertPathValidatorException,
645 KeyStoreException, UnrecoverableKeyException, UnsupportedCallbackException,
646 CertificateEncodingException
647 {
648 log.entering(this.getClass().getName(), "findTrustAndUpdate"); //$NON-NLS-1$
649
650 X509CertPath certPath = new X509CertPath(reply);
651 CertPathValidator validator = CertPathValidator.getInstance("PKIX"); //$NON-NLS-1$
652 PKIXCertPathValidatorResult cpvr = findTrustInStore(certPath, validator);
653 if (cpvr == null && trustCACerts)
654 cpvr = findTrustInCACerts(certPath, validator);
655
656 boolean result = false;
657 if (cpvr == null)
658 {
659 if (promptUser)
660 {
661 printVerbose((Certificate) reply.getLast());
662 ConfirmationCallback ccb;
663 ccb = new ConfirmationCallback(Messages.getString("ImportCmd.32"), //$NON-NLS-1$
664 ConfirmationCallback.INFORMATION,
665 ConfirmationCallback.YES_NO_OPTION,
666 ConfirmationCallback.NO);
667 getCallbackHandler().handle(new Callback[] { ccb });
668 int answer = ccb.getSelectedIndex();
669 result = answer == ConfirmationCallback.YES;
670 }
671 }
672 else
673 {
674 log.fine("Found a chain-of-trust anchored by " + cpvr.getTrustAnchor()); //$NON-NLS-1$
675 Certificate trustedCert = cpvr.getTrustAnchor().getTrustedCert();
676 reply.addLast(trustedCert);
677 result = true;
678 }
679
680 log.entering(this.getClass().getName(), "findTrustAndUpdate", //$NON-NLS-1$
681 Boolean.valueOf(result));
682 return result;
683 }
684
685 private PKIXCertPathValidatorResult findTrustInStore(X509CertPath certPath,
686 CertPathValidator validator)
687 {
688 log.entering(this.getClass().getName(), "findTrustInStore"); //$NON-NLS-1$
689
690 PKIXCertPathValidatorResult result;
691 try
692 {
693 PKIXParameters params = new PKIXParameters(store);
694 result = (PKIXCertPathValidatorResult) validator.validate(certPath, params);
695 }
696 catch (Exception x)
697 {
698 log.log(Level.FINE,
699 "Exception in findTrustInStore(). Ignore + Return NULL", //$NON-NLS-1$
700 x);
701 result = null;
702 }
703
704 log.exiting(this.getClass().getName(), "findTrustInStore", result); //$NON-NLS-1$
705 return result;
706 }
707
708 private PKIXCertPathValidatorResult findTrustInCACerts(X509CertPath certPath,
709 CertPathValidator validator)
710 {
711 log.entering(this.getClass().getName(), "findTrustInCACerts"); //$NON-NLS-1$
712
713 FileInputStream stream = null;
714 PKIXCertPathValidatorResult result = null;
715 try
716 {
717 KeyStore cacerts = KeyStore.getInstance("jks"); //$NON-NLS-1$
718 String cacertsPath = SystemProperties.getProperty("java.home");
719 String fs = SystemProperties.getProperty("file.separator"); //$NON-NLS-1$
720 cacertsPath = new StringBuilder(cacertsPath).append(fs)
721 .append("lib").append(fs) //$NON-NLS-1$
722 .append("security").append(fs) //$NON-NLS-1$
723 .append("cacerts").toString(); //$NON-NLS-1$
724 stream = new FileInputStream(cacertsPath);
725 cacerts.load(stream, "changeit".toCharArray()); //$NON-NLS-1$
726 PKIXParameters params = new PKIXParameters(cacerts);
727 result = (PKIXCertPathValidatorResult) validator.validate(certPath,
728 params);
729 }
730 catch (Exception x)
731 {
732 log.log(Level.FINE,
733 "Exception in findTrustInCACerts(). Ignore + Return NULL", //$NON-NLS-1$
734 x);
735 }
736 finally
737 {
738 if (stream != null)
739 try
740 {
741 stream.close();
742 }
743 catch (Exception ignored)
744 {
745 }
746 }
747
748 log.exiting(this.getClass().getName(), "findTrustInCACerts", result); //$NON-NLS-1$
749 return result;
750 }
751 }