1 /*
2 * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package sun.security.tools;
27
28 import java.io.*;
29 import java.util.*;
30 import java.util.zip.*;
31 import java.util.jar.*;
32 import java.math.BigInteger;
33 import java.net.URI;
34 import java.net.URISyntaxException;
35 import java.net.URL;
36 import java.net.URLClassLoader;
37 import java.net.SocketTimeoutException;
38 import java.text.Collator;
39 import java.text.MessageFormat;
40 import java.security.cert.Certificate;
41 import java.security.cert.X509Certificate;
42 import java.security.cert.CertificateException;
43 import java.security.cert.CertificateExpiredException;
44 import java.security.cert.CertificateNotYetValidException;
45 import java.security.*;
46 import java.lang.reflect.Constructor;
47
48 import com.sun.jarsigner.ContentSigner;
49 import com.sun.jarsigner.ContentSignerParameters;
50 import sun.security.x509.*;
51 import sun.security.util.*;
52 import sun.misc.BASE64Encoder;
53
54 /**
55 * <p>The jarsigner utility.
56 *
57 * @author Roland Schemers
58 * @author Jan Luehe
59 */
60
61 public class JarSigner {
62
63 // for i18n
64 private static final java.util.ResourceBundle rb =
65 java.util.ResourceBundle.getBundle
66 ("sun.security.tools.JarSignerResources");
67 private static final Collator collator = Collator.getInstance();
68 static {
69 // this is for case insensitive string comparisions
70 collator.setStrength(Collator.PRIMARY);
71 }
72
73 private static final String META_INF = "META-INF/";
74
75 private static final Class[] PARAM_STRING = { String.class };
76
77 private static final String NONE = "NONE";
78 private static final String P11KEYSTORE = "PKCS11";
79
80 private static final long SIX_MONTHS = 180*24*60*60*1000L; //milliseconds
81
82 // Attention:
83 // This is the entry that get launched by the security tool jarsigner.
84 // It's marked as exported private per AppServer Team's request.
85 // See http://ccc.sfbay/6428446
86 public static void main(String args[]) throws Exception {
87 JarSigner js = new JarSigner();
88 js.run(args);
89 }
90
91 static final String VERSION = "1.0";
92
93 static final int IN_KEYSTORE = 0x01;
94 static final int IN_SCOPE = 0x02;
95
96 // signer's certificate chain (when composing)
97 X509Certificate[] certChain;
98
99 /*
100 * private key
101 */
102 PrivateKey privateKey;
103 KeyStore store;
104
105 IdentityScope scope;
106
107 String keystore; // key store file
108 boolean nullStream = false; // null keystore input stream (NONE)
109 boolean token = false; // token-based keystore
110 String jarfile; // jar file to sign
111 String alias; // alias to sign jar with
112 char[] storepass; // keystore password
113 boolean protectedPath; // protected authentication path
114 String storetype; // keystore type
115 String providerName; // provider name
116 Vector<String> providers = null; // list of providers
117 HashMap<String,String> providerArgs = new HashMap<String, String>(); // arguments for provider constructors
118 char[] keypass; // private key password
119 String sigfile; // name of .SF file
120 String sigalg; // name of signature algorithm
121 String digestalg = "SHA1"; // name of digest algorithm
122 String signedjar; // output filename
123 String tsaUrl; // location of the Timestamping Authority
124 String tsaAlias; // alias for the Timestamping Authority's certificate
125 boolean verify = false; // verify the jar
126 boolean verbose = false; // verbose output when signing/verifying
127 boolean showcerts = false; // show certs when verifying
128 boolean debug = false; // debug
129 boolean signManifest = true; // "sign" the whole manifest
130 boolean externalSF = true; // leave the .SF out of the PKCS7 block
131
132 // read zip entry raw bytes
133 private ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
134 private byte[] buffer = new byte[8192];
135 private ContentSigner signingMechanism = null;
136 private String altSignerClass = null;
137 private String altSignerClasspath = null;
138 private ZipFile zipFile = null;
139 private boolean hasExpiredCert = false;
140 private boolean hasExpiringCert = false;
141 private boolean notYetValidCert = false;
142
143 private boolean badKeyUsage = false;
144 private boolean badExtendedKeyUsage = false;
145 private boolean badNetscapeCertType = false;
146
147 public void run(String args[]) {
148 try {
149 parseArgs(args);
150
151 // Try to load and install the specified providers
152 if (providers != null) {
153 ClassLoader cl = ClassLoader.getSystemClassLoader();
154 Enumeration<String> e = providers.elements();
155 while (e.hasMoreElements()) {
156 String provName = e.nextElement();
157 Class<?> provClass;
158 if (cl != null) {
159 provClass = cl.loadClass(provName);
160 } else {
161 provClass = Class.forName(provName);
162 }
163
164 String provArg = providerArgs.get(provName);
165 Object obj;
166 if (provArg == null) {
167 obj = provClass.newInstance();
168 } else {
169 Constructor<?> c =
170 provClass.getConstructor(PARAM_STRING);
171 obj = c.newInstance(provArg);
172 }
173
174 if (!(obj instanceof Provider)) {
175 MessageFormat form = new MessageFormat(rb.getString
176 ("provName not a provider"));
177 Object[] source = {provName};
178 throw new Exception(form.format(source));
179 }
180 Security.addProvider((Provider)obj);
181 }
182 }
183
184 hasExpiredCert = false;
185 hasExpiringCert = false;
186 notYetValidCert = false;
187
188 badKeyUsage = false;
189 badExtendedKeyUsage = false;
190 badNetscapeCertType = false;
191
192 if (verify) {
193 try {
194 loadKeyStore(keystore, false);
195 scope = IdentityScope.getSystemScope();
196 } catch (Exception e) {
197 if ((keystore != null) || (storepass != null)) {
198 System.out.println(rb.getString("jarsigner error: ") +
199 e.getMessage());
200 System.exit(1);
201 }
202 }
203 /* if (debug) {
204 SignatureFileVerifier.setDebug(true);
205 ManifestEntryVerifier.setDebug(true);
206 }
207 */
208 verifyJar(jarfile);
209 } else {
210 loadKeyStore(keystore, true);
211 getAliasInfo(alias);
212
213 // load the alternative signing mechanism
214 if (altSignerClass != null) {
215 signingMechanism = loadSigningMechanism(altSignerClass,
216 altSignerClasspath);
217 }
218 signJar(jarfile, alias, args);
219 }
220 } catch (Exception e) {
221 System.out.println(rb.getString("jarsigner error: ") + e);
222 if (debug) {
223 e.printStackTrace();
224 }
225 System.exit(1);
226 } finally {
227 // zero-out private key password
228 if (keypass != null) {
229 Arrays.fill(keypass, ' ');
230 keypass = null;
231 }
232 // zero-out keystore password
233 if (storepass != null) {
234 Arrays.fill(storepass, ' ');
235 storepass = null;
236 }
237 }
238 }
239
240 /*
241 * Parse command line arguments.
242 */
243 void parseArgs(String args[]) {
244 /* parse flags */
245 int n = 0;
246
247 for (n=0; (n < args.length) && args[n].startsWith("-"); n++) {
248
249 String flags = args[n];
250
251 if (collator.compare(flags, "-keystore") == 0) {
252 if (++n == args.length) usage();
253 keystore = args[n];
254 } else if (collator.compare(flags, "-storepass") ==0) {
255 if (++n == args.length) usage();
256 storepass = args[n].toCharArray();
257 } else if (collator.compare(flags, "-storetype") ==0) {
258 if (++n == args.length) usage();
259 storetype = args[n];
260 } else if (collator.compare(flags, "-providerName") ==0) {
261 if (++n == args.length) usage();
262 providerName = args[n];
263 } else if ((collator.compare(flags, "-provider") == 0) ||
264 (collator.compare(flags, "-providerClass") == 0)) {
265 if (++n == args.length) usage();
266 if (providers == null) {
267 providers = new Vector<String>(3);
268 }
269 providers.add(args[n]);
270
271 if (args.length > (n+1)) {
272 flags = args[n+1];
273 if (collator.compare(flags, "-providerArg") == 0) {
274 if (args.length == (n+2)) usage();
275 providerArgs.put(args[n], args[n+2]);
276 n += 2;
277 }
278 }
279 } else if (collator.compare(flags, "-protected") ==0) {
280 protectedPath = true;
281 } else if (collator.compare(flags, "-debug") ==0) {
282 debug = true;
283 } else if (collator.compare(flags, "-keypass") ==0) {
284 if (++n == args.length) usage();
285 keypass = args[n].toCharArray();
286 } else if (collator.compare(flags, "-sigfile") ==0) {
287 if (++n == args.length) usage();
288 sigfile = args[n];
289 } else if (collator.compare(flags, "-signedjar") ==0) {
290 if (++n == args.length) usage();
291 signedjar = args[n];
292 } else if (collator.compare(flags, "-tsa") ==0) {
293 if (++n == args.length) usage();
294 tsaUrl = args[n];
295 } else if (collator.compare(flags, "-tsacert") ==0) {
296 if (++n == args.length) usage();
297 tsaAlias = args[n];
298 } else if (collator.compare(flags, "-altsigner") ==0) {
299 if (++n == args.length) usage();
300 altSignerClass = args[n];
301 } else if (collator.compare(flags, "-altsignerpath") ==0) {
302 if (++n == args.length) usage();
303 altSignerClasspath = args[n];
304 } else if (collator.compare(flags, "-sectionsonly") ==0) {
305 signManifest = false;
306 } else if (collator.compare(flags, "-internalsf") ==0) {
307 externalSF = false;
308 } else if (collator.compare(flags, "-verify") ==0) {
309 verify = true;
310 } else if (collator.compare(flags, "-verbose") ==0) {
311 verbose = true;
312 } else if (collator.compare(flags, "-sigalg") ==0) {
313 if (++n == args.length) usage();
314 sigalg = args[n];
315 } else if (collator.compare(flags, "-digestalg") ==0) {
316 if (++n == args.length) usage();
317 digestalg = args[n];
318 } else if (collator.compare(flags, "-certs") ==0) {
319 showcerts = true;
320 } else if (collator.compare(flags, "-h") == 0 ||
321 collator.compare(flags, "-help") == 0) {
322 usage();
323 } else {
324 System.err.println(rb.getString("Illegal option: ") + flags);
325 usage();
326 }
327 }
328
329 if (n == args.length) usage();
330 jarfile = args[n++];
331
332 if (!verify) {
333 if (n == args.length) usage();
334 alias = args[n++];
335 }
336
337 if (storetype == null) {
338 storetype = KeyStore.getDefaultType();
339 }
340 storetype = KeyStoreUtil.niceStoreTypeName(storetype);
341
342 if (P11KEYSTORE.equalsIgnoreCase(storetype) ||
343 KeyStoreUtil.isWindowsKeyStore(storetype)) {
344 token = true;
345 if (keystore == null) {
346 keystore = NONE;
347 }
348 }
349
350 if (NONE.equals(keystore)) {
351 nullStream = true;
352 }
353
354 if (token && !nullStream) {
355 System.err.println(MessageFormat.format(rb.getString
356 ("-keystore must be NONE if -storetype is {0}"), storetype));
357 System.err.println();
358 usage();
359 }
360
361 if (token && keypass != null) {
362 System.err.println(MessageFormat.format(rb.getString
363 ("-keypass can not be specified " +
364 "if -storetype is {0}"), storetype));
365 System.err.println();
366 usage();
367 }
368
369 if (protectedPath) {
370 if (storepass != null || keypass != null) {
371 System.err.println(rb.getString
372 ("If -protected is specified, " +
373 "then -storepass and -keypass must not be specified"));
374 System.err.println();
375 usage();
376 }
377 }
378 if (KeyStoreUtil.isWindowsKeyStore(storetype)) {
379 if (storepass != null || keypass != null) {
380 System.err.println(rb.getString
381 ("If keystore is not password protected, " +
382 "then -storepass and -keypass must not be specified"));
383 System.err.println();
384 usage();
385 }
386 }
387 }
388
389 void usage() {
390 System.out.println(rb.getString
391 ("Usage: jarsigner [options] jar-file alias"));
392 System.out.println(rb.getString
393 (" jarsigner -verify [options] jar-file"));
394 System.out.println();
395 System.out.println(rb.getString
396 ("[-keystore <url>] keystore location"));
397 System.out.println();
398 System.out.println(rb.getString
399 ("[-storepass <password>] password for keystore integrity"));
400 System.out.println();
401 System.out.println(rb.getString
402 ("[-storetype <type>] keystore type"));
403 System.out.println();
404 System.out.println(rb.getString
405 ("[-keypass <password>] password for private key (if different)"));
406 System.out.println();
407 System.out.println(rb.getString
408 ("[-sigfile <file>] name of .SF/.DSA file"));
409 System.out.println();
410 System.out.println(rb.getString
411 ("[-signedjar <file>] name of signed JAR file"));
412 System.out.println();
413 System.out.println(rb.getString
414 ("[-digestalg <algorithm>] name of digest algorithm"));
415 System.out.println();
416 System.out.println(rb.getString
417 ("[-sigalg <algorithm>] name of signature algorithm"));
418 System.out.println();
419 System.out.println(rb.getString
420 ("[-verify] verify a signed JAR file"));
421 System.out.println();
422 System.out.println(rb.getString
423 ("[-verbose] verbose output when signing/verifying"));
424 System.out.println();
425 System.out.println(rb.getString
426 ("[-certs] display certificates when verbose and verifying"));
427 System.out.println();
428 System.out.println(rb.getString
429 ("[-tsa <url>] location of the Timestamping Authority"));
430 System.out.println();
431 System.out.println(rb.getString
432 ("[-tsacert <alias>] public key certificate for Timestamping Authority"));
433 System.out.println();
434 System.out.println(rb.getString
435 ("[-altsigner <class>] class name of an alternative signing mechanism"));
436 System.out.println();
437 System.out.println(rb.getString
438 ("[-altsignerpath <pathlist>] location of an alternative signing mechanism"));
439 System.out.println();
440 System.out.println(rb.getString
441 ("[-internalsf] include the .SF file inside the signature block"));
442 System.out.println();
443 System.out.println(rb.getString
444 ("[-sectionsonly] don't compute hash of entire manifest"));
445 System.out.println();
446 System.out.println(rb.getString
447 ("[-protected] keystore has protected authentication path"));
448 System.out.println();
449 System.out.println(rb.getString
450 ("[-providerName <name>] provider name"));
451 System.out.println();
452 System.out.println(rb.getString
453 ("[-providerClass <class> name of cryptographic service provider's"));
454 System.out.println(rb.getString
455 (" [-providerArg <arg>]] ... master class file and constructor argument"));
456 System.out.println();
457
458 System.exit(1);
459 }
460
461 void verifyJar(String jarName)
462 throws Exception
463 {
464 boolean anySigned = false;
465 boolean hasUnsignedEntry = false;
466 JarFile jf = null;
467
468 try {
469 jf = new JarFile(jarName, true);
470 Vector<JarEntry> entriesVec = new Vector<JarEntry>();
471 byte[] buffer = new byte[8192];
472
473 Enumeration<JarEntry> entries = jf.entries();
474 while (entries.hasMoreElements()) {
475 JarEntry je = entries.nextElement();
476 entriesVec.addElement(je);
477 InputStream is = null;
478 try {
479 is = jf.getInputStream(je);
480 int n;
481 while ((n = is.read(buffer, 0, buffer.length)) != -1) {
482 // we just read. this will throw a SecurityException
483 // if a signature/digest check fails.
484 }
485 } finally {
486 if (is != null) {
487 is.close();
488 }
489 }
490 }
491
492 Manifest man = jf.getManifest();
493
494 if (man != null) {
495 if (verbose) System.out.println();
496 Enumeration<JarEntry> e = entriesVec.elements();
497
498 long now = System.currentTimeMillis();
499
500 while (e.hasMoreElements()) {
501 JarEntry je = e.nextElement();
502 String name = je.getName();
503 CodeSigner[] signers = je.getCodeSigners();
504 boolean isSigned = (signers != null);
505 anySigned |= isSigned;
506 hasUnsignedEntry |= !je.isDirectory() && !isSigned
507 && !signatureRelated(name);
508
509 if (verbose) {
510 int inStoreOrScope = inKeyStore(signers);
511 boolean inStore = (inStoreOrScope & IN_KEYSTORE) != 0;
512 boolean inScope = (inStoreOrScope & IN_SCOPE) != 0;
513 boolean inManifest =
514 ((man.getAttributes(name) != null) ||
515 (man.getAttributes("./"+name) != null) ||
516 (man.getAttributes("/"+name) != null));
517 System.out.print(
518 (isSigned ? rb.getString("s") : rb.getString(" ")) +
519 (inManifest ? rb.getString("m") : rb.getString(" ")) +
520 (inStore ? rb.getString("k") : rb.getString(" ")) +
521 (inScope ? rb.getString("i") : rb.getString(" ")) +
522 rb.getString(" "));
523 StringBuffer sb = new StringBuffer();
524 String s = Long.toString(je.getSize());
525 for (int i = 6 - s.length(); i > 0; --i) {
526 sb.append(' ');
527 }
528 sb.append(s).append(' ').
529 append(new Date(je.getTime()).toString());
530 sb.append(' ').append(je.getName());
531 System.out.println(sb.toString());
532
533 if (signers != null && showcerts) {
534 String tab = rb.getString(" ");
535 for (int i = 0; i < signers.length; i++) {
536 System.out.println();
537 List<? extends Certificate> certs =
538 signers[i].getSignerCertPath()
539 .getCertificates();
540 // display the signature timestamp, if present
541 Timestamp timestamp = signers[i].getTimestamp();
542 if (timestamp != null) {
543 System.out.println(
544 printTimestamp(tab, timestamp));
545 }
546 // display the certificate(s)
547 for (Certificate c : certs) {
548 System.out.println(
549 printCert(tab, c, true, now));
550 }
551 }
552 System.out.println();
553 }
554
555 }
556 if (isSigned) {
557 for (int i = 0; i < signers.length; i++) {
558 Certificate cert =
559 signers[i].getSignerCertPath()
560 .getCertificates().get(0);
561 if (cert instanceof X509Certificate) {
562 checkCertUsage((X509Certificate)cert, null);
563 if (!showcerts) {
564 long notAfter = ((X509Certificate)cert)
565 .getNotAfter().getTime();
566
567 if (notAfter < now) {
568 hasExpiredCert = true;
569 } else if (notAfter < now + SIX_MONTHS) {
570 hasExpiringCert = true;
571 }
572 }
573 }
574 }
575 }
576
577 }
578 }
579 if (verbose) {
580 System.out.println();
581 System.out.println(rb.getString(
582 " s = signature was verified "));
583 System.out.println(rb.getString(
584 " m = entry is listed in manifest"));
585 System.out.println(rb.getString(
586 " k = at least one certificate was found in keystore"));
587 System.out.println(rb.getString(
588 " i = at least one certificate was found in identity scope"));
589 System.out.println();
590 }
591
592 if (man == null)
593 System.out.println(rb.getString("no manifest."));
594
595 if (!anySigned) {
596 System.out.println(rb.getString(
597 "jar is unsigned. (signatures missing or not parsable)"));
598 } else {
599 System.out.println(rb.getString("jar verified."));
600 if (hasUnsignedEntry || hasExpiredCert || hasExpiringCert ||
601 badKeyUsage || badExtendedKeyUsage || badNetscapeCertType ||
602 notYetValidCert) {
603
604 System.out.println();
605 System.out.println(rb.getString("Warning: "));
606 if (badKeyUsage) {
607 System.out.println(
608 rb.getString("This jar contains entries whose signer certificate's KeyUsage extension doesn't allow code signing."));
609 }
610
611 if (badExtendedKeyUsage) {
612 System.out.println(
613 rb.getString("This jar contains entries whose signer certificate's ExtendedKeyUsage extension doesn't allow code signing."));
614 }
615
616 if (badNetscapeCertType) {
617 System.out.println(
618 rb.getString("This jar contains entries whose signer certificate's NetscapeCertType extension doesn't allow code signing."));
619 }
620
621 if (hasUnsignedEntry) {
622 System.out.println(rb.getString(
623 "This jar contains unsigned entries which have not been integrity-checked. "));
624 }
625 if (hasExpiredCert) {
626 System.out.println(rb.getString(
627 "This jar contains entries whose signer certificate has expired. "));
628 }
629 if (hasExpiringCert) {
630 System.out.println(rb.getString(
631 "This jar contains entries whose signer certificate will expire within six months. "));
632 }
633 if (notYetValidCert) {
634 System.out.println(rb.getString(
635 "This jar contains entries whose signer certificate is not yet valid. "));
636 }
637
638 if (! (verbose && showcerts)) {
639 System.out.println();
640 System.out.println(rb.getString(
641 "Re-run with the -verbose and -certs options for more details."));
642 }
643 }
644 }
645 System.exit(0);
646 } catch (Exception e) {
647 System.out.println(rb.getString("jarsigner: ") + e);
648 if (debug) {
649 e.printStackTrace();
650 }
651 } finally { // close the resource
652 if (jf != null) {
653 jf.close();
654 }
655 }
656
657 System.exit(1);
658 }
659
660 /*
661 * Display some details about a certificate:
662 *
663 * <cert-type> [", " <subject-DN>] [" (" <keystore-entry-alias> ")"]
664 */
665 String printCert(Certificate c) {
666 return printCert("", c, false, 0);
667 }
668
669 private static MessageFormat validityTimeForm = null;
670 private static MessageFormat notYetTimeForm = null;
671 private static MessageFormat expiredTimeForm = null;
672 private static MessageFormat expiringTimeForm = null;
673
674 /*
675 * Display some details about a certificate:
676 *
677 * [<tab>] <cert-type> [", " <subject-DN>] [" (" <keystore-entry-alias> ")"]
678 * [<validity-period> | <expiry-warning>]
679 */
680 String printCert(String tab, Certificate c, boolean checkValidityPeriod,
681 long now) {
682
683 StringBuilder certStr = new StringBuilder();
684 String space = rb.getString(" ");
685 X509Certificate x509Cert = null;
686
687 if (c instanceof X509Certificate) {
688 x509Cert = (X509Certificate) c;
689 certStr.append(tab).append(x509Cert.getType())
690 .append(rb.getString(", "))
691 .append(x509Cert.getSubjectDN().getName());
692 } else {
693 certStr.append(tab).append(c.getType());
694 }
695
696 String alias = storeHash.get(c);
697 if (alias != null) {
698 certStr.append(space).append(alias);
699 }
700
701 if (checkValidityPeriod && x509Cert != null) {
702
703 certStr.append("\n").append(tab).append("[");
704 Date notAfter = x509Cert.getNotAfter();
705 try {
706 x509Cert.checkValidity();
707 // test if cert will expire within six months
708 if (now == 0) {
709 now = System.currentTimeMillis();
710 }
711 if (notAfter.getTime() < now + SIX_MONTHS) {
712 hasExpiringCert = true;
713
714 if (expiringTimeForm == null) {
715 expiringTimeForm = new MessageFormat(
716 rb.getString("certificate will expire on"));
717 }
718 Object[] source = { notAfter };
719 certStr.append(expiringTimeForm.format(source));
720
721 } else {
722 if (validityTimeForm == null) {
723 validityTimeForm = new MessageFormat(
724 rb.getString("certificate is valid from"));
725 }
726 Object[] source = { x509Cert.getNotBefore(), notAfter };
727 certStr.append(validityTimeForm.format(source));
728 }
729 } catch (CertificateExpiredException cee) {
730 hasExpiredCert = true;
731
732 if (expiredTimeForm == null) {
733 expiredTimeForm = new MessageFormat(
734 rb.getString("certificate expired on"));
735 }
736 Object[] source = { notAfter };
737 certStr.append(expiredTimeForm.format(source));
738
739 } catch (CertificateNotYetValidException cnyve) {
740 notYetValidCert = true;
741
742 if (notYetTimeForm == null) {
743 notYetTimeForm = new MessageFormat(
744 rb.getString("certificate is not valid until"));
745 }
746 Object[] source = { x509Cert.getNotBefore() };
747 certStr.append(notYetTimeForm.format(source));
748 }
749 certStr.append("]");
750
751 boolean[] bad = new boolean[3];
752 checkCertUsage(x509Cert, bad);
753 if (bad[0] || bad[1] || bad[2]) {
754 String x = "";
755 if (bad[0]) {
756 x ="KeyUsage";
757 }
758 if (bad[1]) {
759 if (x.length() > 0) x = x + ", ";
760 x = x + "ExtendedKeyUsage";
761 }
762 if (bad[2]) {
763 if (x.length() > 0) x = x + ", ";
764 x = x + "NetscapeCertType";
765 }
766 certStr.append("\n").append(tab)
767 .append(MessageFormat.format(rb.getString(
768 "[{0} extension does not support code signing]"), x));
769 }
770 }
771 return certStr.toString();
772 }
773
774 private static MessageFormat signTimeForm = null;
775
776 private String printTimestamp(String tab, Timestamp timestamp) {
777
778 if (signTimeForm == null) {
779 signTimeForm =
780 new MessageFormat(rb.getString("entry was signed on"));
781 }
782 Object[] source = { timestamp.getTimestamp() };
783
784 return new StringBuilder().append(tab).append("[")
785 .append(signTimeForm.format(source)).append("]").toString();
786 }
787
788 Hashtable<Certificate, String> storeHash =
789 new Hashtable<Certificate, String>();
790
791 int inKeyStore(CodeSigner[] signers) {
792 int result = 0;
793
794 if (signers == null)
795 return 0;
796
797 boolean found = false;
798
799 for (int i = 0; i < signers.length; i++) {
800 found = false;
801 List<? extends Certificate> certs =
802 signers[i].getSignerCertPath().getCertificates();
803
804 for (Certificate c : certs) {
805 String alias = storeHash.get(c);
806
807 if (alias != null) {
808 if (alias.startsWith("("))
809 result |= IN_KEYSTORE;
810 else if (alias.startsWith("["))
811 result |= IN_SCOPE;
812 } else {
813 if (store != null) {
814 try {
815 alias = store.getCertificateAlias(c);
816 } catch (KeyStoreException kse) {
817 // never happens, because keystore has been loaded
818 }
819 if (alias != null) {
820 storeHash.put(c, "("+alias+")");
821 found = true;
822 result |= IN_KEYSTORE;
823 }
824 }
825 if (!found && (scope != null)) {
826 Identity id = scope.getIdentity(c.getPublicKey());
827 if (id != null) {
828 result |= IN_SCOPE;
829 storeHash.put(c, "["+id.getName()+"]");
830 }
831 }
832 }
833 }
834 }
835 return result;
836 }
837
838 void signJar(String jarName, String alias, String[] args)
839 throws Exception {
840 boolean aliasUsed = false;
841 X509Certificate tsaCert = null;
842
843 if (sigfile == null) {
844 sigfile = alias;
845 aliasUsed = true;
846 }
847
848 if (sigfile.length() > 8) {
849 sigfile = sigfile.substring(0, 8).toUpperCase();
850 } else {
851 sigfile = sigfile.toUpperCase();
852 }
853
854 StringBuilder tmpSigFile = new StringBuilder(sigfile.length());
855 for (int j = 0; j < sigfile.length(); j++) {
856 char c = sigfile.charAt(j);
857 if (!
858 ((c>= 'A' && c<= 'Z') ||
859 (c>= '0' && c<= '9') ||
860 (c == '-') ||
861 (c == '_'))) {
862 if (aliasUsed) {
863 // convert illegal characters from the alias to be _'s
864 c = '_';
865 } else {
866 throw new
867 RuntimeException(rb.getString
868 ("signature filename must consist of the following characters: A-Z, 0-9, _ or -"));
869 }
870 }
871 tmpSigFile.append(c);
872 }
873
874 sigfile = tmpSigFile.toString();
875
876 String tmpJarName;
877 if (signedjar == null) tmpJarName = jarName+".sig";
878 else tmpJarName = signedjar;
879
880 File jarFile = new File(jarName);
881 File signedJarFile = new File(tmpJarName);
882
883 // Open the jar (zip) file
884 try {
885 zipFile = new ZipFile(jarName);
886 } catch (IOException ioe) {
887 error(rb.getString("unable to open jar file: ")+jarName, ioe);
888 }
889
890 FileOutputStream fos = null;
891 try {
892 fos = new FileOutputStream(signedJarFile);
893 } catch (IOException ioe) {
894 error(rb.getString("unable to create: ")+tmpJarName, ioe);
895 }
896
897 PrintStream ps = new PrintStream(fos);
898 ZipOutputStream zos = new ZipOutputStream(ps);
899
900 /* First guess at what they might be - we don't xclude RSA ones. */
901 String sfFilename = (META_INF + sigfile + ".SF").toUpperCase();
902 String bkFilename = (META_INF + sigfile + ".DSA").toUpperCase();
903
904 Manifest manifest = new Manifest();
905 Map<String,Attributes> mfEntries = manifest.getEntries();
906
907 // The Attributes of manifest before updating
908 Attributes oldAttr = null;
909
910 boolean mfModified = false;
911 boolean mfCreated = false;
912 byte[] mfRawBytes = null;
913
914 try {
915 MessageDigest digests[] = { MessageDigest.getInstance(digestalg) };
916
917 // Check if manifest exists
918 ZipEntry mfFile;
919 if ((mfFile = getManifestFile(zipFile)) != null) {
920 // Manifest exists. Read its raw bytes.
921 mfRawBytes = getBytes(zipFile, mfFile);
922 manifest.read(new ByteArrayInputStream(mfRawBytes));
923 oldAttr = (Attributes)(manifest.getMainAttributes().clone());
924 } else {
925 // Create new manifest
926 Attributes mattr = manifest.getMainAttributes();
927 mattr.putValue(Attributes.Name.MANIFEST_VERSION.toString(),
928 "1.0");
929 String javaVendor = System.getProperty("java.vendor");
930 String jdkVersion = System.getProperty("java.version");
931 mattr.putValue("Created-By", jdkVersion + " (" +javaVendor
932 + ")");
933 mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
934 mfCreated = true;
935 }
936
937 /*
938 * For each entry in jar
939 * (except for signature-related META-INF entries),
940 * do the following:
941 *
942 * - if entry is not contained in manifest, add it to manifest;
943 * - if entry is contained in manifest, calculate its hash and
944 * compare it with the one in the manifest; if they are
945 * different, replace the hash in the manifest with the newly
946 * generated one. (This may invalidate existing signatures!)
947 */
948 BASE64Encoder encoder = new JarBASE64Encoder();
949 Vector<ZipEntry> mfFiles = new Vector<ZipEntry>();
950
951 boolean wasSigned = false;
952
953 for (Enumeration<? extends ZipEntry> enum_=zipFile.entries();
954 enum_.hasMoreElements();) {
955 ZipEntry ze = enum_.nextElement();
956
957 if (ze.getName().startsWith(META_INF)) {
958 // Store META-INF files in vector, so they can be written
959 // out first
960 mfFiles.addElement(ze);
961
962 if (SignatureFileVerifier.isBlockOrSF(
963 ze.getName().toUpperCase(Locale.ENGLISH))) {
964 wasSigned = true;
965 }
966
967 if (signatureRelated(ze.getName())) {
968 // ignore signature-related and manifest files
969 continue;
970 }
971 }
972
973 if (manifest.getAttributes(ze.getName()) != null) {
974 // jar entry is contained in manifest, check and
975 // possibly update its digest attributes
976 if (updateDigests(ze, zipFile, digests, encoder,
977 manifest) == true) {
978 mfModified = true;
979 }
980 } else if (!ze.isDirectory()) {
981 // Add entry to manifest
982 Attributes attrs = getDigestAttributes(ze, zipFile,
983 digests,
984 encoder);
985 mfEntries.put(ze.getName(), attrs);
986 mfModified = true;
987 }
988 }
989
990 // Recalculate the manifest raw bytes if necessary
991 if (mfModified) {
992 ByteArrayOutputStream baos = new ByteArrayOutputStream();
993 manifest.write(baos);
994 if (wasSigned) {
995 byte[] newBytes = baos.toByteArray();
996 if (mfRawBytes != null
997 && oldAttr.equals(manifest.getMainAttributes())) {
998
999 /*
1000 * Note:
1001 *
1002 * The Attributes object is based on HashMap and can handle
1003 * continuation columns. Therefore, even if the contents are
1004 * not changed (in a Map view), the bytes that it write()
1005 * may be different from the original bytes that it read()
1006 * from. Since the signature on the main attributes is based
1007 * on raw bytes, we must retain the exact bytes.
1008 */
1009
1010 int newPos = findHeaderEnd(newBytes);
1011 int oldPos = findHeaderEnd(mfRawBytes);
1012
1013 if (newPos == oldPos) {
1014 System.arraycopy(mfRawBytes, 0, newBytes, 0, oldPos);
1015 } else {
1016 // cat oldHead newTail > newBytes
1017 byte[] lastBytes = new byte[oldPos +
1018 newBytes.length - newPos];
1019 System.arraycopy(mfRawBytes, 0, lastBytes, 0, oldPos);
1020 System.arraycopy(newBytes, newPos, lastBytes, oldPos,
1021 newBytes.length - newPos);
1022 newBytes = lastBytes;
1023 }
1024 }
1025 mfRawBytes = newBytes;
1026 } else {
1027 mfRawBytes = baos.toByteArray();
1028 }
1029 }
1030
1031 // Write out the manifest
1032 if (mfModified) {
1033 // manifest file has new length
1034 mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
1035 }
1036 if (verbose) {
1037 if (mfCreated) {
1038 System.out.println(rb.getString(" adding: ") +
1039 mfFile.getName());
1040 } else if (mfModified) {
1041 System.out.println(rb.getString(" updating: ") +
1042 mfFile.getName());
1043 }
1044 }
1045 zos.putNextEntry(mfFile);
1046 zos.write(mfRawBytes);
1047
1048 // Calculate SignatureFile (".SF") and SignatureBlockFile
1049 ManifestDigester manDig = new ManifestDigester(mfRawBytes);
1050 SignatureFile sf = new SignatureFile(digests, manifest, manDig,
1051 sigfile, signManifest);
1052
1053 if (tsaAlias != null) {
1054 tsaCert = getTsaCert(tsaAlias);
1055 }
1056
1057 SignatureFile.Block block = null;
1058
1059 try {
1060 block =
1061 sf.generateBlock(privateKey, sigalg, certChain,
1062 externalSF, tsaUrl, tsaCert, signingMechanism, args,
1063 zipFile);
1064 } catch (SocketTimeoutException e) {
1065 // Provide a helpful message when TSA is beyond a firewall
1066 error(rb.getString("unable to sign jar: ") +
1067 rb.getString("no response from the Timestamping Authority. ") +
1068 rb.getString("When connecting from behind a firewall then an HTTP proxy may need to be specified. ") +
1069 rb.getString("Supply the following options to jarsigner: ") +
1070 "\n -J-Dhttp.proxyHost=<hostname> " +
1071 "\n -J-Dhttp.proxyPort=<portnumber> ", e);
1072 }
1073
1074 sfFilename = sf.getMetaName();
1075 bkFilename = block.getMetaName();
1076
1077 ZipEntry sfFile = new ZipEntry(sfFilename);
1078 ZipEntry bkFile = new ZipEntry(bkFilename);
1079
1080 long time = System.currentTimeMillis();
1081 sfFile.setTime(time);
1082 bkFile.setTime(time);
1083
1084 // signature file
1085 zos.putNextEntry(sfFile);
1086 sf.write(zos);
1087 if (verbose) {
1088 if (zipFile.getEntry(sfFilename) != null) {
1089 System.out.println(rb.getString(" updating: ") +
1090 sfFilename);
1091 } else {
1092 System.out.println(rb.getString(" adding: ") +
1093 sfFilename);
1094 }
1095 }
1096
1097 if (verbose) {
1098 if (tsaUrl != null || tsaCert != null) {
1099 System.out.println(
1100 rb.getString("requesting a signature timestamp"));
1101 }
1102 if (tsaUrl != null) {
1103 System.out.println(rb.getString("TSA location: ") + tsaUrl);
1104 }
1105 if (tsaCert != null) {
1106 String certUrl =
1107 TimestampedSigner.getTimestampingUrl(tsaCert);
1108 if (certUrl != null) {
1109 System.out.println(rb.getString("TSA location: ") +
1110 certUrl);
1111 }
1112 System.out.println(
1113 rb.getString("TSA certificate: ") + printCert(tsaCert));
1114 }
1115 if (signingMechanism != null) {
1116 System.out.println(
1117 rb.getString("using an alternative signing mechanism"));
1118 }
1119 }
1120
1121 // signature block file
1122 zos.putNextEntry(bkFile);
1123 block.write(zos);
1124 if (verbose) {
1125 if (zipFile.getEntry(bkFilename) != null) {
1126 System.out.println(rb.getString(" updating: ") +
1127 bkFilename);
1128 } else {
1129 System.out.println(rb.getString(" adding: ") +
1130 bkFilename);
1131 }
1132 }
1133
1134 // Write out all other META-INF files that we stored in the
1135 // vector
1136 for (int i=0; i<mfFiles.size(); i++) {
1137 ZipEntry ze = mfFiles.elementAt(i);
1138 if (!ze.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)
1139 && !ze.getName().equalsIgnoreCase(sfFilename)
1140 && !ze.getName().equalsIgnoreCase(bkFilename)) {
1141 writeEntry(zipFile, zos, ze);
1142 }
1143 }
1144
1145 // Write out all other files
1146 for (Enumeration<? extends ZipEntry> enum_=zipFile.entries();
1147 enum_.hasMoreElements();) {
1148 ZipEntry ze = enum_.nextElement();
1149
1150 if (!ze.getName().startsWith(META_INF)) {
1151 if (verbose) {
1152 if (manifest.getAttributes(ze.getName()) != null)
1153 System.out.println(rb.getString(" signing: ") +
1154 ze.getName());
1155 else
1156 System.out.println(rb.getString(" adding: ") +
1157 ze.getName());
1158 }
1159 writeEntry(zipFile, zos, ze);
1160 }
1161 }
1162 } catch(IOException ioe) {
1163 error(rb.getString("unable to sign jar: ")+ioe, ioe);
1164 } finally {
1165 // close the resouces
1166 if (zipFile != null) {
1167 zipFile.close();
1168 zipFile = null;
1169 }
1170
1171 if (zos != null) {
1172 zos.close();
1173 }
1174 }
1175
1176 // no IOException thrown in the follow try clause, so disable
1177 // the try clause.
1178 // try {
1179 if (signedjar == null) {
1180 // attempt an atomic rename. If that fails,
1181 // rename the original jar file, then the signed
1182 // one, then delete the original.
1183 if (!signedJarFile.renameTo(jarFile)) {
1184 File origJar = new File(jarName+".orig");
1185
1186 if (jarFile.renameTo(origJar)) {
1187 if (signedJarFile.renameTo(jarFile)) {
1188 origJar.delete();
1189 } else {
1190 MessageFormat form = new MessageFormat(rb.getString
1191 ("attempt to rename signedJarFile to jarFile failed"));
1192 Object[] source = {signedJarFile, jarFile};
1193 error(form.format(source));
1194 }
1195 } else {
1196 MessageFormat form = new MessageFormat(rb.getString
1197 ("attempt to rename jarFile to origJar failed"));
1198 Object[] source = {jarFile, origJar};
1199 error(form.format(source));
1200 }
1201 }
1202 }
1203
1204 if (hasExpiredCert || hasExpiringCert || notYetValidCert
1205 || badKeyUsage || badExtendedKeyUsage || badNetscapeCertType) {
1206 System.out.println();
1207
1208 System.out.println(rb.getString("Warning: "));
1209 if (badKeyUsage) {
1210 System.out.println(
1211 rb.getString("The signer certificate's KeyUsage extension doesn't allow code signing."));
1212 }
1213
1214 if (badExtendedKeyUsage) {
1215 System.out.println(
1216 rb.getString("The signer certificate's ExtendedKeyUsage extension doesn't allow code signing."));
1217 }
1218
1219 if (badNetscapeCertType) {
1220 System.out.println(
1221 rb.getString("The signer certificate's NetscapeCertType extension doesn't allow code signing."));
1222 }
1223
1224 if (hasExpiredCert) {
1225 System.out.println(
1226 rb.getString("The signer certificate has expired."));
1227 } else if (hasExpiringCert) {
1228 System.out.println(
1229 rb.getString("The signer certificate will expire within six months."));
1230 } else if (notYetValidCert) {
1231 System.out.println(
1232 rb.getString("The signer certificate is not yet valid."));
1233 }
1234 }
1235
1236 // no IOException thrown in the above try clause, so disable
1237 // the catch clause.
1238 // } catch(IOException ioe) {
1239 // error(rb.getString("unable to sign jar: ")+ioe, ioe);
1240 // }
1241 }
1242
1243 /**
1244 * Find the length of header inside bs. The header is a multiple (>=0)
1245 * lines of attributes plus an empty line. The empty line is included
1246 * in the header.
1247 */
1248 @SuppressWarnings("fallthrough")
1249 private int findHeaderEnd(byte[] bs) {
1250 // Initial state true to deal with empty header
1251 boolean newline = true; // just met a newline
1252 int len = bs.length;
1253 for (int i=0; i<len; i++) {
1254 switch (bs[i]) {
1255 case '\r':
1256 if (i < len - 1 && bs[i+1] == '\n') i++;
1257 // fallthrough
1258 case '\n':
1259 if (newline) return i+1; //+1 to get length
1260 newline = true;
1261 break;
1262 default:
1263 newline = false;
1264 }
1265 }
1266 // If header end is not found, it means the MANIFEST.MF has only
1267 // the main attributes section and it does not end with 2 newlines.
1268 // Returns the whole length so that it can be completely replaced.
1269 return len;
1270 }
1271
1272 /**
1273 * signature-related files include:
1274 * . META-INF/MANIFEST.MF
1275 * . META-INF/SIG-*
1276 * . META-INF/*.SF
1277 * . META-INF/*.DSA
1278 * . META-INF/*.RSA
1279 */
1280 private boolean signatureRelated(String name) {
1281 return SignatureFileVerifier.isSigningRelated(name);
1282 }
1283
1284 private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze)
1285 throws IOException
1286 {
1287 ZipEntry ze2 = new ZipEntry(ze.getName());
1288 ze2.setMethod(ze.getMethod());
1289 ze2.setTime(ze.getTime());
1290 ze2.setComment(ze.getComment());
1291 ze2.setExtra(ze.getExtra());
1292 if (ze.getMethod() == ZipEntry.STORED) {
1293 ze2.setSize(ze.getSize());
1294 ze2.setCrc(ze.getCrc());
1295 }
1296 os.putNextEntry(ze2);
1297 writeBytes(zf, ze, os);
1298 }
1299
1300 /**
1301 * Writes all the bytes for a given entry to the specified output stream.
1302 */
1303 private synchronized void writeBytes
1304 (ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException {
1305 int n;
1306
1307 InputStream is = null;
1308 try {
1309 is = zf.getInputStream(ze);
1310 long left = ze.getSize();
1311
1312 while((left > 0) && (n = is.read(buffer, 0, buffer.length)) != -1) {
1313 os.write(buffer, 0, n);
1314 left -= n;
1315 }
1316 } finally {
1317 if (is != null) {
1318 is.close();
1319 }
1320 }
1321 }
1322
1323 void loadKeyStore(String keyStoreName, boolean prompt) {
1324
1325 if (!nullStream && keyStoreName == null) {
1326 keyStoreName = System.getProperty("user.home") + File.separator
1327 + ".keystore";
1328 }
1329
1330 try {
1331 if (providerName == null) {
1332 store = KeyStore.getInstance(storetype);
1333 } else {
1334 store = KeyStore.getInstance(storetype, providerName);
1335 }
1336
1337 // Get pass phrase
1338 // XXX need to disable echo; on UNIX, call getpass(char *prompt)Z
1339 // and on NT call ??
1340 if (token && storepass == null && !protectedPath
1341 && !KeyStoreUtil.isWindowsKeyStore(storetype)) {
1342 storepass = getPass
1343 (rb.getString("Enter Passphrase for keystore: "));
1344 } else if (!token && storepass == null && prompt) {
1345 storepass = getPass
1346 (rb.getString("Enter Passphrase for keystore: "));
1347 }
1348
1349 if (nullStream) {
1350 store.load(null, storepass);
1351 } else {
1352 keyStoreName = keyStoreName.replace(File.separatorChar, '/');
1353 URL url = null;
1354 try {
1355 url = new URL(keyStoreName);
1356 } catch (java.net.MalformedURLException e) {
1357 // try as file
1358 url = new File(keyStoreName).toURI().toURL();
1359 }
1360 InputStream is = null;
1361 try {
1362 is = url.openStream();
1363 store.load(is, storepass);
1364 } finally {
1365 if (is != null) {
1366 is.close();
1367 }
1368 }
1369 }
1370 } catch (IOException ioe) {
1371 throw new RuntimeException(rb.getString("keystore load: ") +
1372 ioe.getMessage());
1373 } catch (java.security.cert.CertificateException ce) {
1374 throw new RuntimeException(rb.getString("certificate exception: ") +
1375 ce.getMessage());
1376 } catch (NoSuchProviderException pe) {
1377 throw new RuntimeException(rb.getString("keystore load: ") +
1378 pe.getMessage());
1379 } catch (NoSuchAlgorithmException nsae) {
1380 throw new RuntimeException(rb.getString("keystore load: ") +
1381 nsae.getMessage());
1382 } catch (KeyStoreException kse) {
1383 throw new RuntimeException
1384 (rb.getString("unable to instantiate keystore class: ") +
1385 kse.getMessage());
1386 }
1387 }
1388
1389 X509Certificate getTsaCert(String alias) {
1390
1391 java.security.cert.Certificate cs = null;
1392
1393 try {
1394 cs = store.getCertificate(alias);
1395 } catch (KeyStoreException kse) {
1396 // this never happens, because keystore has been loaded
1397 }
1398 if (cs == null || (!(cs instanceof X509Certificate))) {
1399 MessageFormat form = new MessageFormat(rb.getString
1400 ("Certificate not found for: alias. alias must reference a valid KeyStore entry containing an X.509 public key certificate for the Timestamping Authority."));
1401 Object[] source = {alias, alias};
1402 error(form.format(source));
1403 }
1404 return (X509Certificate) cs;
1405 }
1406
1407 /**
1408 * Check if userCert is designed to be a code signer
1409 * @param userCert the certificate to be examined
1410 * @param bad 3 booleans to show if the KeyUsage, ExtendedKeyUsage,
1411 * NetscapeCertType has codeSigning flag turned on.
1412 * If null, the class field badKeyUsage, badExtendedKeyUsage,
1413 * badNetscapeCertType will be set.
1414 */
1415 void checkCertUsage(X509Certificate userCert, boolean[] bad) {
1416
1417 // Can act as a signer?
1418 // 1. if KeyUsage, then [0] should be true
1419 // 2. if ExtendedKeyUsage, then should contains ANY or CODE_SIGNING
1420 // 3. if NetscapeCertType, then should contains OBJECT_SIGNING
1421 // 1,2,3 must be true
1422
1423 if (bad != null) {
1424 bad[0] = bad[1] = bad[2] = false;
1425 }
1426
1427 boolean[] keyUsage = userCert.getKeyUsage();
1428 if (keyUsage != null) {
1429 if (keyUsage.length < 1 || !keyUsage[0]) {
1430 if (bad != null) {
1431 bad[0] = true;
1432 } else {
1433 badKeyUsage = true;
1434 }
1435 }
1436 }
1437
1438 try {
1439 List<String> xKeyUsage = userCert.getExtendedKeyUsage();
1440 if (xKeyUsage != null) {
1441 if (!xKeyUsage.contains("2.5.29.37.0") // anyExtendedKeyUsage
1442 && !xKeyUsage.contains("1.3.6.1.5.5.7.3.3")) { // codeSigning
1443 if (bad != null) {
1444 bad[1] = true;
1445 } else {
1446 badExtendedKeyUsage = true;
1447 }
1448 }
1449 }
1450 } catch (java.security.cert.CertificateParsingException e) {
1451 // shouldn't happen
1452 }
1453
1454 try {
1455 // OID_NETSCAPE_CERT_TYPE
1456 byte[] netscapeEx = userCert.getExtensionValue
1457 ("2.16.840.1.113730.1.1");
1458 if (netscapeEx != null) {
1459 DerInputStream in = new DerInputStream(netscapeEx);
1460 byte[] encoded = in.getOctetString();
1461 encoded = new DerValue(encoded).getUnalignedBitString()
1462 .toByteArray();
1463
1464 NetscapeCertTypeExtension extn =
1465 new NetscapeCertTypeExtension(encoded);
1466
1467 Boolean val = extn.get(NetscapeCertTypeExtension.OBJECT_SIGNING);
1468 if (!val) {
1469 if (bad != null) {
1470 bad[2] = true;
1471 } else {
1472 badNetscapeCertType = true;
1473 }
1474 }
1475 }
1476 } catch (IOException e) {
1477 //
1478 }
1479 }
1480
1481 void getAliasInfo(String alias) {
1482
1483 Key key = null;
1484
1485 try {
1486
1487 java.security.cert.Certificate[] cs = null;
1488
1489 try {
1490 cs = store.getCertificateChain(alias);
1491 } catch (KeyStoreException kse) {
1492 // this never happens, because keystore has been loaded
1493 }
1494 if (cs == null) {
1495 MessageFormat form = new MessageFormat(rb.getString
1496 ("Certificate chain not found for: alias. alias must reference a valid KeyStore key entry containing a private key and corresponding public key certificate chain."));
1497 Object[] source = {alias, alias};
1498 error(form.format(source));
1499 }
1500
1501 certChain = new X509Certificate[cs.length];
1502 for (int i=0; i<cs.length; i++) {
1503 if (!(cs[i] instanceof X509Certificate)) {
1504 error(rb.getString
1505 ("found non-X.509 certificate in signer's chain"));
1506 }
1507 certChain[i] = (X509Certificate)cs[i];
1508 }
1509
1510 // order the cert chain if necessary (put user cert first,
1511 // root-cert last in the chain)
1512 X509Certificate userCert
1513 = (X509Certificate)store.getCertificate(alias);
1514
1515 // check validity of signer certificate
1516 try {
1517 userCert.checkValidity();
1518
1519 if (userCert.getNotAfter().getTime() <
1520 System.currentTimeMillis() + SIX_MONTHS) {
1521
1522 hasExpiringCert = true;
1523 }
1524 } catch (CertificateExpiredException cee) {
1525 hasExpiredCert = true;
1526
1527 } catch (CertificateNotYetValidException cnyve) {
1528 notYetValidCert = true;
1529 }
1530
1531 checkCertUsage(userCert, null);
1532
1533 if (!userCert.equals(certChain[0])) {
1534 // need to order ...
1535 X509Certificate[] certChainTmp
1536 = new X509Certificate[certChain.length];
1537 certChainTmp[0] = userCert;
1538 Principal issuer = userCert.getIssuerDN();
1539 for (int i=1; i<certChain.length; i++) {
1540 int j;
1541 // look for the cert whose subject corresponds to the
1542 // given issuer
1543 for (j=0; j<certChainTmp.length; j++) {
1544 if (certChainTmp[j] == null)
1545 continue;
1546 Principal subject = certChainTmp[j].getSubjectDN();
1547 if (issuer.equals(subject)) {
1548 certChain[i] = certChainTmp[j];
1549 issuer = certChainTmp[j].getIssuerDN();
1550 certChainTmp[j] = null;
1551 break;
1552 }
1553 }
1554 if (j == certChainTmp.length) {
1555 error(rb.getString("incomplete certificate chain"));
1556 }
1557
1558 }
1559 certChain = certChainTmp; // ordered
1560 }
1561
1562 try {
1563 if (!token && keypass == null)
1564 key = store.getKey(alias, storepass);
1565 else
1566 key = store.getKey(alias, keypass);
1567 } catch (UnrecoverableKeyException e) {
1568 if (token) {
1569 throw e;
1570 } else if (keypass == null) {
1571 // Did not work out, so prompt user for key password
1572 MessageFormat form = new MessageFormat(rb.getString
1573 ("Enter key password for alias: "));
1574 Object[] source = {alias};
1575 keypass = getPass(form.format(source));
1576 key = store.getKey(alias, keypass);
1577 }
1578 }
1579 } catch (NoSuchAlgorithmException e) {
1580 error(e.getMessage());
1581 } catch (UnrecoverableKeyException e) {
1582 error(rb.getString("unable to recover key from keystore"));
1583 } catch (KeyStoreException kse) {
1584 // this never happens, because keystore has been loaded
1585 }
1586
1587 if (!(key instanceof PrivateKey)) {
1588 MessageFormat form = new MessageFormat(rb.getString
1589 ("key associated with alias not a private key"));
1590 Object[] source = {alias};
1591 error(form.format(source));
1592 } else {
1593 privateKey = (PrivateKey)key;
1594 }
1595 }
1596
1597 void error(String message)
1598 {
1599 System.out.println(rb.getString("jarsigner: ")+message);
1600 System.exit(1);
1601 }
1602
1603
1604 void error(String message, Exception e)
1605 {
1606 System.out.println(rb.getString("jarsigner: ")+message);
1607 if (debug) {
1608 e.printStackTrace();
1609 }
1610 System.exit(1);
1611 }
1612
1613 char[] getPass(String prompt)
1614 {
1615 System.err.print(prompt);
1616 System.err.flush();
1617 try {
1618 char[] pass = Password.readPassword(System.in);
1619
1620 if (pass == null) {
1621 error(rb.getString("you must enter key password"));
1622 } else {
1623 return pass;
1624 }
1625 } catch (IOException ioe) {
1626 error(rb.getString("unable to read password: ")+ioe.getMessage());
1627 }
1628 // this shouldn't happen
1629 return null;
1630 }
1631
1632 /*
1633 * Reads all the bytes for a given zip entry.
1634 */
1635 private synchronized byte[] getBytes(ZipFile zf,
1636 ZipEntry ze) throws IOException {
1637 int n;
1638
1639 InputStream is = null;
1640 try {
1641 is = zf.getInputStream(ze);
1642 baos.reset();
1643 long left = ze.getSize();
1644
1645 while((left > 0) && (n = is.read(buffer, 0, buffer.length)) != -1) {
1646 baos.write(buffer, 0, n);
1647 left -= n;
1648 }
1649 } finally {
1650 if (is != null) {
1651 is.close();
1652 }
1653 }
1654
1655 return baos.toByteArray();
1656 }
1657
1658 /*
1659 * Returns manifest entry from given jar file, or null if given jar file
1660 * does not have a manifest entry.
1661 */
1662 private ZipEntry getManifestFile(ZipFile zf) {
1663 ZipEntry ze = zf.getEntry(JarFile.MANIFEST_NAME);
1664 if (ze == null) {
1665 // Check all entries for matching name
1666 Enumeration<? extends ZipEntry> enum_ = zf.entries();
1667 while (enum_.hasMoreElements() && ze == null) {
1668 ze = enum_.nextElement();
1669 if (!JarFile.MANIFEST_NAME.equalsIgnoreCase
1670 (ze.getName())) {
1671 ze = null;
1672 }
1673 }
1674 }
1675 return ze;
1676 }
1677
1678 /*
1679 * Computes the digests of a zip entry, and returns them as an array
1680 * of base64-encoded strings.
1681 */
1682 private synchronized String[] getDigests(ZipEntry ze, ZipFile zf,
1683 MessageDigest[] digests,
1684 BASE64Encoder encoder)
1685 throws IOException {
1686
1687 int n, i;
1688 InputStream is = null;
1689 try {
1690 is = zf.getInputStream(ze);
1691 long left = ze.getSize();
1692 while((left > 0)
1693 && (n = is.read(buffer, 0, buffer.length)) != -1) {
1694 for (i=0; i<digests.length; i++) {
1695 digests[i].update(buffer, 0, n);
1696 }
1697 left -= n;
1698 }
1699 } finally {
1700 if (is != null) {
1701 is.close();
1702 }
1703 }
1704
1705 // complete the digests
1706 String[] base64Digests = new String[digests.length];
1707 for (i=0; i<digests.length; i++) {
1708 base64Digests[i] = encoder.encode(digests[i].digest());
1709 }
1710 return base64Digests;
1711 }
1712
1713 /*
1714 * Computes the digests of a zip entry, and returns them as a list of
1715 * attributes
1716 */
1717 private Attributes getDigestAttributes(ZipEntry ze, ZipFile zf,
1718 MessageDigest[] digests,
1719 BASE64Encoder encoder)
1720 throws IOException {
1721
1722 String[] base64Digests = getDigests(ze, zf, digests, encoder);
1723 Attributes attrs = new Attributes();
1724
1725 for (int i=0; i<digests.length; i++) {
1726 attrs.putValue(digests[i].getAlgorithm()+"-Digest",
1727 base64Digests[i]);
1728 }
1729 return attrs;
1730 }
1731
1732 /*
1733 * Updates the digest attributes of a manifest entry, by adding or
1734 * replacing digest values.
1735 * A digest value is added if the manifest entry does not contain a digest
1736 * for that particular algorithm.
1737 * A digest value is replaced if it is obsolete.
1738 *
1739 * Returns true if the manifest entry has been changed, and false
1740 * otherwise.
1741 */
1742 private boolean updateDigests(ZipEntry ze, ZipFile zf,
1743 MessageDigest[] digests,
1744 BASE64Encoder encoder,
1745 Manifest mf) throws IOException {
1746 boolean update = false;
1747
1748 Attributes attrs = mf.getAttributes(ze.getName());
1749 String[] base64Digests = getDigests(ze, zf, digests, encoder);
1750
1751 for (int i=0; i<digests.length; i++) {
1752 String name = digests[i].getAlgorithm()+"-Digest";
1753 String mfDigest = attrs.getValue(name);
1754 if (mfDigest == null
1755 && digests[i].getAlgorithm().equalsIgnoreCase("SHA")) {
1756 // treat "SHA" and "SHA1" the same
1757 mfDigest = attrs.getValue("SHA-Digest");
1758 }
1759 if (mfDigest == null) {
1760 // compute digest and add it to list of attributes
1761 attrs.putValue(name, base64Digests[i]);
1762 update=true;
1763 } else {
1764 // compare digests, and replace the one in the manifest
1765 // if they are different
1766 if (!mfDigest.equalsIgnoreCase(base64Digests[i])) {
1767 attrs.putValue(name, base64Digests[i]);
1768 update=true;
1769 }
1770 }
1771 }
1772 return update;
1773 }
1774
1775 /*
1776 * Try to load the specified signing mechanism.
1777 * The URL class loader is used.
1778 */
1779 private ContentSigner loadSigningMechanism(String signerClassName,
1780 String signerClassPath) throws Exception {
1781
1782 // construct class loader
1783 String cpString = null; // make sure env.class.path defaults to dot
1784
1785 // do prepends to get correct ordering
1786 cpString = PathList.appendPath(System.getProperty("env.class.path"), cpString);
1787 cpString = PathList.appendPath(System.getProperty("java.class.path"), cpString);
1788 cpString = PathList.appendPath(signerClassPath, cpString);
1789 URL[] urls = PathList.pathToURLs(cpString);
1790 ClassLoader appClassLoader = new URLClassLoader(urls);
1791
1792 // attempt to find signer
1793 Class<?> signerClass = appClassLoader.loadClass(signerClassName);
1794
1795 // Check that it implements ContentSigner
1796 Object signer = signerClass.newInstance();
1797 if (!(signer instanceof ContentSigner)) {
1798 MessageFormat form = new MessageFormat(
1799 rb.getString("signerClass is not a signing mechanism"));
1800 Object[] source = {signerClass.getName()};
1801 throw new IllegalArgumentException(form.format(source));
1802 }
1803 return (ContentSigner)signer;
1804 }
1805 }
1806
1807 /**
1808 * This is a BASE64Encoder that does not insert a default newline at the end of
1809 * every output line. This is necessary because java.util.jar does its own
1810 * line management (see Manifest.make72Safe()). Inserting additional new lines
1811 * can cause line-wrapping problems (see CR 6219522).
1812 */
1813 class JarBASE64Encoder extends BASE64Encoder {
1814 /**
1815 * Encode the suffix that ends every output line.
1816 */
1817 protected void encodeLineSuffix(OutputStream aStream) throws IOException { }
1818 }
1819
1820 class SignatureFile {
1821
1822 /** SignatureFile */
1823 Manifest sf;
1824
1825 /** .SF base name */
1826 String baseName;
1827
1828 public SignatureFile(MessageDigest digests[],
1829 Manifest mf,
1830 ManifestDigester md,
1831 String baseName,
1832 boolean signManifest)
1833
1834 {
1835 this.baseName = baseName;
1836
1837 String version = System.getProperty("java.version");
1838 String javaVendor = System.getProperty("java.vendor");
1839
1840 sf = new Manifest();
1841 Attributes mattr = sf.getMainAttributes();
1842 BASE64Encoder encoder = new JarBASE64Encoder();
1843
1844 mattr.putValue(Attributes.Name.SIGNATURE_VERSION.toString(), "1.0");
1845 mattr.putValue("Created-By", version + " (" + javaVendor + ")");
1846
1847 if (signManifest) {
1848 // sign the whole manifest
1849 for (int i=0; i < digests.length; i++) {
1850 mattr.putValue(digests[i].getAlgorithm()+"-Digest-Manifest",
1851 encoder.encode(md.manifestDigest(digests[i])));
1852 }
1853 }
1854
1855 // create digest of the manifest main attributes
1856 ManifestDigester.Entry mde =
1857 md.get(ManifestDigester.MF_MAIN_ATTRS, false);
1858 if (mde != null) {
1859 for (int i=0; i < digests.length; i++) {
1860 mattr.putValue(digests[i].getAlgorithm() +
1861 "-Digest-" + ManifestDigester.MF_MAIN_ATTRS,
1862 encoder.encode(mde.digest(digests[i])));
1863 }
1864 } else {
1865 throw new IllegalStateException
1866 ("ManifestDigester failed to create " +
1867 "Manifest-Main-Attribute entry");
1868 }
1869
1870 /* go through the manifest entries and create the digests */
1871
1872 Map<String,Attributes> entries = sf.getEntries();
1873 Iterator<Map.Entry<String,Attributes>> mit =
1874 mf.getEntries().entrySet().iterator();
1875 while(mit.hasNext()) {
1876 Map.Entry<String,Attributes> e = mit.next();
1877 String name = e.getKey();
1878 mde = md.get(name, false);
1879 if (mde != null) {
1880 Attributes attr = new Attributes();
1881 for (int i=0; i < digests.length; i++) {
1882 attr.putValue(digests[i].getAlgorithm()+"-Digest",
1883 encoder.encode(mde.digest(digests[i])));
1884 }
1885 entries.put(name, attr);
1886 }
1887 }
1888 }
1889
1890 /**
1891 * Writes the SignatureFile to the specified OutputStream.
1892 *
1893 * @param out the output stream
1894 * @exception IOException if an I/O error has occurred
1895 */
1896
1897 public void write(OutputStream out) throws IOException
1898 {
1899 sf.write(out);
1900 }
1901
1902 /**
1903 * get .SF file name
1904 */
1905 public String getMetaName()
1906 {
1907 return "META-INF/"+ baseName + ".SF";
1908 }
1909
1910 /**
1911 * get base file name
1912 */
1913 public String getBaseName()
1914 {
1915 return baseName;
1916 }
1917
1918 /*
1919 * Generate a signed data block.
1920 * If a URL or a certificate (containing a URL) for a Timestamping
1921 * Authority is supplied then a signature timestamp is generated and
1922 * inserted into the signed data block.
1923 *
1924 * @param sigalg signature algorithm to use, or null to use default
1925 * @param tsaUrl The location of the Timestamping Authority. If null
1926 * then no timestamp is requested.
1927 * @param tsaCert The certificate for the Timestamping Authority. If null
1928 * then no timestamp is requested.
1929 * @param signingMechanism The signing mechanism to use.
1930 * @param args The command-line arguments to jarsigner.
1931 * @param zipFile The original source Zip file.
1932 */
1933 public Block generateBlock(PrivateKey privateKey,
1934 String sigalg,
1935 X509Certificate[] certChain,
1936 boolean externalSF, String tsaUrl,
1937 X509Certificate tsaCert,
1938 ContentSigner signingMechanism,
1939 String[] args, ZipFile zipFile)
1940 throws NoSuchAlgorithmException, InvalidKeyException, IOException,
1941 SignatureException, CertificateException
1942 {
1943 return new Block(this, privateKey, sigalg, certChain, externalSF,
1944 tsaUrl, tsaCert, signingMechanism, args, zipFile);
1945 }
1946
1947
1948 public static class Block {
1949
1950 private byte[] block;
1951 private String blockFileName;
1952
1953 /*
1954 * Construct a new signature block.
1955 */
1956 Block(SignatureFile sfg, PrivateKey privateKey, String sigalg,
1957 X509Certificate[] certChain, boolean externalSF, String tsaUrl,
1958 X509Certificate tsaCert, ContentSigner signingMechanism,
1959 String[] args, ZipFile zipFile)
1960 throws NoSuchAlgorithmException, InvalidKeyException, IOException,
1961 SignatureException, CertificateException {
1962
1963 Principal issuerName = certChain[0].getIssuerDN();
1964 if (!(issuerName instanceof X500Name)) {
1965 // must extract the original encoded form of DN for subsequent
1966 // name comparison checks (converting to a String and back to
1967 // an encoded DN could cause the types of String attribute
1968 // values to be changed)
1969 X509CertInfo tbsCert = new
1970 X509CertInfo(certChain[0].getTBSCertificate());
1971 issuerName = (Principal)
1972 tbsCert.get(CertificateIssuerName.NAME + "." +
1973 CertificateIssuerName.DN_NAME);
1974 }
1975 BigInteger serial = certChain[0].getSerialNumber();
1976
1977 String digestAlgorithm;
1978 String signatureAlgorithm;
1979 String keyAlgorithm = privateKey.getAlgorithm();
1980 /*
1981 * If no signature algorithm was specified, we choose a
1982 * default that is compatible with the private key algorithm.
1983 */
1984 if (sigalg == null) {
1985
1986 if (keyAlgorithm.equalsIgnoreCase("DSA"))
1987 digestAlgorithm = "SHA1";
1988 else if (keyAlgorithm.equalsIgnoreCase("RSA"))
1989 digestAlgorithm = "SHA1";
1990 else {
1991 throw new RuntimeException("private key is not a DSA or "
1992 + "RSA key");
1993 }
1994 signatureAlgorithm = digestAlgorithm + "with" + keyAlgorithm;
1995 } else {
1996 signatureAlgorithm = sigalg;
1997 }
1998
1999 // check common invalid key/signature algorithm combinations
2000 String sigAlgUpperCase = signatureAlgorithm.toUpperCase();
2001 if ((sigAlgUpperCase.endsWith("WITHRSA") &&
2002 !keyAlgorithm.equalsIgnoreCase("RSA")) ||
2003 (sigAlgUpperCase.endsWith("WITHDSA") &&
2004 !keyAlgorithm.equalsIgnoreCase("DSA"))) {
2005 throw new SignatureException
2006 ("private key algorithm is not compatible with signature algorithm");
2007 }
2008
2009 blockFileName = "META-INF/"+sfg.getBaseName()+"."+keyAlgorithm;
2010
2011 AlgorithmId sigAlg = AlgorithmId.get(signatureAlgorithm);
2012 AlgorithmId digEncrAlg = AlgorithmId.get(keyAlgorithm);
2013
2014 Signature sig = Signature.getInstance(signatureAlgorithm);
2015 sig.initSign(privateKey);
2016
2017 ByteArrayOutputStream baos = new ByteArrayOutputStream();
2018 sfg.write(baos);
2019
2020 byte[] content = baos.toByteArray();
2021
2022 sig.update(content);
2023 byte[] signature = sig.sign();
2024
2025 // Timestamp the signature and generate the signature block file
2026 if (signingMechanism == null) {
2027 signingMechanism = new TimestampedSigner();
2028 }
2029 URI tsaUri = null;
2030 try {
2031 if (tsaUrl != null) {
2032 tsaUri = new URI(tsaUrl);
2033 }
2034 } catch (URISyntaxException e) {
2035 throw new IOException(e);
2036 }
2037
2038 // Assemble parameters for the signing mechanism
2039 ContentSignerParameters params =
2040 new JarSignerParameters(args, tsaUri, tsaCert, signature,
2041 signatureAlgorithm, certChain, content, zipFile);
2042
2043 // Generate the signature block
2044 block = signingMechanism.generateSignedData(
2045 params, externalSF, (tsaUrl != null || tsaCert != null));
2046 }
2047
2048 /*
2049 * get block file name.
2050 */
2051 public String getMetaName()
2052 {
2053 return blockFileName;
2054 }
2055
2056 /**
2057 * Writes the block file to the specified OutputStream.
2058 *
2059 * @param out the output stream
2060 * @exception IOException if an I/O error has occurred
2061 */
2062
2063 public void write(OutputStream out) throws IOException
2064 {
2065 out.write(block);
2066 }
2067 }
2068 }
2069
2070
2071 /*
2072 * This object encapsulates the parameters used to perform content signing.
2073 */
2074 class JarSignerParameters implements ContentSignerParameters {
2075
2076 private String[] args;
2077 private URI tsa;
2078 private X509Certificate tsaCertificate;
2079 private byte[] signature;
2080 private String signatureAlgorithm;
2081 private X509Certificate[] signerCertificateChain;
2082 private byte[] content;
2083 private ZipFile source;
2084
2085 /**
2086 * Create a new object.
2087 */
2088 JarSignerParameters(String[] args, URI tsa, X509Certificate tsaCertificate,
2089 byte[] signature, String signatureAlgorithm,
2090 X509Certificate[] signerCertificateChain, byte[] content,
2091 ZipFile source) {
2092
2093 if (signature == null || signatureAlgorithm == null ||
2094 signerCertificateChain == null) {
2095 throw new NullPointerException();
2096 }
2097 this.args = args;
2098 this.tsa = tsa;
2099 this.tsaCertificate = tsaCertificate;
2100 this.signature = signature;
2101 this.signatureAlgorithm = signatureAlgorithm;
2102 this.signerCertificateChain = signerCertificateChain;
2103 this.content = content;
2104 this.source = source;
2105 }
2106
2107 /**
2108 * Retrieves the command-line arguments.
2109 *
2110 * @return The command-line arguments. May be null.
2111 */
2112 public String[] getCommandLine() {
2113 return args;
2114 }
2115
2116 /**
2117 * Retrieves the identifier for a Timestamping Authority (TSA).
2118 *
2119 * @return The TSA identifier. May be null.
2120 */
2121 public URI getTimestampingAuthority() {
2122 return tsa;
2123 }
2124
2125 /**
2126 * Retrieves the certificate for a Timestamping Authority (TSA).
2127 *
2128 * @return The TSA certificate. May be null.
2129 */
2130 public X509Certificate getTimestampingAuthorityCertificate() {
2131 return tsaCertificate;
2132 }
2133
2134 /**
2135 * Retrieves the signature.
2136 *
2137 * @return The non-null signature bytes.
2138 */
2139 public byte[] getSignature() {
2140 return signature;
2141 }
2142
2143 /**
2144 * Retrieves the name of the signature algorithm.
2145 *
2146 * @return The non-null string name of the signature algorithm.
2147 */
2148 public String getSignatureAlgorithm() {
2149 return signatureAlgorithm;
2150 }
2151
2152 /**
2153 * Retrieves the signer's X.509 certificate chain.
2154 *
2155 * @return The non-null array of X.509 public-key certificates.
2156 */
2157 public X509Certificate[] getSignerCertificateChain() {
2158 return signerCertificateChain;
2159 }
2160
2161 /**
2162 * Retrieves the content that was signed.
2163 *
2164 * @return The content bytes. May be null.
2165 */
2166 public byte[] getContent() {
2167 return content;
2168 }
2169
2170 /**
2171 * Retrieves the original source ZIP file before it was signed.
2172 *
2173 * @return The original ZIP file. May be null.
2174 */
2175 public ZipFile getSource() {
2176 return source;
2177 }
2178 }
|
1 /*
2 * Copyright (c) 2016 Red Hat, Inc.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package sun.security.tools;
27
28 import sun.security.tools.jarsigner.Main;
29
30 /**
31 * This is a stub for compatibility reasons.
32 * Please use sun.security.tools.jarsigner.Main in new code.
33 */
34 public final class JarSigner {
35
36 private JarSigner() { }
37
38 public static void main(String[] args) throws Exception {
39 Main.main(args);
40 }
41 }
|