OSDN Git Service

af91e4a715127180251462c2a9b756377165ce25
[pf3gnuchains/gcc-fork.git] / libjava / classpath / tools / gnu / classpath / tools / keytool / Command.java
1 /* Command.java -- Abstract implementation of a keytool command handler
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.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;
59
60 import java.io.ByteArrayOutputStream;
61 import java.io.File;
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;
70 import java.net.URL;
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;
95
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;
101
102 /**
103  * A base class of the keytool command to facilitate implementation of concrete
104  * keytool Handlers.
105  */
106 abstract class Command
107 {
108   // Fields and constants -----------------------------------------------------
109
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;
131
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;
162
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;
173
174   // Constructor(s) -----------------------------------------------------------
175
176   protected Command()
177   {
178     super();
179     shutdownThread = new ShutdownHook();
180     Runtime.getRuntime().addShutdownHook(shutdownThread);
181   }
182
183   // Methods ------------------------------------------------------------------
184
185   /**
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.
190    * <p>
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
194    * exception.
195    * 
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.
200    */
201   public void doCommand() throws Exception
202   {
203     try
204       {
205         setup();
206         start();
207       }
208     finally
209       {
210         teardown();
211         if (shutdownThread != null)
212           Runtime.getRuntime().removeShutdownHook(shutdownThread);
213       }
214   }
215
216   /**
217    * @param flag whether to use, or not, more verbose output while processing
218    *          the command.
219    */
220   public void setVerbose(String flag)
221   {
222     this.verbose = Boolean.valueOf(flag).booleanValue();
223   }
224
225   // life-cycle methods -------------------------------------------------------
226
227   /**
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.
233    * <p>
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...
238    * 
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>.
242    */
243   String[] processArgs(String[] args)
244   {
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$
251     return result;
252   }
253
254   /**
255    * Initialize this concrete command handler for later invocation of the
256    * {@link #start()} or {@link #doCommand()} methods.
257    * <p>
258    * Handlers usually initialize their local variables and resources within the
259    * scope of this call.
260    * 
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
267    *           not found.
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
271    *           not instantiable.
272    * @throws KeyStoreException if an exception occurs during the instantiation
273    *           of the KeyStore.
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.
279    */
280   abstract void setup() throws Exception;
281
282   /**
283    * Do the real work this handler is supposed to do.
284    * <p>
285    * The code in this (abstract) class throws a <i>Not implemented yet</i>
286    * runtime exception. Concrete implementations MUST override this method.
287    * 
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.
303    */
304   void start() throws Exception
305   {
306     throw new RuntimeException("Not implemented yet"); //$NON-NLS-1$
307   }
308
309   /**
310    * Tear down the handler, releasing any resources which may have been
311    * allocated at setup time.
312    */
313   void teardown()
314   {
315     if (Configuration.DEBUG)
316       log.entering(this.getClass().getName(), "teardown"); //$NON-NLS-1$
317     if (storeStream != null)
318       try
319         {
320           storeStream.close();
321         }
322       catch (IOException ignored)
323         {
324           if (Configuration.DEBUG)
325             log.fine("Exception while closing key store URL stream. Ignored: " //$NON-NLS-1$
326                      + ignored);
327         }
328
329     if (outStream != null)
330       {
331         try
332           {
333             outStream.flush();
334           }
335         catch (IOException ignored)
336           {
337           }
338
339         if (! systemOut)
340           try
341             {
342               outStream.close();
343             }
344           catch (IOException ignored)
345             {
346             }
347       }
348
349     if (inStream != null)
350       try
351         {
352           inStream.close();
353         }
354       catch (IOException ignored)
355         {
356         }
357
358     if (providerNdx > 0)
359       ProviderUtil.removeProvider(provider.getName());
360
361     if (Configuration.DEBUG)
362       log.exiting(this.getClass().getName(), "teardown"); //$NON-NLS-1$
363   }
364
365   // parameter setup and validation methods -----------------------------------
366
367   /**
368    * @return a {@link Parser} that knows how to parse the concrete command's
369    *         options.
370    */
371   abstract Parser getParser();
372
373   /**
374    * Convenience method to setup the key store given its type, its password, its
375    * location and portentially a specialized security provider.
376    * <p>
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
380    * location.
381    * 
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
386    *          key store factory.
387    * @param password the potentially null password protecting the key store.
388    * @param url the URL of the key store.
389    */
390   protected void setKeyStoreParams(String className, String type,
391                                    String password, String url)
392       throws IOException, UnsupportedCallbackException, KeyStoreException,
393       NoSuchAlgorithmException, CertificateException
394   {
395     setKeyStoreParams(false, className, type, password, url);
396   }
397
398   /**
399    * Convenience method to setup the key store given its type, its password, its
400    * location and portentially a specialized security provider.
401    * 
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
408    *          key store factory.
409    * @param password the potentially null password protecting the key store.
410    * @param url the URL of the key store.
411    */
412   protected void setKeyStoreParams(boolean createIfNotFound, String className,
413                                    String type, String password, String url)
414       throws IOException, UnsupportedCallbackException, KeyStoreException,
415       NoSuchAlgorithmException, CertificateException
416   {
417     setProviderClassNameParam(className);
418     setKeystoreTypeParam(type);
419     setKeystoreURLParam(createIfNotFound, url, password);
420   }
421
422   /**
423    * Set a security provider class name to (install and) use for key store
424    * related operations.
425    * 
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.
429    */
430   private void setProviderClassNameParam(String className)
431   {
432     if (Configuration.DEBUG)
433       log.fine("setProviderClassNameParam(" + className + ")"); //$NON-NLS-1$ //$NON-NLS-2$
434     if (className != null && className.trim().length() > 0)
435       {
436         className = className.trim();
437         SecurityProviderInfo spi = ProviderUtil.addProvider(className);
438         provider = spi.getProvider();
439         if (provider == null)
440           {
441             if (Configuration.DEBUG)
442               log.fine("Was unable to add provider from class " + className);
443           }
444         providerNdx = spi.getPosition();
445       }
446   }
447
448   /**
449    * Set the type of key store to initialize, load and use.
450    * 
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.
457    */
458   private void setKeystoreTypeParam(String type)
459   {
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();
464     else
465       storeType = type.trim();
466   }
467
468   /**
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.
471    * 
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.
476    */
477   protected void setKeyPasswordParam(String password) throws IOException,
478       UnsupportedCallbackException
479   {
480     setKeyPasswordNoPrompt(password);
481     if (keyPasswordChars == null)
482       setKeyPasswordParam();
483   }
484
485   /**
486    * Set the Alias to use when associating Key Entries and Trusted Certificates
487    * in the current key store.
488    * 
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.
492    */
493   protected void setAliasParam(String name)
494   {
495     alias = name == null ? DEFAULT_ALIAS : name.trim();
496   }
497
498   /**
499    * Set the key password given a command line option argument.
500    * 
501    * @param password a possibly null key password gleaned from the command line.
502    */
503   protected void setKeyPasswordNoPrompt(String password)
504   {
505     if (password != null)
506       keyPasswordChars = password.toCharArray();
507   }
508
509   /**
510    * Prompt the user to provide a password to protect a Key Entry in the key
511    * store.
512    * 
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
517    *           the user.
518    */
519   private void setKeyPasswordParam() throws IOException,
520       UnsupportedCallbackException
521   {
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();
526     pcb.clearPassword();
527     if (keyPasswordChars == null)
528       throw new SecurityException(Messages.getString("Command.23")); //$NON-NLS-1$
529   }
530
531   private void setKeystorePasswordParam(String password) throws IOException,
532       UnsupportedCallbackException
533   {
534     if (password != null)
535       storePasswordChars = password.toCharArray();
536     else // ask the user to provide one
537       {
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();
542         pcb.clearPassword();
543       }
544   }
545
546   /**
547    * Set the key store URL to use.
548    * 
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
560    */
561   private void setKeystoreURLParam(boolean createIfNotFound, String url,
562                                      String password) throws IOException,
563       KeyStoreException, UnsupportedCallbackException, NoSuchAlgorithmException,
564       CertificateException
565   {
566     if (Configuration.DEBUG)
567       log.fine("setKeystoreURLParam(" + url + ")"); //$NON-NLS-1$ //$NON-NLS-2$
568     if (url == null || url.trim().length() == 0)
569       {
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$
573
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$
579       }
580     else
581       {
582         url = url.trim();
583         if (url.indexOf(":") == -1) // if it does not exist create it //$NON-NLS-1$
584           {
585             if (createIfNotFound)
586               new File(url).createNewFile();
587           }
588         url = "file:" + url; //$NON-NLS-1$
589       }
590
591     boolean newKeyStore = false;
592     storeURL = new URL(url);
593     storeStream = storeURL.openStream();
594     if (storeStream.available() == 0)
595       {
596         if (Configuration.DEBUG)
597           log.fine("Store is empty. Will use <null> when loading, to create it"); //$NON-NLS-1$
598         newKeyStore = true;
599       }
600
601     try
602       {
603         store = KeyStore.getInstance(storeType);
604       }
605     catch (KeyStoreException x)
606       {
607         if (provider != null)
608           throw x;
609
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)
619           {
620             x.fillInStackTrace();
621             throw x;
622           }
623         // try again
624         store = KeyStore.getInstance(storeType, provider);
625       }
626
627     setKeystorePasswordParam(password);
628
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.
632     if (newKeyStore)
633       store.load(null, storePasswordChars);
634     else
635       store.load(storeStream, storePasswordChars);
636
637     // close the stream
638     try
639     {
640       storeStream.close();
641       storeStream = null;
642     }
643     catch (IOException x)
644     {
645       if (Configuration.DEBUG)
646         log.fine("Exception while closing the key store input stream: " + x //$NON-NLS-1$
647                  + ". Ignore"); //$NON-NLS-1$
648     }
649   }
650
651   protected void setOutputStreamParam(String fileName) throws SecurityException,
652       IOException
653   {
654     if (fileName == null || fileName.trim().length() == 0)
655       {
656         outStream = System.out;
657         systemOut = true;
658       }
659     else
660       {
661         fileName = fileName.trim();
662         File outFile = new File(fileName);
663         if (! outFile.exists())
664           {
665             boolean ok = outFile.createNewFile();
666             if (!ok)
667               throw new InvalidParameterException(Messages.getFormattedString("Command.19", //$NON-NLS-1$
668                                                                               fileName));
669           }
670         else
671           {
672             if (! outFile.isFile())
673               throw new InvalidParameterException(Messages.getFormattedString("Command.42", //$NON-NLS-1$
674                                                                               fileName));
675             if (! outFile.canWrite())
676               throw new InvalidParameterException(Messages.getFormattedString("Command.44", //$NON-NLS-1$
677                                                                               fileName));
678           }
679         outStream = new FileOutputStream(outFile);
680       }
681   }
682
683   protected void setInputStreamParam(String fileName)
684       throws FileNotFoundException
685   {
686     if (fileName == null || fileName.trim().length() == 0)
687       inStream = System.in;
688     else
689       {
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$
694                                                                           fileName));
695         inStream = new FileInputStream(inFile);
696       }
697   }
698
699   /**
700    * Set both the key-pair generation algorithm, and the digital signature
701    * algorithm instances to use when generating new entries.
702    * 
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.
717    */
718   protected void setAlgorithmParams(String kpAlg, String sigAlg)
719       throws NoSuchAlgorithmException
720   {
721     if (kpAlg == null || kpAlg.trim().length() == 0)
722       kpAlg = DEFAULT_KEY_ALGORITHM;
723     else
724       kpAlg = kpAlg.trim().toLowerCase();
725
726     keyPairGenerator = KeyPairGenerator.getInstance(kpAlg);
727
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;
734       else
735         throw new IllegalArgumentException(
736             Messages.getFormattedString("Command.20", //$NON-NLS-1$
737                                         new String[] { sigAlg, kpAlg }));
738     else
739       sigAlg = sigAlg.trim().toLowerCase();
740
741     signatureAlgorithm = Signature.getInstance(sigAlg);
742   }
743
744   /**
745    * Set the signature algorithm to use when digitally signing private keys,
746    * certificates, etc...
747    * <p>
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}.
757    * 
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.
763    */
764   protected void setSignatureAlgorithmParam(String algorithm, Key privateKey)
765       throws NoSuchAlgorithmException
766   {
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;
772       else
773         throw new InvalidParameterException(Messages.getString("Command.48")); //$NON-NLS-1$
774     else
775       algorithm = algorithm.trim();
776
777     signatureAlgorithm = Signature.getInstance(algorithm);
778   }
779
780   /**
781    * Set the validity period, in number of days, to use when issuing new
782    * certificates.
783    * 
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
787    *          will be used.
788    * @throws NumberFormatException if the designated string is not a decimal
789    *           integer.
790    * @throws InvalidParameterException if the integer value of the non-null
791    *           string is not greater than zero.
792    */
793   protected void setValidityParam(String days)
794   {
795     if (days == null || days.trim().length() == 0)
796       validityInDays = DEFAULT_VALIDITY;
797     else
798       {
799         days = days.trim();
800         validityInDays = Integer.parseInt(days);
801         if (validityInDays < 1)
802           throw new InvalidParameterException(Messages.getString("Command.51")); //$NON-NLS-1$
803       }
804   }
805
806   /**
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.
810    * 
811    * <pre>
812    *  Certificate ::= SEQUENCE {
813    *    tbsCertificate      TBSCertificate,
814    *    signatureAlgorithm  AlgorithmIdentifier,
815    *    signatureValue      BIT STRING
816    *  }
817    *  
818    *  TBSCertificate ::= SEQUENCE {
819    *    version           [0] EXPLICIT Version DEFAULT v1,
820    *    serialNumber          CertificateSerialNumber,
821    *    signature             AlgorithmIdentifier,
822    *    issuer                Name,
823    *    validity              Validity,
824    *    subject               Name,
825    *    subjectPublicKeyInfo  SubjectPublicKeyInfo
826    *  }
827    *  
828    *  Version ::= INTEGER { v1(0), v2(1), v3(2) }
829    *  
830    *  CertificateSerialNumber ::= INTEGER
831    *  
832    *  Validity ::= SEQUENCE {
833    *    notBefore  Time,
834    *    notAfter   Time
835    *  }
836    *  
837    *  Time ::= CHOICE {
838    *    utcTime      UTCTime,
839    *    generalTime  GeneralizedTime
840    *  }
841    *  
842    *  UniqueIdentifier ::= BIT STRING
843    *  
844    *  SubjectPublicKeyInfo ::= SEQUENCE {
845    *    algorithm         AlgorithmIdentifier,
846    *    subjectPublicKey  BIT STRING
847    *  }
848    * </pre>
849    * 
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.
862    */
863   protected byte[] getSelfSignedCertificate(X500DistinguishedName distinguishedName,
864                                             PublicKey publicKey,
865                                             PrivateKey privateKey)
866       throws IOException, SignatureException, InvalidKeyException
867   {
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);
874
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));
881
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:
887     //
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.
893     // 
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));
900
901     DERValue derSignature = new DERValue(DER.CONSTRUCTED | DER.SEQUENCE,
902                                          signature);
903
904     DERValue derIssuer = new DERReader(distinguishedName.getDer()).read();
905
906     long notBefore = System.currentTimeMillis();
907     long notAfter = notBefore + validityInDays * MILLIS_IN_A_DAY;
908     
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,
913                                         validity);
914
915     // for a self-signed certificate subject and issuer are identical
916     DERValue derSubject = derIssuer;
917
918     DERValue derSubjectPublicKeyInfo = new DERReader(publicKey.getEncoded()).read();
919
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,
929                                               tbsCertificate);
930
931     // The 'signature' field MUST contain the same algorithm identifier as the
932     // 'signatureAlgorithm' field in the sequence Certificate.
933     DERValue derSignatureAlgorithm = derSignature;
934
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));
940
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,
946                                            certificate);
947
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$
953     return result;
954   }
955
956   /**
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
960    * completness.
961    * 
962    * <pre>
963    *  id-dsa-with-sha1 OBJECT IDENTIFIER ::= {
964    *    iso(1) member-body(2) us(840) x9-57 (10040) x9cm(4) 3
965    *  }
966    *  
967    *  md2WithRSAEncryption OBJECT IDENTIFIER ::= {
968    *    iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) 2
969    *  }
970    *  
971    *  md5WithRSAEncryption OBJECT IDENTIFIER ::= {
972    *    iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) 4
973    *  }
974    *  
975    *  sha-1WithRSAEncryption OBJECT IDENTIFIER ::= {
976    *    iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) 5
977    *  }
978    * </pre>
979    * 
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.
984    * 
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
989    *           return an OID.
990    */
991   protected OID getSignatureAlgorithmOID()
992   {
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$
999
1000     algorithm = algorithm.trim();
1001     if (algorithm.equalsIgnoreCase(Registry.DSS_SIG)
1002         || algorithm.equalsIgnoreCase("SHA1withDSA")) //$NON-NLS-1$
1003       return SHA1_WITH_DSA;
1004     
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;
1009
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;
1015
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;
1020
1021     throw new InvalidParameterException(Messages.getFormattedString("Command.60", //$NON-NLS-1$
1022                                                                     algorithm));
1023   }
1024
1025   /**
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.
1030    * 
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.
1038    */
1039   protected void saveKeyStore(char[] password) throws IOException,
1040       KeyStoreException, NoSuchAlgorithmException, CertificateException
1041   {
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();
1048     if (verbose)
1049       System.out.println(Messages.getFormattedString("Command.63", storeURL.getPath())); //$NON-NLS-1$
1050
1051     store.store(out, password);
1052     out.flush();
1053     out.close();
1054     if (Configuration.DEBUG)
1055       log.exiting(this.getClass().getName(), "saveKeyStore"); //$NON-NLS-1$
1056   }
1057
1058   /**
1059    * Convenience method. Calls the method with the same name passing it the
1060    * same password characters used to initially load the key-store. 
1061    * 
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.
1068    */
1069   protected void saveKeyStore() throws IOException, KeyStoreException,
1070       NoSuchAlgorithmException, CertificateException
1071   {
1072     saveKeyStore(storePasswordChars);
1073   }
1074
1075   /**
1076    * Prints a human-readable form of the designated certificate to a designated
1077    * {@link PrintWriter}.
1078    * 
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>.
1083    */
1084   protected void printVerbose(Certificate certificate, PrintWriter writer)
1085       throws CertificateEncodingException
1086   {
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$
1097   }
1098
1099   /**
1100    * Convenience method. Prints a human-readable form of the designated
1101    * certificate to <code>System.out</code>.
1102    * 
1103    * @param certificate the certificate to process.
1104    * @throws CertificateEncodingException if an exception occurs while obtaining
1105    *           the DER encoded form <code>certificate</code>.
1106    */
1107   protected void printVerbose(Certificate certificate)
1108       throws CertificateEncodingException
1109   {
1110     printVerbose(certificate, new PrintWriter(System.out, true));
1111   }
1112
1113   /**
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.
1117    * 
1118    * @param contents the non-null contents to digest.
1119    * @return a sequence of hexadecimal pairs of characters separated by colons.
1120    */
1121   protected String digestWithMD5(byte[] contents)
1122   {
1123     return digest(md5, contents);
1124   }
1125
1126   private String digest(IMessageDigest hash, byte[] encoded)
1127   {
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$
1133
1134     String result = sb.toString();
1135     return result;
1136   }
1137
1138   /**
1139    * Ensure that the currently set Alias is contained in the currently set key
1140    * store; otherwise throw an exception.
1141    * 
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.
1145    */
1146   protected void ensureStoreContainsAlias() throws KeyStoreException
1147   {
1148     if (! store.containsAlias(alias))
1149       throw new IllegalArgumentException(Messages.getFormattedString("Command.75", //$NON-NLS-1$
1150                                                                      alias));
1151   }
1152
1153   /**
1154    * Ensure that the currently set Alias is associated with a Key Entry in the
1155    * currently set key store; otherwise throw an exception.
1156    * 
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.
1160    */
1161   protected void ensureAliasIsKeyEntry() throws KeyStoreException
1162   {
1163     if (! store.isKeyEntry(alias))
1164       throw new SecurityException(Messages.getFormattedString("Command.77", //$NON-NLS-1$
1165                                                               alias));
1166   }
1167
1168   protected Key getAliasPrivateKey() throws KeyStoreException,
1169       NoSuchAlgorithmException, IOException, UnsupportedCallbackException,
1170       UnrecoverableKeyException
1171   {
1172     ensureAliasIsKeyEntry();
1173     Key result;
1174     if (keyPasswordChars == null)
1175       try
1176         {
1177           result = store.getKey(alias, storePasswordChars);
1178           // it worked. assign to keyPasswordChars for later use
1179           keyPasswordChars = storePasswordChars;
1180         }
1181       catch (UnrecoverableKeyException x)
1182         {
1183           // prompt the user to provide one
1184           setKeyPasswordParam();
1185           result = store.getKey(alias, keyPasswordChars);
1186         }
1187     else
1188       result = store.getKey(alias, keyPasswordChars);
1189
1190     return result;
1191   }
1192
1193   /**
1194    * Return a CallbackHandler which uses the Console (System.in and System.out)
1195    * for interacting with the user.
1196    * <p>
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.
1202    * <p>
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}.
1207    * 
1208    * @return a console-based {@link CallbackHandler}.
1209    */
1210   protected CallbackHandler getCallbackHandler()
1211   {
1212     if (handler == null)
1213       handler = CallbackUtil.getConsoleHandler();
1214
1215     return handler;
1216   }
1217
1218   // Inner class(es) ==========================================================
1219
1220   private class ShutdownHook
1221       extends Thread
1222   {
1223     public void run()
1224     {
1225       teardown();
1226     }
1227   }
1228 }