1 /* 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 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 // 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 an empty line inside bs 1237 */ 1238 private int findHeaderEnd(byte[] bs) { 1239 // An empty line can be at the beginning... 1240 if (bs.length > 1 && bs[0] == '\r' && bs[1] == '\n') { 1241 return 0; 1242 } 1243 // ... or after another line 1244 for (int i=0; i<bs.length-3; i++) { 1245 if (bs[i] == '\r' && bs[i+1] == '\n' && 1246 bs[i+2] == '\r' && bs[i+3] == '\n') { 1247 return i; 1248 } 1249 } 1250 // If header end is not found, return 0, 1251 // which means no behavior change. 1252 return 0; 1253 } 1254 1255 /** 1256 * signature-related files include: 1257 * . META-INF/MANIFEST.MF 1258 * . META-INF/SIG-* 1259 * . META-INF/*.SF 1260 * . META-INF/*.DSA 1261 * . META-INF/*.RSA 1262 */ 1263 private boolean signatureRelated(String name) { 1264 String ucName = name.toUpperCase(); 1265 if (ucName.equals(JarFile.MANIFEST_NAME) || 1266 ucName.equals(META_INF) || 1267 (ucName.startsWith(SIG_PREFIX) && 1268 ucName.indexOf("/") == ucName.lastIndexOf("/"))) { 1269 return true; 1270 } 1271 1272 if (ucName.startsWith(META_INF) && 1273 SignatureFileVerifier.isBlockOrSF(ucName)) { 1274 // .SF/.DSA/.RSA files in META-INF subdirs 1275 // are not considered signature-related 1276 return (ucName.indexOf("/") == ucName.lastIndexOf("/")); 1277 } 1278 1279 return false; 1280 } 1281 1282 private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze) 1283 throws IOException 1284 { 1285 ZipEntry ze2 = new ZipEntry(ze.getName()); 1286 ze2.setMethod(ze.getMethod()); 1287 ze2.setTime(ze.getTime()); 1288 ze2.setComment(ze.getComment()); 1289 ze2.setExtra(ze.getExtra()); 1290 if (ze.getMethod() == ZipEntry.STORED) { 1291 ze2.setSize(ze.getSize()); 1292 ze2.setCrc(ze.getCrc()); 1293 } 1294 os.putNextEntry(ze2); 1295 writeBytes(zf, ze, os); 1296 } 1297 1298 /** 1299 * Writes all the bytes for a given entry to the specified output stream. 1300 */ 1301 private synchronized void writeBytes 1302 (ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException { 1303 int n; 1304 1305 InputStream is = null; 1306 try { 1307 is = zf.getInputStream(ze); 1308 long left = ze.getSize(); 1309 1310 while((left > 0) && (n = is.read(buffer, 0, buffer.length)) != -1) { 1311 os.write(buffer, 0, n); 1312 left -= n; 1313 } 1314 } finally { 1315 if (is != null) { 1316 is.close(); 1317 } 1318 } 1319 } 1320 1321 void loadKeyStore(String keyStoreName, boolean prompt) { 1322 1323 if (!nullStream && keyStoreName == null) { 1324 keyStoreName = System.getProperty("user.home") + File.separator 1325 + ".keystore"; 1326 } 1327 1328 try { 1329 if (providerName == null) { 1330 store = KeyStore.getInstance(storetype); 1331 } else { 1332 store = KeyStore.getInstance(storetype, providerName); 1333 } 1334 1335 // Get pass phrase 1336 // XXX need to disable echo; on UNIX, call getpass(char *prompt)Z 1337 // and on NT call ?? 1338 if (token && storepass == null && !protectedPath 1339 && !KeyStoreUtil.isWindowsKeyStore(storetype)) { 1340 storepass = getPass 1341 (rb.getString("Enter Passphrase for keystore: ")); 1342 } else if (!token && storepass == null && prompt) { 1343 storepass = getPass 1344 (rb.getString("Enter Passphrase for keystore: ")); 1345 } 1346 1347 if (nullStream) { 1348 store.load(null, storepass); 1349 } else { 1350 keyStoreName = keyStoreName.replace(File.separatorChar, '/'); 1351 URL url = null; 1352 try { 1353 url = new URL(keyStoreName); 1354 } catch (java.net.MalformedURLException e) { 1355 // try as file 1356 url = new File(keyStoreName).toURI().toURL(); 1357 } 1358 InputStream is = null; 1359 try { 1360 is = url.openStream(); 1361 store.load(is, storepass); 1362 } finally { 1363 if (is != null) { 1364 is.close(); 1365 } 1366 } 1367 } 1368 } catch (IOException ioe) { 1369 throw new RuntimeException(rb.getString("keystore load: ") + 1370 ioe.getMessage()); 1371 } catch (java.security.cert.CertificateException ce) { 1372 throw new RuntimeException(rb.getString("certificate exception: ") + 1373 ce.getMessage()); 1374 } catch (NoSuchProviderException pe) { 1375 throw new RuntimeException(rb.getString("keystore load: ") + 1376 pe.getMessage()); 1377 } catch (NoSuchAlgorithmException nsae) { 1378 throw new RuntimeException(rb.getString("keystore load: ") + 1379 nsae.getMessage()); 1380 } catch (KeyStoreException kse) { 1381 throw new RuntimeException 1382 (rb.getString("unable to instantiate keystore class: ") + 1383 kse.getMessage()); 1384 } 1385 } 1386 1387 X509Certificate getTsaCert(String alias) { 1388 1389 java.security.cert.Certificate cs = null; 1390 1391 try { 1392 cs = store.getCertificate(alias); 1393 } catch (KeyStoreException kse) { 1394 // this never happens, because keystore has been loaded 1395 } 1396 if (cs == null || (!(cs instanceof X509Certificate))) { 1397 MessageFormat form = new MessageFormat(rb.getString 1398 ("Certificate not found for: alias. alias must reference a valid KeyStore entry containing an X.509 public key certificate for the Timestamping Authority.")); 1399 Object[] source = {alias, alias}; 1400 error(form.format(source)); 1401 } 1402 return (X509Certificate) cs; 1403 } 1404 1405 /** 1406 * Check if userCert is designed to be a code signer 1407 * @param userCert the certificate to be examined 1408 * @param bad 3 booleans to show if the KeyUsage, ExtendedKeyUsage, 1409 * NetscapeCertType has codeSigning flag turned on. 1410 * If null, the class field badKeyUsage, badExtendedKeyUsage, 1411 * badNetscapeCertType will be set. 1412 */ 1413 void checkCertUsage(X509Certificate userCert, boolean[] bad) { 1414 1415 // Can act as a signer? 1416 // 1. if KeyUsage, then [0] should be true 1417 // 2. if ExtendedKeyUsage, then should contains ANY or CODE_SIGNING 1418 // 3. if NetscapeCertType, then should contains OBJECT_SIGNING 1419 // 1,2,3 must be true 1420 1421 if (bad != null) { 1422 bad[0] = bad[1] = bad[2] = false; 1423 } 1424 1425 boolean[] keyUsage = userCert.getKeyUsage(); 1426 if (keyUsage != null) { 1427 if (keyUsage.length < 1 || !keyUsage[0]) { 1428 if (bad != null) { 1429 bad[0] = true; 1430 } else { 1431 badKeyUsage = true; 1432 } 1433 } 1434 } 1435 1436 try { 1437 List<String> xKeyUsage = userCert.getExtendedKeyUsage(); 1438 if (xKeyUsage != null) { 1439 if (!xKeyUsage.contains("2.5.29.37.0") // anyExtendedKeyUsage 1440 && !xKeyUsage.contains("1.3.6.1.5.5.7.3.3")) { // codeSigning 1441 if (bad != null) { 1442 bad[1] = true; 1443 } else { 1444 badExtendedKeyUsage = true; 1445 } 1446 } 1447 } 1448 } catch (java.security.cert.CertificateParsingException e) { 1449 // shouldn't happen 1450 } 1451 1452 try { 1453 // OID_NETSCAPE_CERT_TYPE 1454 byte[] netscapeEx = userCert.getExtensionValue 1455 ("2.16.840.1.113730.1.1"); 1456 if (netscapeEx != null) { 1457 DerInputStream in = new DerInputStream(netscapeEx); 1458 byte[] encoded = in.getOctetString(); 1459 encoded = new DerValue(encoded).getUnalignedBitString() 1460 .toByteArray(); 1461 1462 NetscapeCertTypeExtension extn = 1463 new NetscapeCertTypeExtension(encoded); 1464 1465 Boolean val = (Boolean)extn.get( 1466 NetscapeCertTypeExtension.OBJECT_SIGNING); 1467 if (!val) { 1468 if (bad != null) { 1469 bad[2] = true; 1470 } else { 1471 badNetscapeCertType = true; 1472 } 1473 } 1474 } 1475 } catch (IOException e) { 1476 // 1477 } 1478 } 1479 1480 void getAliasInfo(String alias) { 1481 1482 Key key = null; 1483 1484 try { 1485 1486 java.security.cert.Certificate[] cs = null; 1487 1488 try { 1489 cs = store.getCertificateChain(alias); 1490 } catch (KeyStoreException kse) { 1491 // this never happens, because keystore has been loaded 1492 } 1493 if (cs == null) { 1494 MessageFormat form = new MessageFormat(rb.getString 1495 ("Certificate chain not found for: alias. alias must reference a valid KeyStore key entry containing a private key and corresponding public key certificate chain.")); 1496 Object[] source = {alias, alias}; 1497 error(form.format(source)); 1498 } 1499 1500 certChain = new X509Certificate[cs.length]; 1501 for (int i=0; i<cs.length; i++) { 1502 if (!(cs[i] instanceof X509Certificate)) { 1503 error(rb.getString 1504 ("found non-X.509 certificate in signer's chain")); 1505 } 1506 certChain[i] = (X509Certificate)cs[i]; 1507 } 1508 1509 // order the cert chain if necessary (put user cert first, 1510 // root-cert last in the chain) 1511 X509Certificate userCert 1512 = (X509Certificate)store.getCertificate(alias); 1513 1514 // check validity of signer certificate 1515 try { 1516 userCert.checkValidity(); 1517 1518 if (userCert.getNotAfter().getTime() < 1519 System.currentTimeMillis() + SIX_MONTHS) { 1520 1521 hasExpiringCert = true; 1522 } 1523 } catch (CertificateExpiredException cee) { 1524 hasExpiredCert = true; 1525 1526 } catch (CertificateNotYetValidException cnyve) { 1527 notYetValidCert = true; 1528 } 1529 1530 checkCertUsage(userCert, null); 1531 1532 if (!userCert.equals(certChain[0])) { 1533 // need to order ... 1534 X509Certificate[] certChainTmp 1535 = new X509Certificate[certChain.length]; 1536 certChainTmp[0] = userCert; 1537 Principal issuer = userCert.getIssuerDN(); 1538 for (int i=1; i<certChain.length; i++) { 1539 int j; 1540 // look for the cert whose subject corresponds to the 1541 // given issuer 1542 for (j=0; j<certChainTmp.length; j++) { 1543 if (certChainTmp[j] == null) 1544 continue; 1545 Principal subject = certChainTmp[j].getSubjectDN(); 1546 if (issuer.equals(subject)) { 1547 certChain[i] = certChainTmp[j]; 1548 issuer = certChainTmp[j].getIssuerDN(); 1549 certChainTmp[j] = null; 1550 break; 1551 } 1552 } 1553 if (j == certChainTmp.length) { 1554 error(rb.getString("incomplete certificate chain")); 1555 } 1556 1557 } 1558 certChain = certChainTmp; // ordered 1559 } 1560 1561 try { 1562 if (!token && keypass == null) 1563 key = store.getKey(alias, storepass); 1564 else 1565 key = store.getKey(alias, keypass); 1566 } catch (UnrecoverableKeyException e) { 1567 if (token) { 1568 throw e; 1569 } else if (keypass == null) { 1570 // Did not work out, so prompt user for key password 1571 MessageFormat form = new MessageFormat(rb.getString 1572 ("Enter key password for alias: ")); 1573 Object[] source = {alias}; 1574 keypass = getPass(form.format(source)); 1575 key = store.getKey(alias, keypass); 1576 } 1577 } 1578 } catch (NoSuchAlgorithmException e) { 1579 error(e.getMessage()); 1580 } catch (UnrecoverableKeyException e) { 1581 error(rb.getString("unable to recover key from keystore")); 1582 } catch (KeyStoreException kse) { 1583 // this never happens, because keystore has been loaded 1584 } 1585 1586 if (!(key instanceof PrivateKey)) { 1587 MessageFormat form = new MessageFormat(rb.getString 1588 ("key associated with alias not a private key")); 1589 Object[] source = {alias}; 1590 error(form.format(source)); 1591 } else { 1592 privateKey = (PrivateKey)key; 1593 } 1594 } 1595 1596 void error(String message) 1597 { 1598 System.out.println(rb.getString("jarsigner: ")+message); 1599 System.exit(1); 1600 } 1601 1602 1603 void error(String message, Exception e) 1604 { 1605 System.out.println(rb.getString("jarsigner: ")+message); 1606 if (debug) { 1607 e.printStackTrace(); 1608 } 1609 System.exit(1); 1610 } 1611 1612 char[] getPass(String prompt) 1613 { 1614 System.err.print(prompt); 1615 System.err.flush(); 1616 try { 1617 char[] pass = Password.readPassword(System.in); 1618 1619 if (pass == null) { 1620 error(rb.getString("you must enter key password")); 1621 } else { 1622 return pass; 1623 } 1624 } catch (IOException ioe) { 1625 error(rb.getString("unable to read password: ")+ioe.getMessage()); 1626 } 1627 // this shouldn't happen 1628 return null; 1629 } 1630 1631 /* 1632 * Reads all the bytes for a given zip entry. 1633 */ 1634 private synchronized byte[] getBytes(ZipFile zf, 1635 ZipEntry ze) throws IOException { 1636 int n; 1637 1638 InputStream is = null; 1639 try { 1640 is = zf.getInputStream(ze); 1641 baos.reset(); 1642 long left = ze.getSize(); 1643 1644 while((left > 0) && (n = is.read(buffer, 0, buffer.length)) != -1) { 1645 baos.write(buffer, 0, n); 1646 left -= n; 1647 } 1648 } finally { 1649 if (is != null) { 1650 is.close(); 1651 } 1652 } 1653 1654 return baos.toByteArray(); 1655 } 1656 1657 /* 1658 * Returns manifest entry from given jar file, or null if given jar file 1659 * does not have a manifest entry. 1660 */ 1661 private ZipEntry getManifestFile(ZipFile zf) { 1662 ZipEntry ze = zf.getEntry(JarFile.MANIFEST_NAME); 1663 if (ze == null) { 1664 // Check all entries for matching name 1665 Enumeration<? extends ZipEntry> enum_ = zf.entries(); 1666 while (enum_.hasMoreElements() && ze == null) { 1667 ze = enum_.nextElement(); 1668 if (!JarFile.MANIFEST_NAME.equalsIgnoreCase 1669 (ze.getName())) { 1670 ze = null; 1671 } 1672 } 1673 } 1674 return ze; 1675 } 1676 1677 /* 1678 * Computes the digests of a zip entry, and returns them as an array 1679 * of base64-encoded strings. 1680 */ 1681 private synchronized String[] getDigests(ZipEntry ze, ZipFile zf, 1682 MessageDigest[] digests, 1683 BASE64Encoder encoder) 1684 throws IOException { 1685 1686 int n, i; 1687 InputStream is = null; 1688 try { 1689 is = zf.getInputStream(ze); 1690 long left = ze.getSize(); 1691 while((left > 0) 1692 && (n = is.read(buffer, 0, buffer.length)) != -1) { 1693 for (i=0; i<digests.length; i++) { 1694 digests[i].update(buffer, 0, n); 1695 } 1696 left -= n; 1697 } 1698 } finally { 1699 if (is != null) { 1700 is.close(); 1701 } 1702 } 1703 1704 // complete the digests 1705 String[] base64Digests = new String[digests.length]; 1706 for (i=0; i<digests.length; i++) { 1707 base64Digests[i] = encoder.encode(digests[i].digest()); 1708 } 1709 return base64Digests; 1710 } 1711 1712 /* 1713 * Computes the digests of a zip entry, and returns them as a list of 1714 * attributes 1715 */ 1716 private Attributes getDigestAttributes(ZipEntry ze, ZipFile zf, 1717 MessageDigest[] digests, 1718 BASE64Encoder encoder) 1719 throws IOException { 1720 1721 String[] base64Digests = getDigests(ze, zf, digests, encoder); 1722 Attributes attrs = new Attributes(); 1723 1724 for (int i=0; i<digests.length; i++) { 1725 attrs.putValue(digests[i].getAlgorithm()+"-Digest", 1726 base64Digests[i]); 1727 } 1728 return attrs; 1729 } 1730 1731 /* 1732 * Updates the digest attributes of a manifest entry, by adding or 1733 * replacing digest values. 1734 * A digest value is added if the manifest entry does not contain a digest 1735 * for that particular algorithm. 1736 * A digest value is replaced if it is obsolete. 1737 * 1738 * Returns true if the manifest entry has been changed, and false 1739 * otherwise. 1740 */ 1741 private boolean updateDigests(ZipEntry ze, ZipFile zf, 1742 MessageDigest[] digests, 1743 BASE64Encoder encoder, 1744 Manifest mf) throws IOException { 1745 boolean update = false; 1746 1747 Attributes attrs = mf.getAttributes(ze.getName()); 1748 String[] base64Digests = getDigests(ze, zf, digests, encoder); 1749 1750 for (int i=0; i<digests.length; i++) { 1751 String name = digests[i].getAlgorithm()+"-Digest"; 1752 String mfDigest = attrs.getValue(name); 1753 if (mfDigest == null 1754 && digests[i].getAlgorithm().equalsIgnoreCase("SHA")) { 1755 // treat "SHA" and "SHA1" the same 1756 mfDigest = attrs.getValue("SHA-Digest"); 1757 } 1758 if (mfDigest == null) { 1759 // compute digest and add it to list of attributes 1760 attrs.putValue(name, base64Digests[i]); 1761 update=true; 1762 } else { 1763 // compare digests, and replace the one in the manifest 1764 // if they are different 1765 if (!mfDigest.equalsIgnoreCase(base64Digests[i])) { 1766 attrs.putValue(name, base64Digests[i]); 1767 update=true; 1768 } 1769 } 1770 } 1771 return update; 1772 } 1773 1774 /* 1775 * Try to load the specified signing mechanism. 1776 * The URL class loader is used. 1777 */ 1778 private ContentSigner loadSigningMechanism(String signerClassName, 1779 String signerClassPath) throws Exception { 1780 1781 // construct class loader 1782 String cpString = null; // make sure env.class.path defaults to dot 1783 1784 // do prepends to get correct ordering 1785 cpString = PathList.appendPath(System.getProperty("env.class.path"), cpString); 1786 cpString = PathList.appendPath(System.getProperty("java.class.path"), cpString); 1787 cpString = PathList.appendPath(signerClassPath, cpString); 1788 URL[] urls = PathList.pathToURLs(cpString); 1789 ClassLoader appClassLoader = new URLClassLoader(urls); 1790 1791 // attempt to find signer 1792 Class signerClass = appClassLoader.loadClass(signerClassName); 1793 1794 // Check that it implements ContentSigner 1795 Object signer = signerClass.newInstance(); 1796 if (!(signer instanceof ContentSigner)) { 1797 MessageFormat form = new MessageFormat( 1798 rb.getString("signerClass is not a signing mechanism")); 1799 Object[] source = {signerClass.getName()}; 1800 throw new IllegalArgumentException(form.format(source)); 1801 } 1802 return (ContentSigner)signer; 1803 } 1804 } 1805 1806 /** 1807 * This is a BASE64Encoder that does not insert a default newline at the end of 1808 * every output line. This is necessary because java.util.jar does its own 1809 * line management (see Manifest.make72Safe()). Inserting additional new lines 1810 * can cause line-wrapping problems (see CR 6219522). 1811 */ 1812 class JarBASE64Encoder extends BASE64Encoder { 1813 /** 1814 * Encode the suffix that ends every output line. 1815 */ 1816 protected void encodeLineSuffix(OutputStream aStream) throws IOException { } 1817 } 1818 1819 class SignatureFile { 1820 1821 /** SignatureFile */ 1822 Manifest sf; 1823 1824 /** .SF base name */ 1825 String baseName; 1826 1827 public SignatureFile(MessageDigest digests[], 1828 Manifest mf, 1829 ManifestDigester md, 1830 String baseName, 1831 boolean signManifest) 1832 1833 { 1834 this.baseName = baseName; 1835 1836 String version = System.getProperty("java.version"); 1837 String javaVendor = System.getProperty("java.vendor"); 1838 1839 sf = new Manifest(); 1840 Attributes mattr = sf.getMainAttributes(); 1841 BASE64Encoder encoder = new JarBASE64Encoder(); 1842 1843 mattr.putValue(Attributes.Name.SIGNATURE_VERSION.toString(), "1.0"); 1844 mattr.putValue("Created-By", version + " (" + javaVendor + ")"); 1845 1846 if (signManifest) { 1847 // sign the whole manifest 1848 for (int i=0; i < digests.length; i++) { 1849 mattr.putValue(digests[i].getAlgorithm()+"-Digest-Manifest", 1850 encoder.encode(md.manifestDigest(digests[i]))); 1851 } 1852 } 1853 1854 // create digest of the manifest main attributes 1855 ManifestDigester.Entry mde = 1856 md.get(ManifestDigester.MF_MAIN_ATTRS, false); 1857 if (mde != null) { 1858 for (int i=0; i < digests.length; i++) { 1859 mattr.putValue(digests[i].getAlgorithm() + 1860 "-Digest-" + ManifestDigester.MF_MAIN_ATTRS, 1861 encoder.encode(mde.digest(digests[i]))); 1862 } 1863 } else { 1864 throw new IllegalStateException 1865 ("ManifestDigester failed to create " + 1866 "Manifest-Main-Attribute entry"); 1867 } 1868 1869 /* go through the manifest entries and create the digests */ 1870 1871 Map<String,Attributes> entries = sf.getEntries(); 1872 Iterator<Map.Entry<String,Attributes>> mit = 1873 mf.getEntries().entrySet().iterator(); 1874 while(mit.hasNext()) { 1875 Map.Entry<String,Attributes> e = mit.next(); 1876 String name = e.getKey(); 1877 mde = md.get(name, false); 1878 if (mde != null) { 1879 Attributes attr = new Attributes(); 1880 for (int i=0; i < digests.length; i++) { 1881 attr.putValue(digests[i].getAlgorithm()+"-Digest", 1882 encoder.encode(mde.digest(digests[i]))); 1883 } 1884 entries.put(name, attr); 1885 } 1886 } 1887 } 1888 1889 /** 1890 * Writes the SignatureFile to the specified OutputStream. 1891 * 1892 * @param out the output stream 1893 * @exception IOException if an I/O error has occurred 1894 */ 1895 1896 public void write(OutputStream out) throws IOException 1897 { 1898 sf.write(out); 1899 } 1900 1901 /** 1902 * get .SF file name 1903 */ 1904 public String getMetaName() 1905 { 1906 return "META-INF/"+ baseName + ".SF"; 1907 } 1908 1909 /** 1910 * get base file name 1911 */ 1912 public String getBaseName() 1913 { 1914 return baseName; 1915 } 1916 1917 /* 1918 * Generate a signed data block. 1919 * If a URL or a certificate (containing a URL) for a Timestamping 1920 * Authority is supplied then a signature timestamp is generated and 1921 * inserted into the signed data block. 1922 * 1923 * @param sigalg signature algorithm to use, or null to use default 1924 * @param tsaUrl The location of the Timestamping Authority. If null 1925 * then no timestamp is requested. 1926 * @param tsaCert The certificate for the Timestamping Authority. If null 1927 * then no timestamp is requested. 1928 * @param signingMechanism The signing mechanism to use. 1929 * @param args The command-line arguments to jarsigner. 1930 * @param zipFile The original source Zip file. 1931 */ 1932 public Block generateBlock(PrivateKey privateKey, 1933 String sigalg, 1934 X509Certificate[] certChain, 1935 boolean externalSF, String tsaUrl, 1936 X509Certificate tsaCert, 1937 ContentSigner signingMechanism, 1938 String[] args, ZipFile zipFile) 1939 throws NoSuchAlgorithmException, InvalidKeyException, IOException, 1940 SignatureException, CertificateException 1941 { 1942 return new Block(this, privateKey, sigalg, certChain, externalSF, 1943 tsaUrl, tsaCert, signingMechanism, args, zipFile); 1944 } 1945 1946 1947 public static class Block { 1948 1949 private byte[] block; 1950 private String blockFileName; 1951 1952 /* 1953 * Construct a new signature block. 1954 */ 1955 Block(SignatureFile sfg, PrivateKey privateKey, String sigalg, 1956 X509Certificate[] certChain, boolean externalSF, String tsaUrl, 1957 X509Certificate tsaCert, ContentSigner signingMechanism, 1958 String[] args, ZipFile zipFile) 1959 throws NoSuchAlgorithmException, InvalidKeyException, IOException, 1960 SignatureException, CertificateException { 1961 1962 Principal issuerName = certChain[0].getIssuerDN(); 1963 if (!(issuerName instanceof X500Name)) { 1964 // must extract the original encoded form of DN for subsequent 1965 // name comparison checks (converting to a String and back to 1966 // an encoded DN could cause the types of String attribute 1967 // values to be changed) 1968 X509CertInfo tbsCert = new 1969 X509CertInfo(certChain[0].getTBSCertificate()); 1970 issuerName = (Principal) 1971 tbsCert.get(CertificateIssuerName.NAME + "." + 1972 CertificateIssuerName.DN_NAME); 1973 } 1974 BigInteger serial = certChain[0].getSerialNumber(); 1975 1976 String digestAlgorithm; 1977 String signatureAlgorithm; 1978 String keyAlgorithm = privateKey.getAlgorithm(); 1979 /* 1980 * If no signature algorithm was specified, we choose a 1981 * default that is compatible with the private key algorithm. 1982 */ 1983 if (sigalg == null) { 1984 1985 if (keyAlgorithm.equalsIgnoreCase("DSA")) 1986 digestAlgorithm = "SHA1"; 1987 else if (keyAlgorithm.equalsIgnoreCase("RSA")) 1988 digestAlgorithm = "SHA1"; 1989 else { 1990 throw new RuntimeException("private key is not a DSA or " 1991 + "RSA key"); 1992 } 1993 signatureAlgorithm = digestAlgorithm + "with" + keyAlgorithm; 1994 } else { 1995 signatureAlgorithm = sigalg; 1996 } 1997 1998 // check common invalid key/signature algorithm combinations 1999 String sigAlgUpperCase = signatureAlgorithm.toUpperCase(); 2000 if ((sigAlgUpperCase.endsWith("WITHRSA") && 2001 !keyAlgorithm.equalsIgnoreCase("RSA")) || 2002 (sigAlgUpperCase.endsWith("WITHDSA") && 2003 !keyAlgorithm.equalsIgnoreCase("DSA"))) { 2004 throw new SignatureException 2005 ("private key algorithm is not compatible with signature algorithm"); 2006 } 2007 2008 blockFileName = "META-INF/"+sfg.getBaseName()+"."+keyAlgorithm; 2009 2010 AlgorithmId sigAlg = AlgorithmId.get(signatureAlgorithm); 2011 AlgorithmId digEncrAlg = AlgorithmId.get(keyAlgorithm); 2012 2013 Signature sig = Signature.getInstance(signatureAlgorithm); 2014 sig.initSign(privateKey); 2015 2016 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 2017 sfg.write(baos); 2018 2019 byte[] content = baos.toByteArray(); 2020 2021 sig.update(content); 2022 byte[] signature = sig.sign(); 2023 2024 // Timestamp the signature and generate the signature block file 2025 if (signingMechanism == null) { 2026 signingMechanism = new TimestampedSigner(); 2027 } 2028 URI tsaUri = null; 2029 try { 2030 if (tsaUrl != null) { 2031 tsaUri = new URI(tsaUrl); 2032 } 2033 } catch (URISyntaxException e) { 2034 IOException ioe = new IOException(); 2035 ioe.initCause(e); 2036 throw ioe; 2037 } 2038 2039 // Assemble parameters for the signing mechanism 2040 ContentSignerParameters params = 2041 new JarSignerParameters(args, tsaUri, tsaCert, signature, 2042 signatureAlgorithm, certChain, content, zipFile); 2043 2044 // Generate the signature block 2045 block = signingMechanism.generateSignedData( 2046 params, externalSF, (tsaUrl != null || tsaCert != null)); 2047 } 2048 2049 /* 2050 * get block file name. 2051 */ 2052 public String getMetaName() 2053 { 2054 return blockFileName; 2055 } 2056 2057 /** 2058 * Writes the block file to the specified OutputStream. 2059 * 2060 * @param out the output stream 2061 * @exception IOException if an I/O error has occurred 2062 */ 2063 2064 public void write(OutputStream out) throws IOException 2065 { 2066 out.write(block); 2067 } 2068 } 2069 } 2070 2071 2072 /* 2073 * This object encapsulates the parameters used to perform content signing. 2074 */ 2075 class JarSignerParameters implements ContentSignerParameters { 2076 2077 private String[] args; 2078 private URI tsa; 2079 private X509Certificate tsaCertificate; 2080 private byte[] signature; 2081 private String signatureAlgorithm; 2082 private X509Certificate[] signerCertificateChain; 2083 private byte[] content; 2084 private ZipFile source; 2085 2086 /** 2087 * Create a new object. 2088 */ 2089 JarSignerParameters(String[] args, URI tsa, X509Certificate tsaCertificate, 2090 byte[] signature, String signatureAlgorithm, 2091 X509Certificate[] signerCertificateChain, byte[] content, 2092 ZipFile source) { 2093 2094 if (signature == null || signatureAlgorithm == null || 2095 signerCertificateChain == null) { 2096 throw new NullPointerException(); 2097 } 2098 this.args = args; 2099 this.tsa = tsa; 2100 this.tsaCertificate = tsaCertificate; 2101 this.signature = signature; 2102 this.signatureAlgorithm = signatureAlgorithm; 2103 this.signerCertificateChain = signerCertificateChain; 2104 this.content = content; 2105 this.source = source; 2106 } 2107 2108 /** 2109 * Retrieves the command-line arguments. 2110 * 2111 * @return The command-line arguments. May be null. 2112 */ 2113 public String[] getCommandLine() { 2114 return args; 2115 } 2116 2117 /** 2118 * Retrieves the identifier for a Timestamping Authority (TSA). 2119 * 2120 * @return The TSA identifier. May be null. 2121 */ 2122 public URI getTimestampingAuthority() { 2123 return tsa; 2124 } 2125 2126 /** 2127 * Retrieves the certificate for a Timestamping Authority (TSA). 2128 * 2129 * @return The TSA certificate. May be null. 2130 */ 2131 public X509Certificate getTimestampingAuthorityCertificate() { 2132 return tsaCertificate; 2133 } 2134 2135 /** 2136 * Retrieves the signature. 2137 * 2138 * @return The non-null signature bytes. 2139 */ 2140 public byte[] getSignature() { 2141 return signature; 2142 } 2143 2144 /** 2145 * Retrieves the name of the signature algorithm. 2146 * 2147 * @return The non-null string name of the signature algorithm. 2148 */ 2149 public String getSignatureAlgorithm() { 2150 return signatureAlgorithm; 2151 } 2152 2153 /** 2154 * Retrieves the signer's X.509 certificate chain. 2155 * 2156 * @return The non-null array of X.509 public-key certificates. 2157 */ 2158 public X509Certificate[] getSignerCertificateChain() { 2159 return signerCertificateChain; 2160 } 2161 2162 /** 2163 * Retrieves the content that was signed. 2164 * 2165 * @return The content bytes. May be null. 2166 */ 2167 public byte[] getContent() { 2168 return content; 2169 } 2170 2171 /** 2172 * Retrieves the original source ZIP file before it was signed. 2173 * 2174 * @return The original ZIP file. May be null. 2175 */ 2176 public ZipFile getSource() { 2177 return source; 2178 } 2179 }