1 /* CertReqCmd.java -- The certreq command handler of the keytool
2 Copyright (C) 2006 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package gnu.classpath.tools.keytool;
41 import gnu.classpath.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;
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;
71 import javax.security.auth.callback.UnsupportedCallbackException;
72 import javax.security.auth.x500.X500Principal;
75 * The <b>-certreq</b> keytool command handler is used to generate a Certificate
76 * Signing Request (CSR) in PKCS#10 format.
78 * The ASN.1 specification of a CSR, as stated in RFC-2986 is as follows:
81 * CertificationRequest ::= SEQUENCE {
82 * certificationRequestInfo CertificationRequestInfo,
83 * signatureAlgorithm AlgorithmIdentifier,
84 * signature BIT STRING
87 * CertificationRequestInfo ::= SEQUENCE {
88 * version INTEGER -- v1(0)
90 * subjectPKInfo SubjectPublicKeyInfo,
91 * attributes [0] IMPLICIT Attributes -- see note later
94 * SubjectPublicKeyInfo ::= SEQUENCE {
95 * algorithm AlgorithmIdentifier,
96 * subjectPublicKey BIT STRING
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.
105 * Possible options for this command are:
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.
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.
127 * <dt>-file FILE_NAME</dt>
129 * <dt>-keypass PASSWORD</dt>
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()}
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>
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>.
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.
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.
167 * <dd>Use this option to enable more verbose output.
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>
175 class CertReqCmd extends Command
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;
189 // default 0-arguments constructor
191 // public setters -----------------------------------------------------------
193 /** @param alias the alias to use. */
194 public void setAlias(String alias)
200 * @param algorithm the canonical name of the digital signature algorithm to
203 public void setSigalg(String algorithm)
205 this._sigAlgorithm = algorithm;
208 /** @param pathName the fully qualified path name of the file to process. */
209 public void setFile(String pathName)
211 this._certReqFileName = pathName;
214 /** @param password the (private) key password to use. */
215 public void setKeypass(String password)
217 this._password = password;
220 /** @param type the key-store type to use. */
221 public void setStoretype(String type)
226 /** @param url the key-store URL to use. */
227 public void setKeystore(String url)
232 /** @param password the key-store password to use. */
233 public void setStorepass(String password)
235 this._ksPassword = password;
238 /** @param className a security provider fully qualified class name to use. */
239 public void setProvider(String className)
241 this._providerClassName = className;
245 * @param flag whether to use, or not, a <code>NULL</code> DER value for
246 * the certificate's Attributes field.
248 public void setAttributes(String flag)
250 this.nullAttributes = Boolean.valueOf(flag).booleanValue();
253 // life-cycle methods -------------------------------------------------------
255 void setup() throws Exception
257 setOutputStreamParam(_certReqFileName);
258 setKeyStoreParams(_providerClassName, _ksType, _ksPassword, _ksURL);
259 setAliasParam(_alias);
260 setKeyPasswordNoPrompt(_password);
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$
273 void start() throws KeyStoreException, NoSuchAlgorithmException, IOException,
274 UnsupportedCallbackException, UnrecoverableKeyException,
275 InvalidKeyException, SignatureException
277 log.entering(this.getClass().getName(), "start"); //$NON-NLS-1$
279 // 1. get the key entry and certificate chain associated to alias
280 Key privateKey = getAliasPrivateKey();
281 Certificate[] chain = store.getCertificateChain(alias);
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();
288 // 3. generate the CSR
289 setSignatureAlgorithmParam(_sigAlgorithm, privateKey);
290 byte[] derBytes = getCSR(aliasName, publicKey, (PrivateKey) privateKey);
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$
302 System.out.println(Messages.getFormattedString("CertReqCmd.27", //$NON-NLS-1$
304 System.out.println(Messages.getString("CertReqCmd.28")); //$NON-NLS-1$
309 log.exiting(this.getClass().getName(), "start"); //$NON-NLS-1$
312 // own methods --------------------------------------------------------------
316 log.entering(this.getClass().getName(), "getParser"); //$NON-NLS-1$
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$
326 public void parsed(String argument) throws OptionException
331 options.add(new Option(Main.SIGALG_OPT,
332 Messages.getString("CertReqCmd.20"), //$NON-NLS-1$
333 Messages.getString("CertReqCmd.19")) //$NON-NLS-1$
335 public void parsed(String argument) throws OptionException
337 _sigAlgorithm = argument;
340 options.add(new Option(Main.FILE_OPT,
341 Messages.getString("CertReqCmd.18"), //$NON-NLS-1$
342 Messages.getString("CertReqCmd.17")) //$NON-NLS-1$
344 public void parsed(String argument) throws OptionException
346 _certReqFileName = argument;
349 options.add(new Option(Main.KEYPASS_OPT,
350 Messages.getString("CertReqCmd.16"), //$NON-NLS-1$
351 Messages.getString("CertReqCmd.9")) //$NON-NLS-1$
353 public void parsed(String argument) throws OptionException
355 _password = argument;
358 options.add(new Option(Main.STORETYPE_OPT,
359 Messages.getString("CertReqCmd.14"), //$NON-NLS-1$
360 Messages.getString("CertReqCmd.13")) //$NON-NLS-1$
362 public void parsed(String argument) throws OptionException
367 options.add(new Option(Main.KEYSTORE_OPT,
368 Messages.getString("CertReqCmd.12"), //$NON-NLS-1$
369 Messages.getString("CertReqCmd.11")) //$NON-NLS-1$
371 public void parsed(String argument) throws OptionException
376 options.add(new Option(Main.STOREPASS_OPT,
377 Messages.getString("CertReqCmd.10"), //$NON-NLS-1$
378 Messages.getString("CertReqCmd.9")) //$NON-NLS-1$
380 public void parsed(String argument) throws OptionException
382 _ksPassword = argument;
385 options.add(new Option(Main.PROVIDER_OPT,
386 Messages.getString("CertReqCmd.8"), //$NON-NLS-1$
387 Messages.getString("CertReqCmd.7")) //$NON-NLS-1$
389 public void parsed(String argument) throws OptionException
391 _providerClassName = argument;
394 options.add(new Option(Main.VERBOSE_OPT,
395 Messages.getString("CertReqCmd.6")) //$NON-NLS-1$
397 public void parsed(String argument) throws OptionException
402 options.add(new Option(ATTRIBUTES_OPT,
403 Messages.getString("CertReqCmd.5")) //$NON-NLS-1$
405 public void parsed(String argument) throws OptionException
407 nullAttributes = true;
412 log.exiting(this.getClass().getName(), "getParser", result); //$NON-NLS-1$
420 * @return the DER encoded Certificate Signing Request.
421 * @throws IOException
422 * @throws InvalidKeyException
423 * @throws SignatureException
425 private byte[] getCSR(X500Principal aliasName, PublicKey publicKey,
426 PrivateKey privateKey)
427 throws IOException, InvalidKeyException, SignatureException
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,
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,
443 OID sigAlgorithmID = getSignatureAlgorithmOID();
444 DERValue derSigAlgorithmID = new DERValue(DER.OBJECT_IDENTIFIER,
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));
451 sigAlgorithm.trimToSize();
452 DERValue derSignatureAlgorithm = new DERValue(DER.CONSTRUCTED | DER.SEQUENCE,
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));
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);
466 ByteArrayOutputStream baos = new ByteArrayOutputStream();
467 DERWriter.write(baos, derCSR);
468 byte[] result = baos.toByteArray();