1 /* ImportCmd.java -- The import command handler of the keytool
2 Copyright (C) 2006 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
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)
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.
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
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
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. */
39 package gnu.classpath.tools.keytool;
41 import gnu.classpath.SystemProperties;
42 import gnu.classpath.tools.getopt.ClasspathToolParser;
43 import gnu.classpath.tools.getopt.Option;
44 import gnu.classpath.tools.getopt.OptionException;
45 import gnu.classpath.tools.getopt.OptionGroup;
46 import gnu.classpath.tools.getopt.Parser;
47 import gnu.java.security.x509.X509CertPath;
49 import java.io.FileInputStream;
50 import java.io.IOException;
51 import java.security.Key;
52 import java.security.KeyStore;
53 import java.security.KeyStoreException;
54 import java.security.NoSuchAlgorithmException;
55 import java.security.Principal;
56 import java.security.PublicKey;
57 import java.security.UnrecoverableKeyException;
58 import java.security.cert.CertPathValidator;
59 import java.security.cert.CertPathValidatorException;
60 import java.security.cert.Certificate;
61 import java.security.cert.CertificateEncodingException;
62 import java.security.cert.CertificateException;
63 import java.security.cert.CertificateFactory;
64 import java.security.cert.PKIXCertPathValidatorResult;
65 import java.security.cert.PKIXParameters;
66 import java.security.cert.TrustAnchor;
67 import java.security.cert.X509Certificate;
68 import java.security.interfaces.DSAParams;
69 import java.security.interfaces.DSAPublicKey;
70 import java.security.interfaces.RSAPublicKey;
71 import java.util.Collection;
72 import java.util.LinkedList;
73 import java.util.ListIterator;
74 import java.util.logging.Level;
75 import java.util.logging.Logger;
77 import javax.security.auth.callback.Callback;
78 import javax.security.auth.callback.ConfirmationCallback;
79 import javax.security.auth.callback.UnsupportedCallbackException;
82 * The <code>-import</code> keytool command handler is used to read an X.509
83 * certificate, or a PKCS#7 Certificate Reply from a designated input source and
84 * incorporate the certificates into the key store.
86 * If the <i>Alias</i> does not already exist in the key store, the tool treats
87 * the certificate read from the input source as a new Trusted Certificate. It
88 * then attempts to discover a chain-of-trust, starting from that certificate
89 * and ending at another <i>Trusted Certificate</i>, already stored in the key
90 * store. If the <code>-trustcacerts</code> option is present, an additional
91 * key store, of type <code>JKS</code> named <code>cacerts</code>, and assumed
92 * to be present in <code>${JAVA_HOME}/lib/security</code> will also be
93 * consulted if found --<code>${JAVA_HOME}</code> refers to the location of an
94 * installed Java Runtime Environment (JRE). If no chain-of-trust can be
95 * established, and unless the <code>-noprompt</code> option has been specified,
96 * the certificate is printed to STDOUT and the user is prompted for a
99 * If <i>Alias</i> exists in the key store, the tool will treat the
100 * certificate(s) read from the input source as a <i>Certificate Reply</i>,
101 * which can be a chain of certificates, that eventually would replace the chain
102 * of certificates associated with the <i>Key Entry</i> of that <i>Alias</i>.
103 * The substitution of the certificates only occurs if a chain-of-trust can be
104 * established between the bottom certificate of the chain read from the input
105 * file and the <i>Trusted Certificates</i> already present in the key store.
106 * Again, if the <code>-trustcacerts</code> option is specified, additional
107 * <i>Trusted Certificates</i> in the same <code>cacerts</code> key store will
108 * be considered. If no chain-of-trust can be established, the operation will
111 * Possible options for this command are:
114 * <dt>-alias ALIAS</dt>
115 * <dd>Every entry, be it a <i>Key Entry</i> or a <i>Trusted
116 * Certificate</i>, in a key store is uniquely identified by a user-defined
117 * <i>Alias</i> string. Use this option to specify the <i>Alias</i> to use
118 * when referring to an entry in the key store. Unless specified otherwise,
119 * a default value of <code>mykey</code> shall be used when this option is
120 * omitted from the command line.
123 * <dt>-file FILE_NAME</dt>
124 * <dd>The fully qualified path of the file to read from. If omitted, the
125 * tool will process STDIN.
128 * <dt>-keypass PASSWORD</dt>
129 * <dd>Use this option to specify the password which the tool will use to
130 * protect the <i>Key Entry</i> associated with the designated <i>Alias</i>,
131 * when replacing this <i>Alias</i>' chain of certificates with that found
132 * in the certificate reply.
134 * If this option is omitted, and the chain-of-trust for the certificate
135 * reply has been established, the tool will first attempt to unlock the
136 * <i>Key Entry</i> using the same password protecting the key store. If
137 * this fails, you will then be prompted to provide a password.
141 * <dd>Use this option to prevent the tool from prompting the user.
144 * <dt>-trustcacerts</dt>
145 * <dd>Use this option to indicate to the tool that a key store, of type
146 * <code>JKS</code>, named <code>cacerts</code>, and usually located in
147 * <code>lib/security</code> in an installed Java Runtime Environment
148 * should be considered when trying to establish chain-of-trusts.
151 * <dt>-storetype STORE_TYPE</dt>
152 * <dd>Use this option to specify the type of the key store to use. The
153 * default value, if this option is omitted, is that of the property
154 * <code>keystore.type</code> in the security properties file, which is
155 * obtained by invoking the {@link java.security.KeyStore#getDefaultType()}
159 * <dt>-keystore URL</dt>
160 * <dd>Use this option to specify the location of the key store to use.
161 * The default value is a file {@link java.net.URL} referencing the file
162 * named <code>.keystore</code> located in the path returned by the call to
163 * {@link java.lang.System#getProperty(String)} using <code>user.home</code>
166 * If a URL was specified, but was found to be malformed --e.g. missing
167 * protocol element-- the tool will attempt to use the URL value as a file-
168 * name (with absolute or relative path-name) of a key store --as if the
169 * protocol was <code>file:</code>.
172 * <dt>-storepass PASSWORD</dt>
173 * <dd>Use this option to specify the password protecting the key store. If
174 * this option is omitted from the command line, you will be prompted to
175 * provide a password.
178 * <dt>-provider PROVIDER_CLASS_NAME</dt>
179 * <dd>A fully qualified class name of a Security Provider to add to the
180 * current list of Security Providers already installed in the JVM in-use.
181 * If a provider class is specified with this option, and was successfully
182 * added to the runtime --i.e. it was not already installed-- then the tool
183 * will attempt to removed this Security Provider before exiting.
187 * <dd>Use this option to enable more verbose output.</dd>
190 class ImportCmd extends Command
192 private static final Logger log = Logger.getLogger(ImportCmd.class.getName());
193 private static final String GKR = "gkr"; //$NON-NLS-1$
194 private static final String JKS = "jks"; //$NON-NLS-1$
195 private static final String LIB = "lib"; //$NON-NLS-1$
196 private static final String SECURITY = "security"; //$NON-NLS-1$
197 private static final String CACERTS = "cacerts"; //$NON-NLS-1$
198 private static final String CACERTS_GKR = CACERTS + "." + GKR; //$NON-NLS-1$
199 protected String _alias;
200 protected String _certFileName;
201 protected String _password;
202 protected boolean noPrompt;
203 protected boolean trustCACerts;
204 protected String _ksType;
205 protected String _ksURL;
206 protected String _ksPassword;
207 protected String _providerClassName;
208 private CertificateFactory x509Factory;
209 private boolean imported;
211 * Pathname to a GKR-type cacerts file to use when trustCACerts is true. This
212 * is usually a file named "cacerts.gkr" located in lib/security in the folder
213 * specified by the system-property "gnu.classpath.home".
215 private String gkrCaCertsPathName;
217 * Pathname to a JKS-type cacerts file to use when trustCACerts is true. This
218 * is usually a file named "cacerts" located in lib/security in the folder
219 * specified by the system-property "java.home".
221 private String jksCaCertsPathName;
222 /** Alias self-signed certificate. used when importing certificate replies. */
223 private X509Certificate selfSignedCertificate;
225 // default 0-arguments constructor
227 // public setters -----------------------------------------------------------
229 /** @param alias the existing alias to use. */
230 public void setAlias(String alias)
235 /** @param pathName the fully qualified path name of the file to process. */
236 public void setFile(String pathName)
238 this._certFileName = pathName;
241 /** @param password the existing (private) key password to use. */
242 public void setKeypass(String password)
244 this._password = password;
248 * @param flag whether to prompt, or not, the user to verify certificate
251 public void setNoprompt(String flag)
253 this.noPrompt = Boolean.valueOf(flag).booleanValue();
257 * @param flag whether to trust, or not, certificates found in the
258 * <code>cacerts</code> key store.
260 public void setTrustcacerts(String flag)
262 this.trustCACerts = Boolean.valueOf(flag).booleanValue();
265 /** @param type the key-store type to use. */
266 public void setStoretype(String type)
271 /** @param url the key-store URL to use. */
272 public void setKeystore(String url)
277 /** @param password the key-store password to use. */
278 public void setStorepass(String password)
280 this._ksPassword = password;
283 /** @param className a security provider fully qualified class name to use. */
284 public void setProvider(String className)
286 this._providerClassName = className;
289 // life-cycle methods -------------------------------------------------------
291 void setup() throws Exception
293 setInputStreamParam(_certFileName);
294 setKeyStoreParams(_providerClassName, _ksType, _ksPassword, _ksURL);
295 setAliasParam(_alias);
296 setKeyPasswordNoPrompt(_password);
298 log.finer("-import handler will use the following options:"); //$NON-NLS-1$
299 log.finer(" -alias=" + alias); //$NON-NLS-1$
300 log.finer(" -file=" + _certFileName); //$NON-NLS-1$
301 log.finer(" -noprompt=" + noPrompt); //$NON-NLS-1$
302 log.finer(" -trustcacerts=" + trustCACerts); //$NON-NLS-1$
303 log.finer(" -storetype=" + storeType); //$NON-NLS-1$
304 log.finer(" -keystore=" + storeURL); //$NON-NLS-1$
305 log.finer(" -provider=" + provider); //$NON-NLS-1$
306 log.finer(" -v=" + verbose); //$NON-NLS-1$
309 void start() throws CertificateException, KeyStoreException, IOException,
310 UnsupportedCallbackException, NoSuchAlgorithmException,
311 CertPathValidatorException, UnrecoverableKeyException
313 log.entering(this.getClass().getName(), "start"); //$NON-NLS-1$
317 String fs = SystemProperties.getProperty("file.separator"); //$NON-NLS-1$
318 String classpathHome = SystemProperties.getProperty("gnu.classpath.home"); //$NON-NLS-1$
319 gkrCaCertsPathName = new StringBuilder(classpathHome).append(fs)
320 .append(LIB).append(fs)
321 .append(SECURITY).append(fs)
322 .append(CACERTS_GKR).toString();
323 String javaHome = SystemProperties.getProperty("java.home"); //$NON-NLS-1$
324 jksCaCertsPathName = new StringBuilder(javaHome).append(fs)
325 .append(LIB).append(fs)
326 .append(SECURITY).append(fs)
327 .append(CACERTS).toString();
329 x509Factory = CertificateFactory.getInstance("X.509"); //$NON-NLS-1$
330 // the alias will tell us whether we're dealing with
331 // a new trusted certificate or a certificate reply
332 if (! store.containsAlias(alias))
333 importNewTrustedCertificate();
336 ensureAliasIsKeyEntry();
337 importCertificateReply();
340 log.exiting(this.getClass().getName(), "start"); //$NON-NLS-1$
343 // own methods --------------------------------------------------------------
347 log.entering(this.getClass().getName(), "getParser"); //$NON-NLS-1$
349 Parser result = new ClasspathToolParser(Main.IMPORT_CMD, true);
350 result.setHeader(Messages.getString("ImportCmd.27")); //$NON-NLS-1$
351 result.setFooter(Messages.getString("ImportCmd.26")); //$NON-NLS-1$
352 OptionGroup options = new OptionGroup(Messages.getString("ImportCmd.25")); //$NON-NLS-1$
353 options.add(new Option(Main.ALIAS_OPT,
354 Messages.getString("ImportCmd.24"), //$NON-NLS-1$
355 Messages.getString("ImportCmd.23")) //$NON-NLS-1$
357 public void parsed(String argument) throws OptionException
362 options.add(new Option(Main.FILE_OPT,
363 Messages.getString("ImportCmd.22"), //$NON-NLS-1$
364 Messages.getString("ImportCmd.21")) //$NON-NLS-1$
366 public void parsed(String argument) throws OptionException
368 _certFileName = argument;
371 options.add(new Option(Main.KEYPASS_OPT,
372 Messages.getString("ImportCmd.20"), //$NON-NLS-1$
373 Messages.getString("ImportCmd.19")) //$NON-NLS-1$
375 public void parsed(String argument) throws OptionException
377 _password = argument;
380 options.add(new Option("noprompt", //$NON-NLS-1$
381 Messages.getString("ImportCmd.18")) //$NON-NLS-1$
383 public void parsed(String argument) throws OptionException
388 options.add(new Option("trustcacerts", //$NON-NLS-1$
389 Messages.getString("ImportCmd.17")) //$NON-NLS-1$
391 public void parsed(String argument) throws OptionException
396 options.add(new Option(Main.STORETYPE_OPT,
397 Messages.getString("ImportCmd.16"), //$NON-NLS-1$
398 Messages.getString("ImportCmd.15")) //$NON-NLS-1$
400 public void parsed(String argument) throws OptionException
405 options.add(new Option(Main.KEYSTORE_OPT,
406 Messages.getString("ImportCmd.14"), //$NON-NLS-1$
407 Messages.getString("ImportCmd.13")) //$NON-NLS-1$
409 public void parsed(String argument) throws OptionException
414 options.add(new Option(Main.STOREPASS_OPT,
415 Messages.getString("ImportCmd.12"), //$NON-NLS-1$
416 Messages.getString("ImportCmd.11")) //$NON-NLS-1$
418 public void parsed(String argument) throws OptionException
420 _ksPassword = argument;
423 options.add(new Option(Main.PROVIDER_OPT,
424 Messages.getString("ImportCmd.10"), //$NON-NLS-1$
425 Messages.getString("ImportCmd.9")) //$NON-NLS-1$
427 public void parsed(String argument) throws OptionException
429 _providerClassName = argument;
432 options.add(new Option(Main.VERBOSE_OPT,
433 Messages.getString("ImportCmd.8")) //$NON-NLS-1$
435 public void parsed(String argument) throws OptionException
442 log.exiting(this.getClass().getName(), "getParser", result); //$NON-NLS-1$
447 * When importing a new trusted certificate, <i>alias</i> MUST NOT yet exist
450 * Before adding the certificate to the key store and associate it with the
451 * designated Alias, this method tries to verify it by attempting to construct
452 * a chain of trust from that certificate to a self-signed certificate
453 * (belonging to a root CA), using (already) trusted certificates that are
454 * available in the key store.
456 * If the <code>-trustcacerts</code> option was detected on the command
457 * line, additional trusted certificates are considered for establishing the
458 * chain of trust. Those additional certificates are assumed to be in a key
459 * store, of type <code>JKS</code> named <code>cacerts</code> and usually
460 * located in <code>${JAVA_HOME}/lib/security</code>, where
461 * <code>${JAVA_HOME}</code> is the root folder location of a Java runtime.
463 * If this method fails to establish a trust path from the certificate to be
464 * imported up to a trusted self-signed certificate, the certificate is
465 * printed to <code>STDOUT</code>, and the user is prompted to verify it,
466 * with the option of aborting the import operation. If however the option
467 * <code>-noprompt</code> was detected on the command line, no interaction
468 * with the user will take place and the import operation will abort.
470 * @throws CertificateException
471 * @throws KeyStoreException
472 * @throws NoSuchAlgorithmException
473 * @throws UnsupportedCallbackException
474 * @throws IOException
475 * @throws UnrecoverableKeyException
476 * @throws CertPathValidatorException
478 private void importNewTrustedCertificate() throws CertificateException,
479 KeyStoreException, NoSuchAlgorithmException, IOException,
480 UnsupportedCallbackException, CertPathValidatorException,
481 UnrecoverableKeyException
483 log.entering(this.getClass().getName(), "importNewTrustedCertificate"); //$NON-NLS-1$
485 Certificate certificate = x509Factory.generateCertificate(inStream);
486 log.finest("certificate = " + certificate); //$NON-NLS-1$
487 LinkedList orderedReply = new LinkedList();
488 orderedReply.addLast(certificate);
490 if (findTrustAndUpdate(orderedReply, ! noPrompt))
492 store.setCertificateEntry(alias, certificate);
493 System.out.println(Messages.getString("ImportCmd.29")); //$NON-NLS-1$
497 System.out.println(Messages.getString("ImportCmd.28")); //$NON-NLS-1$
499 log.exiting(this.getClass().getName(), "importNewTrustedCertificate"); //$NON-NLS-1$
503 * A certificate reply is a certificate, whose Owner is stored in the key
504 * store associated to the designated Alias, and now signed by supposedly a
505 * trusted CA (Certificate Authority). In other words, the Subject in this
506 * certificate reply is Alias's own and the Issuer is a CA.
508 * When importing a certificate reply, the reply is validated using trusted
509 * certificates from the key store, and optionally (if the option
510 * <code>-trustcacerts</code> was detected on the command line) certificates
511 * found in the key store, of type <code>JKS</code> named <code>cacerts</code>
512 * located in <code>${JAVA_HOME}/lib/security</code>, where
513 * <code>${JAVA_HOME}</code> is the root folder location of a Java runtime.
515 * @throws CertificateException
516 * @throws UnsupportedCallbackException
517 * @throws IOException
518 * @throws KeyStoreException
519 * @throws CertPathValidatorException
520 * @throws NoSuchAlgorithmException
521 * @throws UnrecoverableKeyException
523 private void importCertificateReply() throws CertificateException,
524 IOException, UnsupportedCallbackException, KeyStoreException,
525 NoSuchAlgorithmException, CertPathValidatorException,
526 UnrecoverableKeyException
528 log.entering(this.getClass().getName(), "importCertificateReply"); //$NON-NLS-1$
530 Collection certificates = x509Factory.generateCertificates(inStream);
531 ensureReplyIsOurs(certificates);
532 // we now have established that the public keys are the same.
533 // find a chain-of-trust if one exists
534 if (certificates.size() == 1)
535 importCertificate((Certificate) certificates.iterator().next());
537 importChain(certificates);
539 log.exiting(this.getClass().getName(), "importCertificateReply"); //$NON-NLS-1$
543 * If the reply is a single X.509 certificate, keytool attempts to establish a
544 * trust chain, starting at the certificate reply and ending at a self-signed
545 * certificate (belonging to a root CA). The certificate reply and the
546 * hierarchy of certificates used to authenticate the certificate reply form
547 * the new certificate chain of alias. If a trust chain cannot be established,
548 * the certificate reply is not imported. In this case, keytool does not print
549 * out the certificate, nor does it prompt the user to verify it. This is
550 * because it is very hard (if not impossible) for a user to determine the
551 * authenticity of the certificate reply.
553 * @param certificate the certificate reply to import into the key store.
554 * @throws NoSuchAlgorithmException
555 * @throws CertPathValidatorException
556 * @throws UnsupportedCallbackException
557 * @throws IOException
558 * @throws UnrecoverableKeyException
559 * @throws KeyStoreException
560 * @throws CertificateException
562 private void importCertificate(Certificate certificate)
563 throws NoSuchAlgorithmException, CertPathValidatorException,
564 KeyStoreException, UnrecoverableKeyException, IOException,
565 UnsupportedCallbackException, CertificateException
567 log.entering(this.getClass().getName(), "importCertificate", certificate); //$NON-NLS-1$
569 LinkedList reply = new LinkedList();
570 reply.addLast(certificate);
572 if (! findTrustAndUpdate(reply, false))
573 throw new CertPathValidatorException(Messages.getString("ImportCmd.34")); //$NON-NLS-1$
575 Certificate[] newChain = (Certificate[]) reply.toArray(new Certificate[0]);
576 Key privateKey = getAliasPrivateKey();
577 store.setKeyEntry(alias, privateKey, keyPasswordChars, newChain);
580 log.exiting(this.getClass().getName(), "importCertificate"); //$NON-NLS-1$
584 * If the reply is a PKCS#7 formatted certificate chain, the chain is first
585 * ordered (with the user certificate first and the self-signed root CA
586 * certificate last), before keytool attempts to match the root CA certificate
587 * provided in the reply with any of the trusted certificates in the key store
588 * or the "cacerts" keystore file (if the -trustcacerts option was specified).
589 * If no match can be found, the information of the root CA certificate is
590 * printed out, and the user is prompted to verify it, e.g., by comparing the
591 * displayed certificate fingerprints with the fingerprints obtained from some
592 * other (trusted) source of information, which might be the root CA itself.
593 * The user then has the option of aborting the import operation. If the
594 * -noprompt option is given, however, there will be no interaction with the
597 * @param chain the collection of certificates parsed from the user
599 * @throws UnsupportedCallbackException
600 * @throws IOException
601 * @throws UnrecoverableKeyException
602 * @throws KeyStoreException
603 * @throws CertPathValidatorException
604 * @throws NoSuchAlgorithmException
605 * @throws CertificateException
607 private void importChain(Collection chain) throws NoSuchAlgorithmException,
608 CertPathValidatorException, KeyStoreException, UnrecoverableKeyException,
609 IOException, UnsupportedCallbackException, CertificateException
611 log.entering(this.getClass().getName(), "importChain", chain); //$NON-NLS-1$
613 LinkedList reply = orderChain(chain);
614 if (findTrustAndUpdate(reply, ! noPrompt))
616 Certificate[] newChain = (Certificate[]) reply.toArray(new Certificate[0]);
617 Key privateKey = getAliasPrivateKey();
618 store.setKeyEntry(alias, privateKey, keyPasswordChars, newChain);
622 log.exiting(this.getClass().getName(), "importChain"); //$NON-NLS-1$
626 * Check to ensure that alias's public key is the subject of the first
627 * certificate in the passed certificate collection. Throws an exception if
628 * the public keys do not match.
630 * @param certificates a {@link Collection} of certificate replies (either a
631 * signle certificate reply, or a PKCS#7 certificate reply chain)
632 * usually sent by a CA as a response to a Certificate Signing
634 * @throws IOException
635 * @throws UnsupportedCallbackException
636 * @throws KeyStoreException
638 private void ensureReplyIsOurs(Collection certificates) throws IOException,
639 UnsupportedCallbackException, KeyStoreException
641 log.entering(this.getClass().getName(), "ensureReplyIsOurs"); //$NON-NLS-1$
643 Certificate certificate = (Certificate) certificates.iterator().next();
644 log.finest("certificate = " + certificate); //$NON-NLS-1$
645 Certificate[] chain = store.getCertificateChain(alias);
647 throw new IllegalArgumentException(Messages.getFormattedString("ImportCmd.37", //$NON-NLS-1$
649 selfSignedCertificate = (X509Certificate) chain[0];
650 PublicKey anchorPublicKey = selfSignedCertificate.getPublicKey();
651 PublicKey certPublicKey = certificate.getPublicKey();
653 if (anchorPublicKey instanceof DSAPublicKey)
655 DSAPublicKey pk1 = (DSAPublicKey) anchorPublicKey;
656 if (!(certPublicKey instanceof DSAPublicKey))
657 throw new IllegalArgumentException(Messages.getString("ImportCmd.38")); //$NON-NLS-1$
659 sameKey = areEqual(pk1, (DSAPublicKey) certPublicKey);
661 else if (anchorPublicKey instanceof RSAPublicKey)
663 RSAPublicKey pk1 = (RSAPublicKey) anchorPublicKey;
664 if (!(certPublicKey instanceof RSAPublicKey))
665 throw new IllegalArgumentException(Messages.getString("ImportCmd.38")); //$NON-NLS-1$
667 sameKey = areEqual(pk1, (RSAPublicKey) certPublicKey);
670 throw new IllegalArgumentException(
671 Messages.getFormattedString("ImportCmd.40", //$NON-NLS-1$
672 new String[] { alias,
673 anchorPublicKey.getClass().getName() }));
675 throw new IllegalArgumentException(Messages.getString("ImportCmd.41")); //$NON-NLS-1$
677 log.exiting(this.getClass().getName(), "ensureReplyIsOurs"); //$NON-NLS-1$
680 private boolean areEqual(DSAPublicKey pk1, DSAPublicKey pk2)
682 if (pk1.getY().compareTo(pk2.getY()) != 0)
685 DSAParams p1 = pk1.getParams();
686 DSAParams p2 = pk2.getParams();
687 if (p1.getG().compareTo(p2.getG()) != 0)
690 if (p1.getP().compareTo(p2.getP()) != 0)
693 return p1.getQ().compareTo(p2.getQ()) == 0;
696 private boolean areEqual(RSAPublicKey pk1, RSAPublicKey pk2)
698 if (pk1.getPublicExponent().compareTo(pk2.getPublicExponent()) != 0)
701 return pk1.getModulus().compareTo(pk2.getModulus()) == 0;
705 * Given a collection of certificates returned as a certificate-reply, this
706 * method sorts the certificates in the collection so that the <i>Issuer</i>
707 * of the certificate at position <code>i</code> is the <i>Subject</i> of
708 * the certificate at position <code>i + 1</code>.
710 * This method uses <code>selfSignedCertificate</code> to discover the first
711 * certificate in the chain. The <i>Trust Anchor</i> of the chain; i.e. the
712 * self-signed CA certificate, if it exsits, will be discovered/established
713 * later by an appropriate <i>Certificate Path Validator</i>.
715 * An exception is thrown if (a) no initial certificate is found in the
716 * designated collection which can be used as the start of the chain, or (b)
717 * if a chain can not be constructed using all the certificates in the
718 * designated collection.
720 * @param chain a collection of certificates, not necessarily ordered, but
721 * assumed to include a CA certificate authenticating our alias
722 * public key, which is the subject of the alias self-signed
724 * @return the input collection, ordered with own certificate first, and CA's
725 * self-signed certificate last.
727 private LinkedList orderChain(Collection chain)
729 log.entering(this.getClass().getName(), "orderChain"); //$NON-NLS-1$
730 LinkedList in = new LinkedList(chain);
731 int initialCount = in.size();
732 LinkedList result = new LinkedList();
733 Principal issuer = selfSignedCertificate.getIssuerDN();
735 outer: while (in.size() > 0)
737 for (it = in.listIterator(); it.hasNext();)
739 X509Certificate certificate = (X509Certificate) it.next();
740 if (issuer.equals(certificate.getSubjectDN()))
743 result.addLast(certificate);
744 issuer = certificate.getIssuerDN();
748 throw new IllegalArgumentException(
749 Messages.getFormattedString(Messages.getString("ImportCmd.7"), //$NON-NLS-1$
750 new Object[] { Integer.valueOf(result.size()),
751 Integer.valueOf(initialCount) }));
753 log.entering(this.getClass().getName(), "orderChain", result); //$NON-NLS-1$
758 * Given an ordered list of certificates, this method attempts to validate the
759 * chain, and if successful, updates the key store entry for the designated
760 * alias. The list of certificates is expected to be ordered as a chain, where
761 * the first is the alias's own certificate and the last being a self-signed
764 * if <code>promptUser</code> is <code>true</code>, then even if no
765 * anchor trust certificate is found, the user is prompted to approve, or not,
766 * the import operation. On the other hand if the <code>promptUser</code>
767 * parameter is <code>false</code> then this method will throw an exception
768 * if no trust anchor is to be found.
770 * @param reply an ordered certificate path, where the last entry is the CA's
771 * self-signed certificate.
772 * @param promptUser a boolean flag indicating whether or not to prompt the
773 * user for explicit trust in a CA certificate.
774 * @return <code>true</code> if the validation succeeds; or <code>false</code>
776 * @throws NoSuchAlgorithmException
777 * @throws CertPathValidatorException
778 * @throws UnsupportedCallbackException
779 * @throws IOException
780 * @throws UnrecoverableKeyException
781 * @throws KeyStoreException
782 * @throws CertificateEncodingException
784 private boolean findTrustAndUpdate(LinkedList reply, boolean promptUser)
785 throws IOException, NoSuchAlgorithmException, CertPathValidatorException,
786 KeyStoreException, UnrecoverableKeyException, UnsupportedCallbackException,
787 CertificateEncodingException
789 log.entering(this.getClass().getName(), "findTrustAndUpdate"); //$NON-NLS-1$
790 CertPathValidator validator = CertPathValidator.getInstance("PKIX"); //$NON-NLS-1$
791 X509CertPath certPath = new X509CertPath(reply);
792 PKIXCertPathValidatorResult cpvr = findTrustInStore(certPath, validator);
793 if (cpvr == null && trustCACerts) // try cacerts.gkr - a GKR key store
795 PKIXParameters params = getCertPathParameters(GKR, gkrCaCertsPathName);
796 cpvr = validate(validator, certPath, params);
797 if (cpvr == null) // try cacerts - a JKS key store
799 params = getCertPathParameters(JKS, jksCaCertsPathName);
800 cpvr = validate(validator, certPath, params);
803 boolean result = false;
808 printVerbose((Certificate) reply.getLast());
809 ConfirmationCallback ccb;
810 ccb = new ConfirmationCallback(Messages.getString("ImportCmd.32"), //$NON-NLS-1$
811 ConfirmationCallback.INFORMATION,
812 ConfirmationCallback.YES_NO_OPTION,
813 ConfirmationCallback.NO);
814 getCallbackHandler().handle(new Callback[] { ccb });
815 int answer = ccb.getSelectedIndex();
816 result = answer == ConfirmationCallback.YES;
821 TrustAnchor anchor = cpvr.getTrustAnchor();
822 log.fine("Found a chain-of-trust anchored by " + anchor); //$NON-NLS-1$
823 Certificate trustedCert = anchor.getTrustedCert();
824 reply.addLast(trustedCert);
827 log.entering(this.getClass().getName(), "findTrustAndUpdate", //$NON-NLS-1$
828 Boolean.valueOf(result));
832 private PKIXCertPathValidatorResult findTrustInStore(X509CertPath certPath,
833 CertPathValidator validator)
835 log.entering(this.getClass().getName(), "findTrustInStore"); //$NON-NLS-1$
837 PKIXCertPathValidatorResult result;
840 PKIXParameters params = new PKIXParameters(store);
841 result = (PKIXCertPathValidatorResult) validator.validate(certPath, params);
846 "Exception in findTrustInStore(). Ignore + Return NULL", //$NON-NLS-1$
851 log.exiting(this.getClass().getName(), "findTrustInStore", result); //$NON-NLS-1$
856 * Return an instance of {@link PKIXParameters} constructed using a key store
857 * of the designated type and located at the designated path.
859 * @param type the type of the key-store to load.
860 * @param pathName the local File System fully qualified path name to the key
862 * @return an instance of <code>CertPathParameters</code> to use for
863 * validating certificates and certificate replies.
865 private PKIXParameters getCertPathParameters(String type, String pathName)
867 log.entering(this.getClass().getName(), "getCertPathParameters", //$NON-NLS-1$
868 new Object[] { type, pathName });
869 FileInputStream stream = null;
870 PKIXParameters result = null;
873 KeyStore cacerts = KeyStore.getInstance(type);
874 stream = new FileInputStream(pathName);
875 cacerts.load(stream, "changeit".toCharArray()); //$NON-NLS-1$
876 result = new PKIXParameters(cacerts);
880 log.log(Level.FINE, "Exception in getCertPathParameters(). Ignore", x); //$NON-NLS-1$
889 catch (Exception ignored)
893 log.exiting(this.getClass().getName(), "getCertPathParameters", result); //$NON-NLS-1$
897 private PKIXCertPathValidatorResult validate(CertPathValidator validator,
898 X509CertPath certPath,
899 PKIXParameters params)
901 log.entering(this.getClass().getName(), "validate"); //$NON-NLS-1$
902 PKIXCertPathValidatorResult result = null;
906 result = (PKIXCertPathValidatorResult) validator.validate(certPath,
911 log.log(Level.FINE, "Exception in validate(). Ignore", x); //$NON-NLS-1$
913 log.exiting(this.getClass().getName(), "validate", result); //$NON-NLS-1$