1 /* Command.java -- Abstract implementation of a keytool command handler
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.Configuration;
42 import gnu.classpath.SystemProperties;
43 import gnu.classpath.tools.common.CallbackUtil;
44 import gnu.classpath.tools.common.ProviderUtil;
45 import gnu.classpath.tools.common.SecurityProviderInfo;
46 import gnu.classpath.tools.getopt.Parser;
47 import gnu.java.security.OID;
48 import gnu.java.security.Registry;
49 import gnu.java.security.der.BitString;
50 import gnu.java.security.der.DER;
51 import gnu.java.security.der.DERReader;
52 import gnu.java.security.der.DERValue;
53 import gnu.java.security.der.DERWriter;
54 import gnu.java.security.hash.IMessageDigest;
55 import gnu.java.security.hash.MD5;
56 import gnu.java.security.hash.Sha160;
57 import gnu.java.security.util.Util;
58 import gnu.java.security.x509.X500DistinguishedName;
60 import java.io.ByteArrayOutputStream;
62 import java.io.FileInputStream;
63 import java.io.FileNotFoundException;
64 import java.io.FileOutputStream;
65 import java.io.IOException;
66 import java.io.InputStream;
67 import java.io.OutputStream;
68 import java.io.PrintWriter;
69 import java.math.BigInteger;
71 import java.net.URLConnection;
72 import java.security.InvalidKeyException;
73 import java.security.InvalidParameterException;
74 import java.security.Key;
75 import java.security.KeyPairGenerator;
76 import java.security.KeyStore;
77 import java.security.KeyStoreException;
78 import java.security.NoSuchAlgorithmException;
79 import java.security.PrivateKey;
80 import java.security.Provider;
81 import java.security.PublicKey;
82 import java.security.Signature;
83 import java.security.SignatureException;
84 import java.security.UnrecoverableKeyException;
85 import java.security.cert.Certificate;
86 import java.security.cert.CertificateEncodingException;
87 import java.security.cert.CertificateException;
88 import java.security.cert.X509Certificate;
89 import java.security.interfaces.DSAKey;
90 import java.security.interfaces.RSAKey;
91 import java.util.ArrayList;
92 import java.util.Date;
93 import java.util.logging.Logger;
94 import java.util.prefs.Preferences;
96 import javax.security.auth.callback.Callback;
97 import javax.security.auth.callback.CallbackHandler;
98 import javax.security.auth.callback.NameCallback;
99 import javax.security.auth.callback.PasswordCallback;
100 import javax.security.auth.callback.UnsupportedCallbackException;
103 * A base class of the keytool command to facilitate implementation of concrete
106 abstract class Command
108 // Fields and constants -----------------------------------------------------
110 private static final Logger log = Logger.getLogger(Command.class.getName());
111 /** Default value for the ALIAS argument. */
112 private static final String DEFAULT_ALIAS = "mykey"; //$NON-NLS-1$
113 /** Default algorithm for key-pair generation. */
114 private static final String DEFAULT_KEY_ALGORITHM = "DSA"; //$NON-NLS-1$
115 /** Default DSA digital signature algorithm to use with DSA keys. */
116 private static final String DSA_SIGNATURE_ALGORITHM = "SHA1withDSA"; //$NON-NLS-1$
117 /** Default RSA digital signature algorithm to use with RSA keys. */
118 private static final String RSA_SIGNATURE_ALGORITHM = "MD5withRSA"; //$NON-NLS-1$
119 /** Default validity (in days) of newly generated certificates. */
120 private static final int DEFAULT_VALIDITY = 90;
121 /** OID of SHA1withDSA signature algorithm as stated in RFC-2459. */
122 protected static final OID SHA1_WITH_DSA = new OID("1.2.840.10040.4.3"); //$NON-NLS-1$
123 /** OID of MD2withRSA signature algorithm as stated in RFC-2459. */
124 private static final OID MD2_WITH_RSA = new OID("1.2.840.113549.1.1.2"); //$NON-NLS-1$
125 /** OID of MD5withRSA signature algorithm as stated in RFC-2459. */
126 private static final OID MD5_WITH_RSA = new OID("1.2.840.113549.1.1.4"); //$NON-NLS-1$
127 /** OID of SHA1withRSA signature algorithm as stated in RFC-2459. */
128 private static final OID SHA1_WITH_RSA = new OID("1.2.840.113549.1.1.5"); //$NON-NLS-1$
129 /** Number of milliseconds in one day. */
130 private static final long MILLIS_IN_A_DAY = 24 * 60 * 60 * 1000L;
132 /** The Alias to use. */
133 protected String alias;
134 /** The password characters protecting a Key Entry. */
135 protected char[] keyPasswordChars;
136 /** A security provider to add. */
137 protected Provider provider;
138 /** The key store type. */
139 protected String storeType;
140 /** The password characters protecting the key store. */
141 protected char[] storePasswordChars;
142 /** The key store URL. */
143 protected URL storeURL;
144 /** The input stream from the key store URL. */
145 protected InputStream storeStream;
146 /** The key store instance to use. */
147 protected KeyStore store;
148 /** The output stream the concrete handler will use. */
149 protected OutputStream outStream;
150 /** Whether we are printing to System.out. */
151 protected boolean systemOut;
152 /** The key-pair generation algorithm instance to use. */
153 protected KeyPairGenerator keyPairGenerator;
154 /** The digital signature algorithm instance to use. */
155 protected Signature signatureAlgorithm;
156 /** Validity period, in number of days, to use when generating certificates. */
157 protected int validityInDays;
158 /** The input stream the concrete handler will use. */
159 protected InputStream inStream;
160 /** Whether verbose output is required or not. */
161 protected boolean verbose;
163 /** MD5 hash to use when generating certificate fingerprints. */
164 private IMessageDigest md5 = new MD5();
165 /** SHA1 hash to use when generating certificate fingerprints. */
166 private IMessageDigest sha = new Sha160();
167 /** The new position of a user-defined provider if it is not already installed. */
168 private int providerNdx = -2;
169 /** The callback handler to use when needing to interact with user. */
170 private CallbackHandler handler;
171 /** The shutdown hook. */
172 private ShutdownHook shutdownThread;
174 // Constructor(s) -----------------------------------------------------------
179 shutdownThread = new ShutdownHook();
180 Runtime.getRuntime().addShutdownHook(shutdownThread);
183 // Methods ------------------------------------------------------------------
186 * A public method to allow using any keytool command handler programmatically
187 * by using a JavaBeans style of parameter(s) initialization. The user is
188 * assumed to have set individually the required options through their
189 * respective setters before invoking this method.
191 * If an exception is encountered during the processing of the command, this
192 * implementation attempts to release any resources that may have been
193 * allocated at the time the exception occurs, before re-throwing that
196 * @throws Exception if an exception occurs during the processing of this
197 * command. For a more comprehensive list of exceptions that may
198 * occur, see the documentation of the {@link #setup()} and
199 * {@link #start()} methods.
201 public void doCommand() throws Exception
211 if (shutdownThread != null)
212 Runtime.getRuntime().removeShutdownHook(shutdownThread);
217 * @param flag whether to use, or not, more verbose output while processing
220 public void setVerbose(String flag)
222 this.verbose = Boolean.valueOf(flag).booleanValue();
225 // life-cycle methods -------------------------------------------------------
228 * Given a potential sub-array of options for this concrete handler, starting
229 * at position <code>startIndex + 1</code>, potentially followed by other
230 * commands and their options, this method sets up this concrete command
231 * handler with its own options and returns the index of the first unprocessed
232 * argument in the array.
234 * The general contract of this method is that it is invoked with the
235 * <code>startIndex</code> argument pointing to the keyword argument that
236 * uniquelly identifies the command itself; e.g. <code>-genkey</code> or
237 * <code>-list</code>, etc...
239 * @param args an array of options for this handler and possibly other
240 * commands and their options.
241 * @return the remaining un-processed <code>args</code>.
243 String[] processArgs(String[] args)
245 if (Configuration.DEBUG)
246 log.entering(this.getClass().getName(), "processArgs", args); //$NON-NLS-1$
247 Parser cmdOptionsParser = getParser();
248 String[] result = cmdOptionsParser.parse(args);
249 if (Configuration.DEBUG)
250 log.exiting(this.getClass().getName(), "processArgs", result); //$NON-NLS-1$
255 * Initialize this concrete command handler for later invocation of the
256 * {@link #start()} or {@link #doCommand()} methods.
258 * Handlers usually initialize their local variables and resources within the
259 * scope of this call.
261 * @throws IOException if an I/O related exception, such as opening an input
262 * stream, occurs during the execution of this method.
263 * @throws UnsupportedCallbackException if a requested callback handler
264 * implementation was not found, or was found but encountered an
265 * exception during its processing.
266 * @throws ClassNotFoundException if a designated security provider class was
268 * @throws IllegalAccessException no 0-arguments constructor for the
269 * designated security provider class was found.
270 * @throws InstantiationException the designated security provider class is
272 * @throws KeyStoreException if an exception occurs during the instantiation
274 * @throws CertificateException if a certificate related exception, such as
275 * expiry, occurs during the loading of the KeyStore.
276 * @throws NoSuchAlgorithmException if no current security provider can
277 * provide a needed algorithm referenced by the KeyStore or one of
278 * its Key Entries or Certificates.
280 abstract void setup() throws Exception;
283 * Do the real work this handler is supposed to do.
285 * The code in this (abstract) class throws a <i>Not implemented yet</i>
286 * runtime exception. Concrete implementations MUST override this method.
288 * @throws CertificateException If no concrete implementation was found for a
289 * certificate Factory of a designated type. In this tool, the type
290 * is usually X.509 v1.
291 * @throws KeyStoreException if a keys-store related exception occurs; e.g.
292 * the key store has not been initialized.
293 * @throws IOException if an I/O related exception occurs during the process.
294 * @throws SignatureException if a digital signature related exception occurs.
295 * @throws InvalidKeyException if the genereated keys are invalid.
296 * @throws UnrecoverableKeyException if the password used to unlock a key in
297 * the key store was invalid.
298 * @throws NoSuchAlgorithmException if a concrete implementation of an
299 * algorithm used to store a Key Entry was not found at runtime.
300 * @throws UnsupportedCallbackException if a requested callback handler
301 * implementation was not found, or was found but encountered an
302 * exception during its processing.
304 void start() throws Exception
306 throw new RuntimeException("Not implemented yet"); //$NON-NLS-1$
310 * Tear down the handler, releasing any resources which may have been
311 * allocated at setup time.
315 if (Configuration.DEBUG)
316 log.entering(this.getClass().getName(), "teardown"); //$NON-NLS-1$
317 if (storeStream != null)
322 catch (IOException ignored)
324 if (Configuration.DEBUG)
325 log.fine("Exception while closing key store URL stream. Ignored: " //$NON-NLS-1$
329 if (outStream != null)
335 catch (IOException ignored)
344 catch (IOException ignored)
349 if (inStream != null)
354 catch (IOException ignored)
359 ProviderUtil.removeProvider(provider.getName());
361 if (Configuration.DEBUG)
362 log.exiting(this.getClass().getName(), "teardown"); //$NON-NLS-1$
365 // parameter setup and validation methods -----------------------------------
368 * @return a {@link Parser} that knows how to parse the concrete command's
371 abstract Parser getParser();
374 * Convenience method to setup the key store given its type, its password, its
375 * location and portentially a specialized security provider.
377 * Calls the method with the same name and 5 arguments passing
378 * <code>false</code> to the first argument implying that no attempt to
379 * create the keystore will be made if one was not found at the designated
382 * @param className the potentially null fully qualified class name of a
383 * security provider to add at runtime, if no installed provider is
384 * able to provide a key store implementation of the desired type.
385 * @param type the potentially null type of the key store to request from the
387 * @param password the potentially null password protecting the key store.
388 * @param url the URL of the key store.
390 protected void setKeyStoreParams(String className, String type,
391 String password, String url)
392 throws IOException, UnsupportedCallbackException, KeyStoreException,
393 NoSuchAlgorithmException, CertificateException
395 setKeyStoreParams(false, className, type, password, url);
399 * Convenience method to setup the key store given its type, its password, its
400 * location and portentially a specialized security provider.
402 * @param createIfNotFound if <code>true</code> then create the keystore if
403 * it was not found; otherwise do not.
404 * @param className the potentially null fully qualified class name of a
405 * security provider to add at runtime, if no installed provider is
406 * able to provide a key store implementation of the desired type.
407 * @param type the potentially null type of the key store to request from the
409 * @param password the potentially null password protecting the key store.
410 * @param url the URL of the key store.
412 protected void setKeyStoreParams(boolean createIfNotFound, String className,
413 String type, String password, String url)
414 throws IOException, UnsupportedCallbackException, KeyStoreException,
415 NoSuchAlgorithmException, CertificateException
417 setProviderClassNameParam(className);
418 setKeystoreTypeParam(type);
419 setKeystoreURLParam(createIfNotFound, url, password);
423 * Set a security provider class name to (install and) use for key store
424 * related operations.
426 * @param className the possibly null, fully qualified class name of a
427 * security provider to add, if it is not already installed, to the
428 * set of available providers.
430 private void setProviderClassNameParam(String className)
432 if (Configuration.DEBUG)
433 log.fine("setProviderClassNameParam(" + className + ")"); //$NON-NLS-1$ //$NON-NLS-2$
434 if (className != null && className.trim().length() > 0)
436 className = className.trim();
437 SecurityProviderInfo spi = ProviderUtil.addProvider(className);
438 provider = spi.getProvider();
439 if (provider == null)
441 if (Configuration.DEBUG)
442 log.fine("Was unable to add provider from class " + className);
444 providerNdx = spi.getPosition();
449 * Set the type of key store to initialize, load and use.
451 * @param type the possibly null type of the key store. if this argument is
452 * <code>null</code>, or is an empty string, then this method sets
453 * the type of the key store to be the default value returned from
454 * the invocation of the {@link KeyStore#getDefaultType()} method.
455 * For GNU Classpath this is <i>gkr</i> which stands for the "Gnu
456 * KeyRing" specifications.
458 private void setKeystoreTypeParam(String type)
460 if (Configuration.DEBUG)
461 log.fine("setKeystoreTypeParam(" + type + ")"); //$NON-NLS-1$ //$NON-NLS-2$
462 if (type == null || type.trim().length() == 0)
463 storeType = KeyStore.getDefaultType();
465 storeType = type.trim();
469 * Set the key password given a command line option argument. If no value was
470 * present on the command line then prompt the user to provide one.
472 * @param password a possibly null key password gleaned from the command line.
473 * @throws IOException if an I/O related exception occurs.
474 * @throws UnsupportedCallbackException if no concrete implementation of a
475 * password callback was found at runtime.
477 protected void setKeyPasswordParam(String password) throws IOException,
478 UnsupportedCallbackException
480 setKeyPasswordNoPrompt(password);
481 if (keyPasswordChars == null)
482 setKeyPasswordParam();
486 * Set the Alias to use when associating Key Entries and Trusted Certificates
487 * in the current key store.
489 * @param name the possibly null alias to use. If this arfument is
490 * <code>null</code>, then a default value of <code>mykey</code>
491 * will be used instead.
493 protected void setAliasParam(String name)
495 alias = name == null ? DEFAULT_ALIAS : name.trim();
499 * Set the key password given a command line option argument.
501 * @param password a possibly null key password gleaned from the command line.
503 protected void setKeyPasswordNoPrompt(String password)
505 if (password != null)
506 keyPasswordChars = password.toCharArray();
510 * Prompt the user to provide a password to protect a Key Entry in the key
513 * @throws IOException if an I/O related exception occurs.
514 * @throws UnsupportedCallbackException if no concrete implementation of a
515 * password callback was found at runtime.
516 * @throws SecurityException if no password is available, even after prompting
519 private void setKeyPasswordParam() throws IOException,
520 UnsupportedCallbackException
522 String prompt = Messages.getFormattedString("Command.21", alias); //$NON-NLS-1$
523 PasswordCallback pcb = new PasswordCallback(prompt, false);
524 getCallbackHandler().handle(new Callback[] { pcb });
525 keyPasswordChars = pcb.getPassword();
527 if (keyPasswordChars == null)
528 throw new SecurityException(Messages.getString("Command.23")); //$NON-NLS-1$
531 private void setKeystorePasswordParam(String password) throws IOException,
532 UnsupportedCallbackException
534 if (password != null)
535 storePasswordChars = password.toCharArray();
536 else // ask the user to provide one
538 String prompt = Messages.getString("Command.24"); //$NON-NLS-1$
539 PasswordCallback pcb = new PasswordCallback(prompt, false);
540 getCallbackHandler().handle(new Callback[] { pcb });
541 storePasswordChars = pcb.getPassword();
547 * Set the key store URL to use.
549 * @param createIfNotFound when <code>true</code> an attempt to create a
550 * keystore at the designated location will be made. If
551 * <code>false</code> then no file creation is carried out, which
552 * may cause an exception to be thrown later.
553 * @param url the full, or partial, URL to the keystore location.
554 * @param password an eventually null string to use when loading the keystore.
555 * @throws IOException
556 * @throws KeyStoreException
557 * @throws UnsupportedCallbackException
558 * @throws NoSuchAlgorithmException
559 * @throws CertificateException
561 private void setKeystoreURLParam(boolean createIfNotFound, String url,
562 String password) throws IOException,
563 KeyStoreException, UnsupportedCallbackException, NoSuchAlgorithmException,
566 if (Configuration.DEBUG)
567 log.fine("setKeystoreURLParam(" + url + ")"); //$NON-NLS-1$ //$NON-NLS-2$
568 if (url == null || url.trim().length() == 0)
570 String userHome = SystemProperties.getProperty("user.home"); //$NON-NLS-1$
571 if (userHome == null || userHome.trim().length() == 0)
572 throw new InvalidParameterException(Messages.getString("Command.36")); //$NON-NLS-1$
574 url = userHome.trim() + "/.keystore"; //$NON-NLS-1$
575 // if it does not exist create it if required
576 if (createIfNotFound)
577 new File(url).createNewFile();
578 url = "file:" + url; //$NON-NLS-1$
583 if (url.indexOf(":") == -1) // if it does not exist create it //$NON-NLS-1$
585 if (createIfNotFound)
586 new File(url).createNewFile();
588 url = "file:" + url; //$NON-NLS-1$
591 boolean newKeyStore = false;
592 storeURL = new URL(url);
593 storeStream = storeURL.openStream();
594 if (storeStream.available() == 0)
596 if (Configuration.DEBUG)
597 log.fine("Store is empty. Will use <null> when loading, to create it"); //$NON-NLS-1$
603 store = KeyStore.getInstance(storeType);
605 catch (KeyStoreException x)
607 if (provider != null)
610 if (Configuration.DEBUG)
611 log.fine("Exception while getting key store with default provider(s)." //$NON-NLS-1$
612 + " Will prompt user for another provider and continue"); //$NON-NLS-1$
613 String prompt = Messages.getString("Command.40"); //$NON-NLS-1$
614 NameCallback ncb = new NameCallback(prompt);
615 getCallbackHandler().handle(new Callback[] { ncb });
616 String className = ncb.getName();
617 setProviderClassNameParam(className); // we may have a Provider
618 if (provider == null)
620 x.fillInStackTrace();
624 store = KeyStore.getInstance(storeType, provider);
627 setKeystorePasswordParam(password);
629 // now we have a KeyStore instance. load it
630 // KeyStore public API claims: "...In order to create an empty keystore,
631 // you pass null as the InputStream argument to the load method.
633 store.load(null, storePasswordChars);
635 store.load(storeStream, storePasswordChars);
643 catch (IOException x)
645 if (Configuration.DEBUG)
646 log.fine("Exception while closing the key store input stream: " + x //$NON-NLS-1$
647 + ". Ignore"); //$NON-NLS-1$
651 protected void setOutputStreamParam(String fileName) throws SecurityException,
654 if (fileName == null || fileName.trim().length() == 0)
656 outStream = System.out;
661 fileName = fileName.trim();
662 File outFile = new File(fileName);
663 if (! outFile.exists())
665 boolean ok = outFile.createNewFile();
667 throw new InvalidParameterException(Messages.getFormattedString("Command.19", //$NON-NLS-1$
672 if (! outFile.isFile())
673 throw new InvalidParameterException(Messages.getFormattedString("Command.42", //$NON-NLS-1$
675 if (! outFile.canWrite())
676 throw new InvalidParameterException(Messages.getFormattedString("Command.44", //$NON-NLS-1$
679 outStream = new FileOutputStream(outFile);
683 protected void setInputStreamParam(String fileName)
684 throws FileNotFoundException
686 if (fileName == null || fileName.trim().length() == 0)
687 inStream = System.in;
690 fileName = fileName.trim();
691 File inFile = new File(fileName);
692 if (! (inFile.exists() && inFile.isFile() && inFile.canRead()))
693 throw new InvalidParameterException(Messages.getFormattedString("Command.46", //$NON-NLS-1$
695 inStream = new FileInputStream(inFile);
700 * Set both the key-pair generation algorithm, and the digital signature
701 * algorithm instances to use when generating new entries.
703 * @param kpAlg the possibly null name of a key-pair generator algorithm.
704 * if this argument is <code>null</code> or is an empty string, the
705 * "DSS" algorithm will be used.
706 * @param sigAlg the possibly null name of a digital signature algorithm.
707 * If this argument is <code>null</code> or is an empty string, this
708 * method uses the "SHA1withDSA" (Digital Signature Standard, a.k.a.
709 * DSA, with the Secure Hash Algorithm function) as the default
710 * algorithm if, and only if, the key-pair generation algorithm ends
711 * up being "DSS"; otherwise, if the key-pair generation algorithm
712 * was "RSA", then the "MD5withRSA" signature algorithm will be used.
713 * If the key-pair generation algorithm is neither "DSS" (or its
714 * alias "DSA"), nor is it "RSA", then an exception is thrown.
715 * @throws NoSuchAlgorithmException if no concrete implementation of the
716 * designated algorithm is available.
718 protected void setAlgorithmParams(String kpAlg, String sigAlg)
719 throws NoSuchAlgorithmException
721 if (kpAlg == null || kpAlg.trim().length() == 0)
722 kpAlg = DEFAULT_KEY_ALGORITHM;
724 kpAlg = kpAlg.trim().toLowerCase();
726 keyPairGenerator = KeyPairGenerator.getInstance(kpAlg);
728 if (sigAlg == null || sigAlg.trim().length() == 0)
729 if (kpAlg.equalsIgnoreCase(Registry.DSS_KPG)
730 || kpAlg.equalsIgnoreCase(Registry.DSA_KPG))
731 sigAlg = DSA_SIGNATURE_ALGORITHM;
732 else if (kpAlg.equalsIgnoreCase(Registry.RSA_KPG))
733 sigAlg = RSA_SIGNATURE_ALGORITHM;
735 throw new IllegalArgumentException(
736 Messages.getFormattedString("Command.20", //$NON-NLS-1$
737 new String[] { sigAlg, kpAlg }));
739 sigAlg = sigAlg.trim().toLowerCase();
741 signatureAlgorithm = Signature.getInstance(sigAlg);
745 * Set the signature algorithm to use when digitally signing private keys,
746 * certificates, etc...
748 * If the designated algorithm name is <code>null</code> or is an empty
749 * string, this method checks the private key (the second argument) and based
750 * on its type decides which algorithm to use. The keytool public
751 * specification states that if the private key is a DSA key, then the
752 * signature algorithm will be <code>SHA1withDSA</code>, otherwise if it is
753 * an RSA private key, then the signature algorithm will be
754 * <code>MD5withRSA</code>. If the private key is neither a private DSA nor
755 * a private RSA key, then this method throws an
756 * {@link IllegalArgumentException}.
758 * @param algorithm the possibly null name of a digital signature algorithm.
759 * @param privateKey an instance of a private key to use as a fal-back option
760 * when <code>algorithm</code> is invalid.
761 * @throws NoSuchAlgorithmException if no concrete implementation of the
762 * designated, or default, signature algorithm is available.
764 protected void setSignatureAlgorithmParam(String algorithm, Key privateKey)
765 throws NoSuchAlgorithmException
767 if (algorithm == null || algorithm.trim().length() == 0)
768 if (privateKey instanceof DSAKey)
769 algorithm = DSA_SIGNATURE_ALGORITHM;
770 else if (privateKey instanceof RSAKey)
771 algorithm = RSA_SIGNATURE_ALGORITHM;
773 throw new InvalidParameterException(Messages.getString("Command.48")); //$NON-NLS-1$
775 algorithm = algorithm.trim();
777 signatureAlgorithm = Signature.getInstance(algorithm);
781 * Set the validity period, in number of days, to use when issuing new
784 * @param days the number of days, as a string, the generated certificate will
785 * be valid for, starting from today's date. if this argument is
786 * <code>null</code>, a default value of <code>90</code> days
788 * @throws NumberFormatException if the designated string is not a decimal
790 * @throws InvalidParameterException if the integer value of the non-null
791 * string is not greater than zero.
793 protected void setValidityParam(String days)
795 if (days == null || days.trim().length() == 0)
796 validityInDays = DEFAULT_VALIDITY;
800 validityInDays = Integer.parseInt(days);
801 if (validityInDays < 1)
802 throw new InvalidParameterException(Messages.getString("Command.51")); //$NON-NLS-1$
807 * RFC-2459 (http://rfc.net/rfc2459.html) fully describes the structure and
808 * semantics of X.509 certificates. The ASN.1 structures below are gleaned
809 * from that reference.
812 * Certificate ::= SEQUENCE {
813 * tbsCertificate TBSCertificate,
814 * signatureAlgorithm AlgorithmIdentifier,
815 * signatureValue BIT STRING
818 * TBSCertificate ::= SEQUENCE {
819 * version [0] EXPLICIT Version DEFAULT v1,
820 * serialNumber CertificateSerialNumber,
821 * signature AlgorithmIdentifier,
825 * subjectPublicKeyInfo SubjectPublicKeyInfo
828 * Version ::= INTEGER { v1(0), v2(1), v3(2) }
830 * CertificateSerialNumber ::= INTEGER
832 * Validity ::= SEQUENCE {
839 * generalTime GeneralizedTime
842 * UniqueIdentifier ::= BIT STRING
844 * SubjectPublicKeyInfo ::= SEQUENCE {
845 * algorithm AlgorithmIdentifier,
846 * subjectPublicKey BIT STRING
850 * @param distinguishedName the X.500 Distinguished Name to use as both the
851 * Issuer and Subject of the self-signed certificate to generate.
852 * @param publicKey the public key of the issuer/subject.
853 * @param privateKey the private key of the issuer/signer.
854 * @return the DER encoded form of a self-signed X.509 v1 certificate.
855 * @throws IOException If an I/O related exception occurs during the process.
856 * @throws SignatureException If a digital signature related exception occurs.
857 * @throws InvalidKeyException if the designated private key is invalid.
858 * @throws InvalidParameterException if the concrete signature algorithm does
859 * not know its name, no OID is known/supported for that name, or we
860 * were unable to match the name to a known string for which we can
861 * use a standard OID.
863 protected byte[] getSelfSignedCertificate(X500DistinguishedName distinguishedName,
865 PrivateKey privateKey)
866 throws IOException, SignatureException, InvalidKeyException
868 if (Configuration.DEBUG)
869 log.entering(this.getClass().getName(), "getSelfSignedCertificate", //$NON-NLS-1$
870 new Object[] { distinguishedName, publicKey, privateKey });
871 byte[] versionBytes = new DERValue(DER.INTEGER, BigInteger.ZERO).getEncoded();
872 DERValue derVersion = new DERValue(DER.CONSTRUCTED | DER.CONTEXT | 0,
873 versionBytes.length, versionBytes, null);
875 // NOTE (rsn): the next 3 lines should be atomic but they're not.
876 Preferences prefs = Preferences.systemNodeForPackage(this.getClass());
877 int lastSerialNumber = prefs.getInt(Main.LAST_SERIAL_NUMBER, 0) + 1;
878 prefs.putInt(Main.LAST_SERIAL_NUMBER, lastSerialNumber);
879 DERValue derSerialNumber = new DERValue(DER.INTEGER,
880 BigInteger.valueOf(lastSerialNumber));
882 OID signatureID = getSignatureAlgorithmOID();
883 DERValue derSignatureID = new DERValue(DER.OBJECT_IDENTIFIER, signatureID);
884 ArrayList signature = new ArrayList(1);
885 signature.add(derSignatureID);
886 // rfc-2459 states the following:
888 // for the DSA signature:
889 // ...Where the id-dsa-with-sha1 algorithm identifier appears as the
890 // algorithm field in an AlgorithmIdentifier, the encoding shall omit
891 // the parameters field. That is, the AlgorithmIdentifier shall be a
892 // SEQUENCE of one component - the OBJECT IDENTIFIER id-dsa-with-sha1.
894 // for RSA signatures:
895 // ...When any of these three OIDs (i.e. xxxWithRSAEncryption) appears
896 // within the ASN.1 type AlgorithmIdentifier, the parameters component of
897 // that type shall be the ASN.1 type NULL.
898 if (! signatureID.equals(SHA1_WITH_DSA))
899 signature.add(new DERValue(DER.NULL, null));
901 DERValue derSignature = new DERValue(DER.CONSTRUCTED | DER.SEQUENCE,
904 DERValue derIssuer = new DERReader(distinguishedName.getDer()).read();
906 long notBefore = System.currentTimeMillis();
907 long notAfter = notBefore + validityInDays * MILLIS_IN_A_DAY;
909 ArrayList validity = new ArrayList(2);
910 validity.add(new DERValue(DER.UTC_TIME, new Date(notBefore)));
911 validity.add(new DERValue(DER.UTC_TIME, new Date(notAfter)));
912 DERValue derValidity = new DERValue(DER.CONSTRUCTED | DER.SEQUENCE,
915 // for a self-signed certificate subject and issuer are identical
916 DERValue derSubject = derIssuer;
918 DERValue derSubjectPublicKeyInfo = new DERReader(publicKey.getEncoded()).read();
920 ArrayList tbsCertificate = new ArrayList(7);
921 tbsCertificate.add(derVersion);
922 tbsCertificate.add(derSerialNumber);
923 tbsCertificate.add(derSignature);
924 tbsCertificate.add(derIssuer);
925 tbsCertificate.add(derValidity);
926 tbsCertificate.add(derSubject);
927 tbsCertificate.add(derSubjectPublicKeyInfo);
928 DERValue derTBSCertificate = new DERValue(DER.CONSTRUCTED | DER.SEQUENCE,
931 // The 'signature' field MUST contain the same algorithm identifier as the
932 // 'signatureAlgorithm' field in the sequence Certificate.
933 DERValue derSignatureAlgorithm = derSignature;
935 signatureAlgorithm.initSign(privateKey);
936 signatureAlgorithm.update(derTBSCertificate.getEncoded());
937 byte[] sigBytes = signatureAlgorithm.sign();
938 DERValue derSignatureValue = new DERValue(DER.BIT_STRING,
939 new BitString(sigBytes));
941 ArrayList certificate = new ArrayList(3);
942 certificate.add(derTBSCertificate);
943 certificate.add(derSignatureAlgorithm);
944 certificate.add(derSignatureValue);
945 DERValue derCertificate = new DERValue(DER.CONSTRUCTED | DER.SEQUENCE,
948 ByteArrayOutputStream baos = new ByteArrayOutputStream();
949 DERWriter.write(baos, derCertificate);
950 byte[] result = baos.toByteArray();
951 if (Configuration.DEBUG)
952 log.exiting(this.getClass().getName(), "getSelfSignedCertificate"); //$NON-NLS-1$
957 * This method attempts to find, and return, an OID representing the digital
958 * signature algorithm used to sign the certificate. The OIDs returned are
959 * those described in RFC-2459. They are listed here for the sake of
963 * id-dsa-with-sha1 OBJECT IDENTIFIER ::= {
964 * iso(1) member-body(2) us(840) x9-57 (10040) x9cm(4) 3
967 * md2WithRSAEncryption OBJECT IDENTIFIER ::= {
968 * iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) 2
971 * md5WithRSAEncryption OBJECT IDENTIFIER ::= {
972 * iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) 4
975 * sha-1WithRSAEncryption OBJECT IDENTIFIER ::= {
976 * iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) 5
980 * <b>IMPORTANT</b>: This method checks the signature algorithm name against
981 * (a) The GNU algorithm implementation's name, and (b) publicly referenced
982 * names of the same algorithm. In other words this search is not
983 * comprehensive and may fail for uncommon names of the same algorithms.
985 * @return the OID of the signature algorithm in use.
986 * @throws InvalidParameterException if the concrete signature algorithm does
987 * not know its name, no OID is known/supported for that name, or we
988 * were unable to match the name to a known string for which we can
991 protected OID getSignatureAlgorithmOID()
993 String algorithm = signatureAlgorithm.getAlgorithm();
994 // if we already have a non-null signature then the name was valid. the
995 // only case where algorithm is invalid would be if the implementation is
996 // flawed. check anyway
997 if (algorithm == null || algorithm.trim().length() == 0)
998 throw new InvalidParameterException(Messages.getString("Command.52")); //$NON-NLS-1$
1000 algorithm = algorithm.trim();
1001 if (algorithm.equalsIgnoreCase(Registry.DSS_SIG)
1002 || algorithm.equalsIgnoreCase("SHA1withDSA")) //$NON-NLS-1$
1003 return SHA1_WITH_DSA;
1005 if (algorithm.equalsIgnoreCase(Registry.RSA_PKCS1_V1_5_SIG + "-" //$NON-NLS-1$
1006 + Registry.MD2_HASH)
1007 || algorithm.equalsIgnoreCase("MD2withRSA")) //$NON-NLS-1$
1008 return MD2_WITH_RSA;
1010 if (algorithm.equalsIgnoreCase(Registry.RSA_PKCS1_V1_5_SIG + "-" //$NON-NLS-1$
1011 + Registry.MD5_HASH)
1012 || algorithm.equalsIgnoreCase("MD5withRSA") //$NON-NLS-1$
1013 || algorithm.equalsIgnoreCase("rsa")) //$NON-NLS-1$
1014 return MD5_WITH_RSA;
1016 if (algorithm.equalsIgnoreCase(Registry.RSA_PKCS1_V1_5_SIG + "-" //$NON-NLS-1$
1017 + Registry.SHA160_HASH)
1018 || algorithm.equalsIgnoreCase("SHA1withRSA")) //$NON-NLS-1$
1019 return SHA1_WITH_RSA;
1021 throw new InvalidParameterException(Messages.getFormattedString("Command.60", //$NON-NLS-1$
1026 * Saves the key store using the designated password. This operation is called
1027 * by handlers if/when the key store password has changed, or amendements have
1028 * been made to the contents of the store; e.g. addition of a new Key Entry or
1029 * a Trusted Certificate.
1031 * @param password the password protecting the key store.
1032 * @throws IOException if an I/O related exception occurs during the process.
1033 * @throws CertificateException if any of the certificates in the current key
1034 * store could not be persisted.
1035 * @throws NoSuchAlgorithmException if a required data integrity algorithm
1036 * implementation was not found.
1037 * @throws KeyStoreException if the key store has not been loaded previously.
1039 protected void saveKeyStore(char[] password) throws IOException,
1040 KeyStoreException, NoSuchAlgorithmException, CertificateException
1042 if (Configuration.DEBUG)
1043 log.entering(this.getClass().getName(), "saveKeyStore"); //$NON-NLS-1$
1044 URLConnection con = storeURL.openConnection();
1045 con.setDoOutput(true);
1046 con.setUseCaches(false);
1047 OutputStream out = con.getOutputStream();
1049 System.out.println(Messages.getFormattedString("Command.63", storeURL.getPath())); //$NON-NLS-1$
1051 store.store(out, password);
1054 if (Configuration.DEBUG)
1055 log.exiting(this.getClass().getName(), "saveKeyStore"); //$NON-NLS-1$
1059 * Convenience method. Calls the method with the same name passing it the
1060 * same password characters used to initially load the key-store.
1062 * @throws IOException if an I/O related exception occurs during the process.
1063 * @throws KeyStoreException if the key store has not been loaded previously.
1064 * @throws NoSuchAlgorithmException if a required data integrity algorithm
1065 * implementation was not found.
1066 * @throws CertificateException if any of the certificates in the current key
1067 * store could not be persisted.
1069 protected void saveKeyStore() throws IOException, KeyStoreException,
1070 NoSuchAlgorithmException, CertificateException
1072 saveKeyStore(storePasswordChars);
1076 * Prints a human-readable form of the designated certificate to a designated
1077 * {@link PrintWriter}.
1079 * @param certificate the certificate to process.
1080 * @param writer where to print it.
1081 * @throws CertificateEncodingException if an exception occurs while obtaining
1082 * the DER encoded form <code>certificate</code>.
1084 protected void printVerbose(Certificate certificate, PrintWriter writer)
1085 throws CertificateEncodingException
1087 X509Certificate x509 = (X509Certificate) certificate;
1088 writer.println(Messages.getFormattedString("Command.66", x509.getSubjectDN())); //$NON-NLS-1$
1089 writer.println(Messages.getFormattedString("Command.67", x509.getIssuerDN())); //$NON-NLS-1$
1090 writer.println(Messages.getFormattedString("Command.68", x509.getSerialNumber())); //$NON-NLS-1$
1091 writer.println(Messages.getFormattedString("Command.69", x509.getNotBefore())); //$NON-NLS-1$
1092 writer.println(Messages.getFormattedString("Command.70", x509.getNotAfter())); //$NON-NLS-1$
1093 writer.println(Messages.getString("Command.71")); //$NON-NLS-1$
1094 byte[] derBytes = certificate.getEncoded();
1095 writer.println(Messages.getFormattedString("Command.72", digest(md5, derBytes))); //$NON-NLS-1$
1096 writer.println(Messages.getFormattedString("Command.73", digest(sha, derBytes))); //$NON-NLS-1$
1100 * Convenience method. Prints a human-readable form of the designated
1101 * certificate to <code>System.out</code>.
1103 * @param certificate the certificate to process.
1104 * @throws CertificateEncodingException if an exception occurs while obtaining
1105 * the DER encoded form <code>certificate</code>.
1107 protected void printVerbose(Certificate certificate)
1108 throws CertificateEncodingException
1110 printVerbose(certificate, new PrintWriter(System.out, true));
1114 * Digest the designated contents with MD5 and return a string representation
1115 * suitable for use as a fingerprint; i.e. sequence of hexadecimal pairs of
1116 * characters separated by a colon.
1118 * @param contents the non-null contents to digest.
1119 * @return a sequence of hexadecimal pairs of characters separated by colons.
1121 protected String digestWithMD5(byte[] contents)
1123 return digest(md5, contents);
1126 private String digest(IMessageDigest hash, byte[] encoded)
1128 hash.update(encoded);
1129 byte[] b = hash.digest();
1130 StringBuilder sb = new StringBuilder().append(Util.toString(b, 0, 1));
1131 for (int i = 1; i < b.length; i++)
1132 sb.append(":").append(Util.toString(b, i, 1)); //$NON-NLS-1$
1134 String result = sb.toString();
1139 * Ensure that the currently set Alias is contained in the currently set key
1140 * store; otherwise throw an exception.
1142 * @throws KeyStoreException if the keystore has not been loaded.
1143 * @throws IllegalArgumentException if the currently set alias is not known to
1144 * the currently set key store.
1146 protected void ensureStoreContainsAlias() throws KeyStoreException
1148 if (! store.containsAlias(alias))
1149 throw new IllegalArgumentException(Messages.getFormattedString("Command.75", //$NON-NLS-1$
1154 * Ensure that the currently set Alias is associated with a Key Entry in the
1155 * currently set key store; otherwise throw an exception.
1157 * @throws KeyStoreException if the keystore has not been loaded.
1158 * @throws SecurityException if the currently set alias is not a Key Entry in
1159 * the currently set key store.
1161 protected void ensureAliasIsKeyEntry() throws KeyStoreException
1163 if (! store.isKeyEntry(alias))
1164 throw new SecurityException(Messages.getFormattedString("Command.77", //$NON-NLS-1$
1168 protected Key getAliasPrivateKey() throws KeyStoreException,
1169 NoSuchAlgorithmException, IOException, UnsupportedCallbackException,
1170 UnrecoverableKeyException
1172 ensureAliasIsKeyEntry();
1174 if (keyPasswordChars == null)
1177 result = store.getKey(alias, storePasswordChars);
1178 // it worked. assign to keyPasswordChars for later use
1179 keyPasswordChars = storePasswordChars;
1181 catch (UnrecoverableKeyException x)
1183 // prompt the user to provide one
1184 setKeyPasswordParam();
1185 result = store.getKey(alias, keyPasswordChars);
1188 result = store.getKey(alias, keyPasswordChars);
1194 * Return a CallbackHandler which uses the Console (System.in and System.out)
1195 * for interacting with the user.
1197 * This method first finds all currently installed security providers capable
1198 * of providing such service and then in turn attempts to instantiate the
1199 * handler from those providers. As soon as one provider returns a non-null
1200 * instance of the callback handler, the search stops and that instance is
1201 * set to be used from now on.
1203 * If no installed providers were found, this method falls back on the GNU
1204 * provider, by-passing the Security search mechanism. The default console
1205 * callback handler implementation is
1206 * {@link gnu.javax.security.auth.callback.ConsoleCallbackHandler}.
1208 * @return a console-based {@link CallbackHandler}.
1210 protected CallbackHandler getCallbackHandler()
1212 if (handler == null)
1213 handler = CallbackUtil.getConsoleHandler();
1218 // Inner class(es) ==========================================================
1220 private class ShutdownHook