1 |
779 |
jeremybenn |
/* 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 |
|
|
}
|