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