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 }