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