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