OSDN Git Service

2006-06-09 Thomas Fitzsimmons <fitzsim@redhat.com>
[pf3gnuchains/gcc-fork.git] / libjava / classpath / tools / gnu / classpath / tools / keytool / CertReqCmd.java
1 /* CertReqCmd.java -- The certreq command handler of the keytool
2    Copyright (C) 2006 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package gnu.classpath.tools.keytool;
40
41 import gnu.classpath.tools.getopt.ClasspathToolParser;
42 import gnu.classpath.tools.getopt.Option;
43 import gnu.classpath.tools.getopt.OptionException;
44 import gnu.classpath.tools.getopt.OptionGroup;
45 import gnu.classpath.tools.getopt.Parser;
46 import gnu.java.security.OID;
47 import gnu.java.security.der.BitString;
48 import gnu.java.security.der.DER;
49 import gnu.java.security.der.DERReader;
50 import gnu.java.security.der.DERValue;
51 import gnu.java.security.der.DERWriter;
52 import gnu.java.security.util.Base64;
53
54 import java.io.ByteArrayOutputStream;
55 import java.io.IOException;
56 import java.io.PrintWriter;
57 import java.math.BigInteger;
58 import java.security.InvalidKeyException;
59 import java.security.Key;
60 import java.security.KeyStoreException;
61 import java.security.NoSuchAlgorithmException;
62 import java.security.PrivateKey;
63 import java.security.PublicKey;
64 import java.security.SignatureException;
65 import java.security.UnrecoverableKeyException;
66 import java.security.cert.Certificate;
67 import java.security.cert.X509Certificate;
68 import java.util.ArrayList;
69 import java.util.logging.Logger;
70
71 import javax.security.auth.callback.UnsupportedCallbackException;
72 import javax.security.auth.x500.X500Principal;
73
74 /**
75  * The <b>-certreq</b> keytool command handler is used to generate a Certificate
76  * Signing Request (CSR) in PKCS#10 format.
77  * <p>
78  * The ASN.1 specification of a CSR, as stated in RFC-2986 is as follows:
79  * <p>
80  * <pre>
81  * CertificationRequest ::= SEQUENCE {
82  *   certificationRequestInfo  CertificationRequestInfo,
83  *   signatureAlgorithm        AlgorithmIdentifier,
84  *   signature                 BIT STRING
85  * }
86  * 
87  * CertificationRequestInfo ::= SEQUENCE {
88  *   version           INTEGER -- v1(0)
89  *   subject           Name,
90  *   subjectPKInfo     SubjectPublicKeyInfo,
91  *   attributes    [0] IMPLICIT Attributes -- see note later
92  * }
93  * 
94  * SubjectPublicKeyInfo ::= SEQUENCE {
95  *   algorithm         AlgorithmIdentifier,
96  *   subjectPublicKey  BIT STRING
97  * }
98  * </pre>
99  * <b>IMPORTANT</b>: Some documentation (e.g. RSA examples) claims that the
100  * <code>attributes</code> field is <i>OPTIONAL</i> while <i>RFC-2986</i>
101  * implies the opposite. This implementation considers this field, by default,
102  * as <i>OPTIONAL</i>, unless the option <code>-attributes</code> is included
103  * on the command line.
104  * <p>
105  * Possible options for this command are:
106  * <p>
107  * <dl>
108  *      <dt>-alias ALIAS</dt>
109  *      <dd>Every entry, be it a <i>Key Entry</i> or a <i>Trusted
110  *      Certificate</i>, in a key store is uniquely identified by a user-defined
111  *      <i>Alias</i> string. Use this option to specify the <i>Alias</i> to use
112  *      when referring to an entry in the key store. Unless specified otherwise,
113  *      a default value of <code>mykey</code> shall be used when this option is
114  *      omitted from the command line.
115  *      <p></dd>
116  *      
117  *      <dt>-sigalg ALGORITHM</dt>
118  *      <dd>The canonical name of the digital signature algorithm to use for
119  *      signing the certificate. If this option is omitted, a default value will
120  *      be chosen based on the type of the private key associated with the
121  *      designated <i>Alias</i>. If the private key is a <code>DSA</code> one,
122  *      the value for the signature algorithm will be <code>SHA1withDSA</code>.
123  *      If on the other hand the private key is an <code>RSA</code> one, then
124  *      the tool will use <code>MD5withRSA</code> as the signature algorithm.
125  *      <p></dd>
126  *      
127  *      <dt>-file FILE_NAME</dt>
128  *      
129  *      <dt>-keypass PASSWORD</dt>
130  *      
131  *      <dt>-storetype STORE_TYPE</dt>
132  *      <dd>Use this option to specify the type of the key store to use. The
133  *      default value, if this option is omitted, is that of the property
134  *      <code>keystore.type</code> in the security properties file, which is
135  *      obtained by invoking the {@link java.security.KeyStore#getDefaultType()}
136  *      static method.
137  *      <p></dd>
138  *      
139  *      <dt>-keystore URL</dt>
140  *      <dd>Use this option to specify the location of the key store to use.
141  *      The default value is a file {@link java.net.URL} referencing the file
142  *      named <code>.keystore</code> located in the path returned by the call to
143  *      {@link java.lang.System#getProperty(String)} using <code>user.home</code>
144  *      as argument.
145  *      <p>
146  *      If a URL was specified, but was found to be malformed --e.g. missing
147  *      protocol element-- the tool will attempt to use the URL value as a file-
148  *      name (with absolute or relative path-name) of a key store --as if the
149  *      protocol was <code>file:</code>.
150  *      <p></dd>
151  *      
152  *      <dt>-storepass PASSWORD</dt>
153  *      <dd>Use this option to specify the password protecting the key store. If
154  *      this option is omitted from the command line, you will be prompted to
155  *      provide a password.
156  *      <p></dd>
157  *      
158  *      <dt>-provider PROVIDER_CLASS_NAME</dt>
159  *      <dd>A fully qualified class name of a Security Provider to add to the
160  *      current list of Security Providers already installed in the JVM in-use.
161  *      If a provider class is specified with this option, and was successfully
162  *      added to the runtime --i.e. it was not already installed-- then the tool
163  *      will attempt to removed this Security Provider before exiting.
164  *      <p></dd>
165  *      
166  *      <dt>-v</dt>
167  *      <dd>Use this option to enable more verbose output.
168  *      <p></dd>
169  *      
170  *      <dt>-attributes</dt>
171  *      <dd>Use this option to force the tool to encode a NULL DER value in the
172  *      CSR as the value of the Attributes field.</dd>
173  * </dl>
174  */
175 class CertReqCmd extends Command
176 {
177   private static final Logger log = Logger.getLogger(CertReqCmd.class.getName());
178   private static final String ATTRIBUTES_OPT = "attributes"; //$NON-NLS-1$
179   protected String _alias;
180   protected String _sigAlgorithm;
181   protected String _certReqFileName;
182   protected String _password;
183   protected String _ksType;
184   protected String _ksURL;
185   protected String _ksPassword;
186   protected String _providerClassName;
187   protected boolean nullAttributes;
188
189   // default 0-arguments constructor
190
191   // public setters -----------------------------------------------------------
192
193   /** @param alias the alias to use. */
194   public void setAlias(String alias)
195   {
196     this._alias = alias;
197   }
198
199   /**
200    * @param algorithm the canonical name of the digital signature algorithm to
201    *          use.
202    */
203   public void setSigalg(String algorithm)
204   {
205     this._sigAlgorithm = algorithm;
206   }
207
208   /** @param pathName the fully qualified path name of the file to process. */
209   public void setFile(String pathName)
210   {
211     this._certReqFileName = pathName;
212   }
213
214   /** @param password the (private) key password to use. */
215   public void setKeypass(String password)
216   {
217     this._password = password;
218   }
219
220   /** @param type the key-store type to use. */
221   public void setStoretype(String type)
222   {
223     this._ksType = type;
224   }
225
226   /** @param url the key-store URL to use. */
227   public void setKeystore(String url)
228   {
229     this._ksURL = url;
230   }
231
232   /** @param password the key-store password to use. */
233   public void setStorepass(String password)
234   {
235     this._ksPassword = password;
236   }
237
238   /** @param className a security provider fully qualified class name to use. */
239   public void setProvider(String className)
240   {
241     this._providerClassName = className;
242   }
243
244   /**
245    * @param flag whether to use, or not, a <code>NULL</code> DER value for
246    *          the certificate's Attributes field.
247    */
248   public void setAttributes(String flag)
249   {
250     this.nullAttributes = Boolean.valueOf(flag).booleanValue();
251   }
252
253   // life-cycle methods -------------------------------------------------------
254
255   void setup() throws Exception
256   {
257     setOutputStreamParam(_certReqFileName);
258     setKeyStoreParams(_providerClassName, _ksType, _ksPassword, _ksURL);
259     setAliasParam(_alias);
260     setKeyPasswordNoPrompt(_password);
261
262     log.finer("-certreq handler will use the following options:"); //$NON-NLS-1$
263     log.finer("  -alias=" + alias); //$NON-NLS-1$
264     log.finer("  -sigalg=" + _sigAlgorithm); //$NON-NLS-1$
265     log.finer("  -file=" + _certReqFileName); //$NON-NLS-1$
266     log.finer("  -storetype=" + storeType); //$NON-NLS-1$
267     log.finer("  -keystore=" + storeURL); //$NON-NLS-1$
268     log.finer("  -provider=" + provider); //$NON-NLS-1$
269     log.finer("  -v=" + verbose); //$NON-NLS-1$
270     log.finer("  -attributes=" + nullAttributes); //$NON-NLS-1$
271   }
272
273   void start() throws KeyStoreException, NoSuchAlgorithmException, IOException,
274       UnsupportedCallbackException, UnrecoverableKeyException,
275       InvalidKeyException, SignatureException
276   {
277     log.entering(this.getClass().getName(), "start"); //$NON-NLS-1$
278
279     // 1. get the key entry and certificate chain associated to alias
280     Key privateKey = getAliasPrivateKey();
281     Certificate[] chain = store.getCertificateChain(alias);
282
283     // 2. get alias's DN and public key to use in the CSR
284     X509Certificate bottomCertificate = (X509Certificate) chain[0];
285     X500Principal aliasName = bottomCertificate.getIssuerX500Principal();
286     PublicKey publicKey = bottomCertificate.getPublicKey();
287
288     // 3. generate the CSR
289     setSignatureAlgorithmParam(_sigAlgorithm, privateKey);
290     byte[] derBytes = getCSR(aliasName, publicKey, (PrivateKey) privateKey);
291
292     // 4. encode it in base-64 and write it to outStream
293     String encoded = Base64.encode(derBytes, 0, derBytes.length, true);
294     PrintWriter writer = new PrintWriter(outStream, true);
295     writer.println("-----BEGIN NEW CERTIFICATE REQUEST-----"); //$NON-NLS-1$
296     writer.println(encoded);
297     writer.println("-----END NEW CERTIFICATE REQUEST-----"); //$NON-NLS-1$
298
299     if (verbose)
300       {
301         if (! systemOut)
302           System.out.println(Messages.getFormattedString("CertReqCmd.27", //$NON-NLS-1$
303                                                          _certReqFileName));
304         System.out.println(Messages.getString("CertReqCmd.28")); //$NON-NLS-1$
305       }
306
307     writer.close();
308
309     log.exiting(this.getClass().getName(), "start"); //$NON-NLS-1$
310   }
311
312   // own methods --------------------------------------------------------------
313
314   Parser getParser()
315   {
316     log.entering(this.getClass().getName(), "getParser"); //$NON-NLS-1$
317
318     Parser result = new ClasspathToolParser(Main.CERTREQ_CMD, true);
319     result.setHeader(Messages.getString("CertReqCmd.25")); //$NON-NLS-1$
320     result.setFooter(Messages.getString("CertReqCmd.24")); //$NON-NLS-1$
321     OptionGroup options = new OptionGroup(Messages.getString("CertReqCmd.23")); //$NON-NLS-1$
322     options.add(new Option(Main.ALIAS_OPT,
323                            Messages.getString("CertReqCmd.22"), //$NON-NLS-1$
324                            Messages.getString("CertReqCmd.21")) //$NON-NLS-1$
325     {
326       public void parsed(String argument) throws OptionException
327       {
328         _alias = argument;
329       }
330     });
331     options.add(new Option(Main.SIGALG_OPT,
332                            Messages.getString("CertReqCmd.20"), //$NON-NLS-1$
333                            Messages.getString("CertReqCmd.19")) //$NON-NLS-1$
334     {
335       public void parsed(String argument) throws OptionException
336       {
337         _sigAlgorithm = argument;
338       }
339     });
340     options.add(new Option(Main.FILE_OPT,
341                            Messages.getString("CertReqCmd.18"), //$NON-NLS-1$
342                            Messages.getString("CertReqCmd.17")) //$NON-NLS-1$
343     {
344       public void parsed(String argument) throws OptionException
345       {
346         _certReqFileName = argument;
347       }
348     });
349     options.add(new Option(Main.KEYPASS_OPT,
350                            Messages.getString("CertReqCmd.16"), //$NON-NLS-1$
351                            Messages.getString("CertReqCmd.9")) //$NON-NLS-1$
352     {
353       public void parsed(String argument) throws OptionException
354       {
355         _password = argument;
356       }
357     });
358     options.add(new Option(Main.STORETYPE_OPT,
359                            Messages.getString("CertReqCmd.14"), //$NON-NLS-1$
360                            Messages.getString("CertReqCmd.13")) //$NON-NLS-1$
361     {
362       public void parsed(String argument) throws OptionException
363       {
364         _ksType = argument;
365       }
366     });
367     options.add(new Option(Main.KEYSTORE_OPT,
368                            Messages.getString("CertReqCmd.12"), //$NON-NLS-1$
369                            Messages.getString("CertReqCmd.11")) //$NON-NLS-1$
370     {
371       public void parsed(String argument) throws OptionException
372       {
373         _ksURL = argument;
374       }
375     });
376     options.add(new Option(Main.STOREPASS_OPT,
377                            Messages.getString("CertReqCmd.10"), //$NON-NLS-1$
378                            Messages.getString("CertReqCmd.9")) //$NON-NLS-1$
379     {
380       public void parsed(String argument) throws OptionException
381       {
382         _ksPassword = argument;
383       }
384     });
385     options.add(new Option(Main.PROVIDER_OPT,
386                            Messages.getString("CertReqCmd.8"), //$NON-NLS-1$
387                            Messages.getString("CertReqCmd.7")) //$NON-NLS-1$
388     {
389       public void parsed(String argument) throws OptionException
390       {
391         _providerClassName = argument;
392       }
393     });
394     options.add(new Option(Main.VERBOSE_OPT,
395                            Messages.getString("CertReqCmd.6")) //$NON-NLS-1$
396     {
397       public void parsed(String argument) throws OptionException
398       {
399         verbose = true;
400       }
401     });
402     options.add(new Option(ATTRIBUTES_OPT,
403                            Messages.getString("CertReqCmd.5")) //$NON-NLS-1$
404     {
405       public void parsed(String argument) throws OptionException
406       {
407         nullAttributes = true;
408       }
409     });
410     result.add(options);
411
412     log.exiting(this.getClass().getName(), "getParser", result); //$NON-NLS-1$
413     return result;
414   }
415
416   /**
417    * @param aliasName
418    * @param publicKey
419    * @param privateKey
420    * @return the DER encoded Certificate Signing Request.
421    * @throws IOException 
422    * @throws InvalidKeyException 
423    * @throws SignatureException 
424    */
425   private byte[] getCSR(X500Principal aliasName, PublicKey publicKey,
426                         PrivateKey privateKey)
427       throws IOException, InvalidKeyException, SignatureException
428   {
429     DERValue derVersion = new DERValue(DER.INTEGER, BigInteger.ZERO);
430     DERValue derSubject = new DERReader(aliasName.getEncoded()).read();
431     DERValue derSubjectPKInfo = new DERReader(publicKey.getEncoded()).read();
432     byte[] b = nullAttributes ? new byte[] { 0x05, 0x00 } : new byte[0];
433     DERValue derAttributes = new DERValue(DER.CONSTRUCTED | DER.CONTEXT | 0,
434                                           b.length, b, null);
435     ArrayList certRequestInfo = new ArrayList(4);
436     certRequestInfo.add(derVersion);
437     certRequestInfo.add(derSubject);
438     certRequestInfo.add(derSubjectPKInfo);
439     certRequestInfo.add(derAttributes);
440     DERValue derCertRequestInfo = new DERValue(DER.CONSTRUCTED | DER.SEQUENCE,
441                                                certRequestInfo);
442
443     OID sigAlgorithmID = getSignatureAlgorithmOID();
444     DERValue derSigAlgorithmID = new DERValue(DER.OBJECT_IDENTIFIER,
445                                               sigAlgorithmID);
446     ArrayList sigAlgorithm = new ArrayList(2);
447     sigAlgorithm.add(derSigAlgorithmID);
448     if (! sigAlgorithmID.equals(Command.SHA1_WITH_DSA)) // it's an RSA-based
449       sigAlgorithm.add(new DERValue(DER.NULL, null));
450
451     sigAlgorithm.trimToSize();
452     DERValue derSignatureAlgorithm = new DERValue(DER.CONSTRUCTED | DER.SEQUENCE,
453                                                   sigAlgorithm);
454     
455     signatureAlgorithm.initSign(privateKey);
456     signatureAlgorithm.update(derCertRequestInfo.getEncoded());
457     byte[] sigBytes = signatureAlgorithm.sign();
458     DERValue derSignature = new DERValue(DER.BIT_STRING, new BitString(sigBytes));
459
460     ArrayList csr = new ArrayList(3);
461     csr.add(derCertRequestInfo);
462     csr.add(derSignatureAlgorithm);
463     csr.add(derSignature);
464     DERValue derCSR = new DERValue(DER.CONSTRUCTED | DER.SEQUENCE, csr);
465
466     ByteArrayOutputStream baos = new ByteArrayOutputStream();
467     DERWriter.write(baos, derCSR);
468     byte[] result = baos.toByteArray();
469
470     return result;
471   }
472 }