Merge from mainline.
[official-gcc.git] / libjava / classpath / tools / gnu / classpath / tools / jarsigner / Main.java
blobf460a96cc3a260d476216ba0330f30dfc8937406
1 /* Main.java -- JAR signing and verification tool not unlike jarsigner
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)
9 any later version.
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
19 02110-1301 USA.
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.
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.jarsigner;
41 import gnu.classpath.SystemProperties;
42 import gnu.classpath.tools.HelpPrinter;
43 import gnu.classpath.tools.common.CallbackUtil;
44 import gnu.classpath.tools.common.ProviderUtil;
45 import gnu.java.security.OID;
46 import gnu.java.security.Registry;
47 import gnu.javax.security.auth.callback.ConsoleCallbackHandler;
49 import java.io.File;
50 import java.io.FileNotFoundException;
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.net.URL;
54 import java.security.Key;
55 import java.security.KeyStore;
56 import java.security.KeyStoreException;
57 import java.security.NoSuchAlgorithmException;
58 import java.security.PrivateKey;
59 import java.security.Provider;
60 import java.security.Security;
61 import java.security.UnrecoverableKeyException;
62 import java.security.cert.Certificate;
63 import java.security.cert.CertificateException;
64 import java.util.Locale;
65 import java.util.jar.Attributes.Name;
66 import java.util.logging.Logger;
68 import javax.security.auth.callback.Callback;
69 import javax.security.auth.callback.CallbackHandler;
70 import javax.security.auth.callback.PasswordCallback;
71 import javax.security.auth.callback.UnsupportedCallbackException;
73 /**
74 * The GNU Classpath implementation of the <i>jarsigner</i> tool.
75 * <p>
76 * The <i>jarsigner</i> tool is used to sign and verify JAR (Java ARchive)
77 * files.
78 * <p>
79 * This implementation is intended to be compatible with the behaviour
80 * described in the public documentation of the same tool included in JDK 1.4.
82 public class Main
84 private static final Logger log = Logger.getLogger(Main.class.getName());
85 private static final String HELP_PATH = "jarsigner/jarsigner.txt"; //$NON-NLS-1$
86 private static final Locale EN_US_LOCALE = new Locale("en", "US"); //$NON-NLS-1$ //$NON-NLS-2$
87 static final String DIGEST = "SHA1-Digest"; //$NON-NLS-1$
88 static final String DIGEST_MANIFEST = "SHA1-Digest-Manifest"; //$NON-NLS-1$
89 static final Name DIGEST_ATTR = new Name(DIGEST);
90 static final Name DIGEST_MANIFEST_ATTR = new Name(DIGEST_MANIFEST);
91 static final OID DSA_SIGNATURE_OID = new OID(Registry.DSA_OID_STRING);
92 static final OID RSA_SIGNATURE_OID = new OID(Registry.RSA_OID_STRING);
94 private boolean verify;
95 private String ksURL;
96 private String ksType;
97 private String password;
98 private String ksPassword;
99 private String sigFileName;
100 private String signedJarFileName;
101 private boolean verbose;
102 private boolean certs;
103 private boolean internalSF;
104 private boolean sectionsOnly;
105 private String providerClassName;
106 private String jarFileName;
107 private String alias;
109 protected Provider provider;
110 private boolean providerInstalled;
111 private char[] ksPasswordChars;
112 private KeyStore store;
113 private char[] passwordChars;
114 private PrivateKey signerPrivateKey;
115 private Certificate[] signerCertificateChain;
116 /** The callback handler to use when needing to interact with user. */
117 private CallbackHandler handler;
119 private Main()
121 super();
124 public static final void main(String[] args)
126 log.entering(Main.class.getName(), "main", args); //$NON-NLS-1$
128 Main tool = new Main();
131 tool.processArgs(args);
132 tool.start();
134 catch (SecurityException x)
136 log.throwing(Main.class.getName(), "main", x); //$NON-NLS-1$
137 System.err.println(Messages.getString("Main.7") + x.getMessage()); //$NON-NLS-1$
139 catch (Exception x)
141 log.throwing(Main.class.getName(), "main", x); //$NON-NLS-1$
142 System.err.println(Messages.getString("Main.9") + x); //$NON-NLS-1$
145 tool.teardown();
147 log.exiting(Main.class.getName(), "main"); //$NON-NLS-1$
148 // System.exit(0);
151 // helper methods -----------------------------------------------------------
154 * Read the command line arguments setting the tool's parameters in
155 * preparation for the user desired action.
157 * @param args an array of options (strings).
158 * @throws Exception if an exceptio occurs during the process.
160 private void processArgs(String[] args) throws Exception
162 log.entering(this.getClass().getName(), "processArgs", args); //$NON-NLS-1$
164 HelpPrinter.checkHelpKey(args, HELP_PATH);
165 if (args == null || args.length == 0)
166 HelpPrinter.printHelpAndExit(HELP_PATH);
168 int limit = args.length;
169 log.finest("args.length=" + limit); //$NON-NLS-1$
170 int i = 0;
171 String opt;
172 while (i < limit)
174 opt = args[i++];
175 log.finest("args[" + (i - 1) + "]=" + opt); //$NON-NLS-1$ //$NON-NLS-2$
176 if (opt == null || opt.length() == 0)
177 continue;
179 if ("-verify".equals(opt)) // -verify //$NON-NLS-1$
180 verify = true;
181 else if ("-keystore".equals(opt)) // -keystore URL //$NON-NLS-1$
182 ksURL = args[i++];
183 else if ("-storetype".equals(opt)) // -storetype STORE_TYPE //$NON-NLS-1$
184 ksType = args[i++];
185 else if ("-storepass".equals(opt)) // -storepass PASSWORD //$NON-NLS-1$
186 ksPassword = args[i++];
187 else if ("-keypass".equals(opt)) // -keypass PASSWORD //$NON-NLS-1$
188 password = args[i++];
189 else if ("-sigfile".equals(opt)) // -sigfile NAME //$NON-NLS-1$
190 sigFileName = args[i++];
191 else if ("-signedjar".equals(opt)) // -signedjar FILE_NAME //$NON-NLS-1$
192 signedJarFileName = args[i++];
193 else if ("-verbose".equals(opt)) // -verbose //$NON-NLS-1$
194 verbose = true;
195 else if ("-certs".equals(opt)) // -certs //$NON-NLS-1$
196 certs = true;
197 else if ("-internalsf".equals(opt)) // -internalsf //$NON-NLS-1$
198 internalSF = true;
199 else if ("-sectionsonly".equals(opt)) // -sectionsonly //$NON-NLS-1$
200 sectionsOnly = true;
201 else if ("-provider".equals(opt)) // -provider PROVIDER_CLASS_NAME //$NON-NLS-1$
202 providerClassName = args[i++];
203 else
205 jarFileName = opt;
206 if (! verify)
207 alias = args[i++];
209 break;
213 if (i < limit) // more options than needed
214 log.fine("Last argument is assumed at index #" + (i - 1) //$NON-NLS-1$
215 + ". Remaining arguments (" + args[i] //$NON-NLS-1$
216 + "...) will be ignored"); //$NON-NLS-1$
218 setupCommonParams();
219 if (verify)
221 log.finer("Will verify with the following parameters:"); //$NON-NLS-1$
222 log.finer(" jar-file = '" + jarFileName + "'"); //$NON-NLS-1$ //$NON-NLS-2$
223 log.finer("Options:"); //$NON-NLS-1$
224 log.finer(" provider = '" + providerClassName + "'"); //$NON-NLS-1$ //$NON-NLS-2$
225 log.finer(" verbose ? " + verbose); //$NON-NLS-1$
226 log.finer(" certs ? " + certs); //$NON-NLS-1$
227 log.finer(" internalsf ? " + internalSF); //$NON-NLS-1$
228 log.finer(" sectionsonly ? " + sectionsOnly); //$NON-NLS-1$
230 else // sign
232 setupSigningParams();
234 log.finer("Will sign with the following parameters:"); //$NON-NLS-1$
235 log.finer(" jar-file = '" + jarFileName + "'"); //$NON-NLS-1$ //$NON-NLS-2$
236 log.finer(" alias = '" + alias + "'"); //$NON-NLS-1$ //$NON-NLS-2$
237 log.finer("Options:"); //$NON-NLS-1$
238 log.finer(" keystore = '" + ksURL + "'"); //$NON-NLS-1$ //$NON-NLS-2$
239 log.finer(" storetype = '" + ksType + "'"); //$NON-NLS-1$ //$NON-NLS-2$
240 log.finer(" storepass = '" + ksPassword + "'"); //$NON-NLS-1$ //$NON-NLS-2$
241 log.finer(" keypass = '" + password + "'"); //$NON-NLS-1$ //$NON-NLS-2$
242 log.finer(" sigfile = '" + sigFileName + "'"); //$NON-NLS-1$ //$NON-NLS-2$
243 log.finer(" signedjar = '" + signedJarFileName + "'"); //$NON-NLS-1$ //$NON-NLS-2$
244 log.finer(" provider = '" + providerClassName + "'"); //$NON-NLS-1$ //$NON-NLS-2$
245 log.finer(" verbose ? " + verbose); //$NON-NLS-1$
246 log.finer(" internalsf ? " + internalSF); //$NON-NLS-1$
247 log.finer(" sectionsonly ? " + sectionsOnly); //$NON-NLS-1$
250 log.exiting(this.getClass().getName(), "processArgs"); //$NON-NLS-1$
254 * Invokes the <code>start()</code> method of the concrete handler.
255 * <p>
256 * Depending on the result of processing the command line arguments, this
257 * handler may be one for signing the jar, or verifying it.
259 * @throws Exception if an exception occurs during the process.
261 private void start() throws Exception
263 log.entering(this.getClass().getName(), "start"); //$NON-NLS-1$
265 if (verify)
267 JarVerifier jv = new JarVerifier(this);
268 jv.start();
270 else
272 JarSigner js = new JarSigner(this);
273 js.start();
276 log.exiting(this.getClass().getName(), "start"); //$NON-NLS-1$
280 * Ensures that the underlying JVM is left in the same state as we found it
281 * when we first launched the tool. Specifically, if we have installed a new
282 * security provider then now is the time to remove it.
283 * <p>
284 * Note (rsn): this may not be necessary if we terminate the JVM; i.e. call
285 * {@link System#exit(int)} at the end of the tool's invocation. Nevertheless
286 * it's good practive to return the JVM to its initial state.
288 private void teardown()
290 log.entering(this.getClass().getName(), "teardown"); //$NON-NLS-1$
292 if (providerInstalled)
293 ProviderUtil.removeProvider(provider.getName());
295 log.exiting(this.getClass().getName(), "teardown"); //$NON-NLS-1$
299 * After processing the command line arguments, this method is invoked to
300 * process the common parameters which may have been encountered among the
301 * actual arguments.
302 * <p>
303 * Common parameters are those which are allowed in both signing and
304 * verification modes.
306 * @throws InstantiationException if a security provider class name is
307 * specified but that class name is that of either an interface or
308 * an abstract class.
309 * @throws IllegalAccessException if a security provider class name is
310 * specified but no 0-arguments constructor is defined for that
311 * class.
312 * @throws ClassNotFoundException if a security provider class name is
313 * specified but no such class was found in the classpath.
314 * @throws IOException if the JAR file name for signing, or verifying, does
315 * not exist, exists but denotes a directory, or is not readable.
317 private void setupCommonParams() throws InstantiationException,
318 IllegalAccessException, ClassNotFoundException, IOException
320 log.entering(this.getClass().getName(), "setupCommonParams"); //$NON-NLS-1$
322 if (jarFileName == null)
323 HelpPrinter.printHelpAndExit(HELP_PATH);
325 File jar = new File(jarFileName);
326 if (! jar.exists())
327 throw new FileNotFoundException(jarFileName);
329 if (jar.isDirectory())
330 throw new IOException(Messages.getFormattedString("Main.70", jarFileName)); //$NON-NLS-1$
332 if (! jar.canRead())
333 throw new IOException(Messages.getFormattedString("Main.72", jarFileName)); //$NON-NLS-1$ //$NON-NLS-2$
335 if (providerClassName != null && providerClassName.length() > 0)
337 provider = (Provider) Class.forName(providerClassName).newInstance();
338 // is it already installed?
339 String providerName = provider.getName();
340 Provider installedProvider = Security.getProvider(providerName);
341 if (installedProvider != null)
342 log.finer("Provider " + providerName + " is already installed"); //$NON-NLS-1$ //$NON-NLS-2$
343 else // install it
344 installNewProvider();
347 if (! verbose && certs)
349 log.fine("Option <certs> is set but <verbose> is not. Ignored"); //$NON-NLS-1$
350 certs = false;
353 log.exiting(this.getClass().getName(), "setupCommonParams"); //$NON-NLS-1$
357 * Install the user defined security provider in the underlying JVM.
358 * <p>
359 * Also record this fact so we can remove it when we exit the tool.
361 private void installNewProvider()
363 log.entering(this.getClass().getName(), "installNewProvider"); //$NON-NLS-1$
365 providerInstalled = ProviderUtil.addProvider(provider) != -1;
367 log.exiting(this.getClass().getName(), "installNewProvider"); //$NON-NLS-1$
371 * After processing the command line arguments, this method is invoked to
372 * process the parameters which may have been encountered among the actual
373 * arguments, and which are specific to the signing action of the tool.
375 * @throws KeyStoreException if no implementation of the designated (or
376 * default type) of a key store is availabe.
377 * @throws IOException if an I/O related exception occurs during the process.
378 * @throws NoSuchAlgorithmException if an implementation of an algorithm used
379 * by the key store is not available.
380 * @throws CertificateException if an exception occurs while reading a
381 * certificate from the key store.
382 * @throws UnsupportedCallbackException if no implementation of a password
383 * callback is available.
384 * @throws UnrecoverableKeyException if the wrong password was used to unlock
385 * the key store.
386 * @throws SecurityException if the designated alias is not known to the key
387 * store or is not an Alias of a Key Entry.
389 private void setupSigningParams() throws KeyStoreException, IOException,
390 NoSuchAlgorithmException, CertificateException,
391 UnsupportedCallbackException, UnrecoverableKeyException
393 log.entering(this.getClass().getName(), "setupSigningParams"); //$NON-NLS-1$
395 if (ksURL == null || ksURL.trim().length() == 0)
397 String userHome = SystemProperties.getProperty("user.home"); //$NON-NLS-1$
398 if (userHome == null || userHome.trim().length() == 0)
399 throw new SecurityException(Messages.getString("Main.85")); //$NON-NLS-1$
401 ksURL = "file:" + userHome.trim() + "/.keystore"; //$NON-NLS-1$ //$NON-NLS-2$
403 else
405 ksURL = ksURL.trim();
406 if (ksURL.indexOf(":") == -1) //$NON-NLS-1$
407 ksURL = "file:" + ksURL; //$NON-NLS-1$
410 if (ksType == null || ksType.trim().length() == 0)
411 ksType = KeyStore.getDefaultType();
412 else
413 ksType = ksType.trim();
415 store = KeyStore.getInstance(ksType);
417 if (ksPassword == null)
419 // ask the user to provide one
420 PasswordCallback pcb = new PasswordCallback(Messages.getString("Main.92"), //$NON-NLS-1$
421 false);
422 getCallbackHandler().handle(new Callback[] { pcb });
423 ksPasswordChars = pcb.getPassword();
425 else
426 ksPasswordChars = ksPassword.toCharArray();
428 URL url = new URL(ksURL);
429 InputStream stream = url.openStream();
430 store.load(stream, ksPasswordChars);
432 if (alias == null)
433 HelpPrinter.printHelpAndExit(HELP_PATH);
435 if (! store.containsAlias(alias))
436 throw new SecurityException(Messages.getFormattedString("Main.6", alias)); //$NON-NLS-1$
438 if (! store.isKeyEntry(alias))
439 throw new SecurityException(Messages.getFormattedString("Main.95", alias)); //$NON-NLS-1$
441 Key key;
442 if (password == null)
444 passwordChars = ksPasswordChars;
447 key = store.getKey(alias, passwordChars);
449 catch (UnrecoverableKeyException x)
451 // ask the user to provide one
452 String prompt = Messages.getFormattedString("Main.97", alias); //$NON-NLS-1$
453 PasswordCallback pcb = new PasswordCallback(prompt, false);
454 getCallbackHandler().handle(new Callback[] { pcb });
455 passwordChars = pcb.getPassword();
456 // take 2
457 key = store.getKey(alias, passwordChars);
460 else
462 passwordChars = password.toCharArray();
463 key = store.getKey(alias, passwordChars);
466 if (! (key instanceof PrivateKey))
467 throw new SecurityException(Messages.getFormattedString("Main.99", alias)); //$NON-NLS-1$
469 signerPrivateKey = (PrivateKey) key;
470 signerCertificateChain = store.getCertificateChain(alias);
471 log.finest(String.valueOf(signerCertificateChain));
473 if (sigFileName == null)
474 sigFileName = alias;
476 sigFileName = sigFileName.toUpperCase(EN_US_LOCALE);
477 if (sigFileName.length() > 8)
478 sigFileName = sigFileName.substring(0, 8);
480 char[] chars = sigFileName.toCharArray();
481 for (int i = 0; i < chars.length; i++)
483 char c = chars[i];
484 if (! (Character.isLetter(c)
485 || Character.isDigit(c)
486 || c == '_'
487 || c == '-'))
488 chars[i] = '_';
491 sigFileName = new String(chars);
493 if (signedJarFileName == null)
494 signedJarFileName = jarFileName;
496 log.exiting(this.getClass().getName(), "setupSigningParams"); //$NON-NLS-1$
499 boolean isVerbose()
501 return verbose;
504 boolean isCerts()
506 return certs;
509 String getSigFileName()
511 return this.sigFileName;
514 String getJarFileName()
516 return this.jarFileName;
519 boolean isSectionsOnly()
521 return this.sectionsOnly;
524 boolean isInternalSF()
526 return this.internalSF;
529 PrivateKey getSignerPrivateKey()
531 return this.signerPrivateKey;
534 Certificate[] getSignerCertificateChain()
536 return signerCertificateChain;
539 String getSignedJarFileName()
541 return this.signedJarFileName;
545 * Return a CallbackHandler which uses the Console (System.in and System.out)
546 * for interacting with the user.
547 * <p>
548 * This method first finds all currently installed security providers capable
549 * of providing such service and then in turn attempts to instantiate the
550 * handler from those providers. As soon as one provider returns a non-null
551 * instance of the callback handler, the search stops and that instance is
552 * set to be used from now on.
553 * <p>
554 * If no installed providers were found, this method falls back on the GNU
555 * provider, by-passing the Security search mechanism. The default console
556 * callback handler implementation is {@link ConsoleCallbackHandler}.
558 * @return a console-based {@link CallbackHandler}.
560 protected CallbackHandler getCallbackHandler()
562 if (handler == null)
563 handler = CallbackUtil.getConsoleHandler();
565 return handler;