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