1 /* 2 * Copyright (c) 2015, 2019, 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 jdk.security.jarsigner; 27 28 import com.sun.jarsigner.ContentSigner; 29 import com.sun.jarsigner.ContentSignerParameters; 30 import jdk.internal.access.JavaUtilZipFileAccess; 31 import jdk.internal.access.SharedSecrets; 32 import sun.security.tools.PathList; 33 import sun.security.tools.jarsigner.TimestampedSigner; 34 import sun.security.util.Event; 35 import sun.security.util.ManifestDigester; 36 import sun.security.util.SignatureFileVerifier; 37 import sun.security.x509.AlgorithmId; 38 39 import java.io.*; 40 import java.lang.reflect.InvocationTargetException; 41 import java.net.SocketTimeoutException; 42 import java.net.URI; 43 import java.net.URL; 44 import java.net.URLClassLoader; 45 import java.security.*; 46 import java.security.cert.CertPath; 47 import java.security.cert.Certificate; 48 import java.security.cert.CertificateException; 49 import java.security.cert.X509Certificate; 50 import java.util.*; 51 import java.util.function.BiConsumer; 52 import java.util.jar.Attributes; 53 import java.util.jar.JarEntry; 54 import java.util.jar.JarFile; 55 import java.util.jar.Manifest; 56 import java.util.zip.ZipEntry; 57 import java.util.zip.ZipFile; 58 import java.util.zip.ZipOutputStream; 59 60 /** 61 * An immutable utility class to sign a jar file. 62 * <p> 63 * A caller creates a {@code JarSigner.Builder} object, (optionally) sets 64 * some parameters, and calls {@link JarSigner.Builder#build build} to create 65 * a {@code JarSigner} object. This {@code JarSigner} object can then 66 * be used to sign a jar file. 67 * <p> 68 * Unless otherwise stated, calling a method of {@code JarSigner} or 69 * {@code JarSigner.Builder} with a null argument will throw 70 * a {@link NullPointerException}. 71 * <p> 72 * Example: 73 * <pre> 74 * JarSigner signer = new JarSigner.Builder(key, certPath) 75 * .digestAlgorithm("SHA-1") 76 * .signatureAlgorithm("SHA1withDSA") 77 * .build(); 78 * try (ZipFile in = new ZipFile(inputFile); 79 * FileOutputStream out = new FileOutputStream(outputFile)) { 80 * signer.sign(in, out); 81 * } 82 * </pre> 83 * 84 * @since 9 85 */ 86 public final class JarSigner { 87 88 static final JavaUtilZipFileAccess JUZFA = SharedSecrets.getJavaUtilZipFileAccess(); 89 90 /** 91 * A mutable builder class that can create an immutable {@code JarSigner} 92 * from various signing-related parameters. 93 * 94 * @since 9 95 */ 96 public static class Builder { 97 98 // Signer materials: 99 final PrivateKey privateKey; 100 final X509Certificate[] certChain; 101 102 // JarSigner options: 103 // Support multiple digestalg internally. Can be null, but not empty 104 String[] digestalg; 105 String sigalg; 106 // Precisely should be one provider for each digestalg, maybe later 107 Provider digestProvider; 108 Provider sigProvider; 109 URI tsaUrl; 110 String signerName; 111 BiConsumer<String,String> handler; 112 113 // Implementation-specific properties: 114 String tSAPolicyID; 115 String tSADigestAlg; 116 boolean signManifest = true; 117 boolean externalSF = true; 118 String altSignerPath; 119 String altSigner; 120 121 /** 122 * Creates a {@code JarSigner.Builder} object with 123 * a {@link KeyStore.PrivateKeyEntry} object. 124 * 125 * @param entry the {@link KeyStore.PrivateKeyEntry} of the signer. 126 */ 127 public Builder(KeyStore.PrivateKeyEntry entry) { 128 this.privateKey = entry.getPrivateKey(); 129 try { 130 // called internally, no need to clone 131 Certificate[] certs = entry.getCertificateChain(); 132 this.certChain = Arrays.copyOf(certs, certs.length, 133 X509Certificate[].class); 134 } catch (ArrayStoreException ase) { 135 // Wrong type, not X509Certificate. Won't document. 136 throw new IllegalArgumentException( 137 "Entry does not contain X509Certificate"); 138 } 139 } 140 141 /** 142 * Creates a {@code JarSigner.Builder} object with a private key and 143 * a certification path. 144 * 145 * @param privateKey the private key of the signer. 146 * @param certPath the certification path of the signer. 147 * @throws IllegalArgumentException if {@code certPath} is empty, or 148 * the {@code privateKey} algorithm does not match the algorithm 149 * of the {@code PublicKey} in the end entity certificate 150 * (the first certificate in {@code certPath}). 151 */ 152 public Builder(PrivateKey privateKey, CertPath certPath) { 153 List<? extends Certificate> certs = certPath.getCertificates(); 154 if (certs.isEmpty()) { 155 throw new IllegalArgumentException("certPath cannot be empty"); 156 } 157 if (!privateKey.getAlgorithm().equals 158 (certs.get(0).getPublicKey().getAlgorithm())) { 159 throw new IllegalArgumentException 160 ("private key algorithm does not match " + 161 "algorithm of public key in end entity " + 162 "certificate (the 1st in certPath)"); 163 } 164 this.privateKey = privateKey; 165 try { 166 this.certChain = certs.toArray(new X509Certificate[certs.size()]); 167 } catch (ArrayStoreException ase) { 168 // Wrong type, not X509Certificate. 169 throw new IllegalArgumentException( 170 "Entry does not contain X509Certificate"); 171 } 172 } 173 174 /** 175 * Sets the digest algorithm. If no digest algorithm is specified, 176 * the default algorithm returned by {@link #getDefaultDigestAlgorithm} 177 * will be used. 178 * 179 * @param algorithm the standard name of the algorithm. See 180 * the {@code MessageDigest} section in the <a href= 181 * "{@docRoot}/../specs/security/standard-names.html#messagedigest-algorithms"> 182 * Java Cryptography Architecture Standard Algorithm Name 183 * Documentation</a> for information about standard algorithm names. 184 * @return the {@code JarSigner.Builder} itself. 185 * @throws NoSuchAlgorithmException if {@code algorithm} is not available. 186 */ 187 public Builder digestAlgorithm(String algorithm) throws NoSuchAlgorithmException { 188 MessageDigest.getInstance(Objects.requireNonNull(algorithm)); 189 this.digestalg = new String[]{algorithm}; 190 this.digestProvider = null; 191 return this; 192 } 193 194 /** 195 * Sets the digest algorithm from the specified provider. 196 * If no digest algorithm is specified, the default algorithm 197 * returned by {@link #getDefaultDigestAlgorithm} will be used. 198 * 199 * @param algorithm the standard name of the algorithm. See 200 * the {@code MessageDigest} section in the <a href= 201 * "{@docRoot}/../specs/security/standard-names.html#messagedigest-algorithms"> 202 * Java Cryptography Architecture Standard Algorithm Name 203 * Documentation</a> for information about standard algorithm names. 204 * @param provider the provider. 205 * @return the {@code JarSigner.Builder} itself. 206 * @throws NoSuchAlgorithmException if {@code algorithm} is not 207 * available in the specified provider. 208 */ 209 public Builder digestAlgorithm(String algorithm, Provider provider) 210 throws NoSuchAlgorithmException { 211 MessageDigest.getInstance( 212 Objects.requireNonNull(algorithm), 213 Objects.requireNonNull(provider)); 214 this.digestalg = new String[]{algorithm}; 215 this.digestProvider = provider; 216 return this; 217 } 218 219 /** 220 * Sets the signature algorithm. If no signature algorithm 221 * is specified, the default signature algorithm returned by 222 * {@link #getDefaultSignatureAlgorithm} for the private key 223 * will be used. 224 * 225 * @param algorithm the standard name of the algorithm. See 226 * the {@code Signature} section in the <a href= 227 * "{@docRoot}/../specs/security/standard-names.html#signature-algorithms"> 228 * Java Cryptography Architecture Standard Algorithm Name 229 * Documentation</a> for information about standard algorithm names. 230 * @return the {@code JarSigner.Builder} itself. 231 * @throws NoSuchAlgorithmException if {@code algorithm} is not available. 232 * @throws IllegalArgumentException if {@code algorithm} is not 233 * compatible with the algorithm of the signer's private key. 234 */ 235 public Builder signatureAlgorithm(String algorithm) 236 throws NoSuchAlgorithmException { 237 // Check availability 238 Signature.getInstance(Objects.requireNonNull(algorithm)); 239 AlgorithmId.checkKeyAndSigAlgMatch( 240 privateKey.getAlgorithm(), algorithm); 241 this.sigalg = algorithm; 242 this.sigProvider = null; 243 return this; 244 } 245 246 /** 247 * Sets the signature algorithm from the specified provider. If no 248 * signature algorithm is specified, the default signature algorithm 249 * returned by {@link #getDefaultSignatureAlgorithm} for the private 250 * key will be used. 251 * 252 * @param algorithm the standard name of the algorithm. See 253 * the {@code Signature} section in the <a href= 254 * "{@docRoot}/../specs/security/standard-names.html#signature-algorithms"> 255 * Java Cryptography Architecture Standard Algorithm Name 256 * Documentation</a> for information about standard algorithm names. 257 * @param provider the provider. 258 * @return the {@code JarSigner.Builder} itself. 259 * @throws NoSuchAlgorithmException if {@code algorithm} is not 260 * available in the specified provider. 261 * @throws IllegalArgumentException if {@code algorithm} is not 262 * compatible with the algorithm of the signer's private key. 263 */ 264 public Builder signatureAlgorithm(String algorithm, Provider provider) 265 throws NoSuchAlgorithmException { 266 // Check availability 267 Signature.getInstance( 268 Objects.requireNonNull(algorithm), 269 Objects.requireNonNull(provider)); 270 AlgorithmId.checkKeyAndSigAlgMatch( 271 privateKey.getAlgorithm(), algorithm); 272 this.sigalg = algorithm; 273 this.sigProvider = provider; 274 return this; 275 } 276 277 /** 278 * Sets the URI of the Time Stamping Authority (TSA). 279 * 280 * @param uri the URI. 281 * @return the {@code JarSigner.Builder} itself. 282 */ 283 public Builder tsa(URI uri) { 284 this.tsaUrl = Objects.requireNonNull(uri); 285 return this; 286 } 287 288 /** 289 * Sets the signer name. The name will be used as the base name for 290 * the signature files. All lowercase characters will be converted to 291 * uppercase for signature file names. If a signer name is not 292 * specified, the string "SIGNER" will be used. 293 * 294 * @param name the signer name. 295 * @return the {@code JarSigner.Builder} itself. 296 * @throws IllegalArgumentException if {@code name} is empty or has 297 * a size bigger than 8, or it contains characters not from the 298 * set "a-zA-Z0-9_-". 299 */ 300 public Builder signerName(String name) { 301 if (name.isEmpty() || name.length() > 8) { 302 throw new IllegalArgumentException("Name too long"); 303 } 304 305 name = name.toUpperCase(Locale.ENGLISH); 306 307 for (int j = 0; j < name.length(); j++) { 308 char c = name.charAt(j); 309 if (! 310 ((c >= 'A' && c <= 'Z') || 311 (c >= '0' && c <= '9') || 312 (c == '-') || 313 (c == '_'))) { 314 throw new IllegalArgumentException( 315 "Invalid characters in name"); 316 } 317 } 318 this.signerName = name; 319 return this; 320 } 321 322 /** 323 * Sets en event handler that will be triggered when a {@link JarEntry} 324 * is to be added, signed, or updated during the signing process. 325 * <p> 326 * The handler can be used to display signing progress. The first 327 * argument of the handler can be "adding", "signing", or "updating", 328 * and the second argument is the name of the {@link JarEntry} 329 * being processed. 330 * 331 * @param handler the event handler. 332 * @return the {@code JarSigner.Builder} itself. 333 */ 334 public Builder eventHandler(BiConsumer<String,String> handler) { 335 this.handler = Objects.requireNonNull(handler); 336 return this; 337 } 338 339 /** 340 * Sets an additional implementation-specific property indicated by 341 * the specified key. 342 * 343 * @implNote This implementation supports the following properties: 344 * <ul> 345 * <li>"tsaDigestAlg": algorithm of digest data in the timestamping 346 * request. The default value is the same as the result of 347 * {@link #getDefaultDigestAlgorithm}. 348 * <li>"tsaPolicyId": TSAPolicyID for Timestamping Authority. 349 * No default value. 350 * <li>"internalsf": "true" if the .SF file is included inside the 351 * signature block, "false" otherwise. Default "false". 352 * <li>"sectionsonly": "true" if the .SF file only contains the hash 353 * value for each section of the manifest and not for the whole 354 * manifest, "false" otherwise. Default "false". 355 * </ul> 356 * All property names are case-insensitive. 357 * 358 * @param key the name of the property. 359 * @param value the value of the property. 360 * @return the {@code JarSigner.Builder} itself. 361 * @throws UnsupportedOperationException if the key is not supported 362 * by this implementation. 363 * @throws IllegalArgumentException if the value is not accepted as 364 * a legal value for this key. 365 */ 366 public Builder setProperty(String key, String value) { 367 Objects.requireNonNull(key); 368 Objects.requireNonNull(value); 369 switch (key.toLowerCase(Locale.US)) { 370 case "tsadigestalg": 371 try { 372 MessageDigest.getInstance(value); 373 } catch (NoSuchAlgorithmException nsae) { 374 throw new IllegalArgumentException( 375 "Invalid tsadigestalg", nsae); 376 } 377 this.tSADigestAlg = value; 378 break; 379 case "tsapolicyid": 380 this.tSAPolicyID = value; 381 break; 382 case "internalsf": 383 switch (value) { 384 case "true": 385 externalSF = false; 386 break; 387 case "false": 388 externalSF = true; 389 break; 390 default: 391 throw new IllegalArgumentException( 392 "Invalid internalsf value"); 393 } 394 break; 395 case "sectionsonly": 396 switch (value) { 397 case "true": 398 signManifest = false; 399 break; 400 case "false": 401 signManifest = true; 402 break; 403 default: 404 throw new IllegalArgumentException( 405 "Invalid signManifest value"); 406 } 407 break; 408 case "altsignerpath": 409 altSignerPath = value; 410 break; 411 case "altsigner": 412 altSigner = value; 413 break; 414 default: 415 throw new UnsupportedOperationException( 416 "Unsupported key " + key); 417 } 418 return this; 419 } 420 421 /** 422 * Gets the default digest algorithm. 423 * 424 * @implNote This implementation returns "SHA-256". The value may 425 * change in the future. 426 * 427 * @return the default digest algorithm. 428 */ 429 public static String getDefaultDigestAlgorithm() { 430 return "SHA-256"; 431 } 432 433 /** 434 * Gets the default signature algorithm for a private key. 435 * For example, SHA256withRSA for a 2048-bit RSA key, and 436 * SHA384withECDSA for a 384-bit EC key. 437 * 438 * @implNote This implementation makes use of comparable strengths 439 * as defined in Tables 2 and 3 of NIST SP 800-57 Part 1-Rev.4. 440 * Specifically, if a DSA or RSA key with a key size greater than 7680 441 * bits, or an EC key with a key size greater than or equal to 512 bits, 442 * SHA-512 will be used as the hash function for the signature. 443 * If a DSA or RSA key has a key size greater than 3072 bits, or an 444 * EC key has a key size greater than or equal to 384 bits, SHA-384 will 445 * be used. Otherwise, SHA-256 will be used. The value may 446 * change in the future. 447 * 448 * @param key the private key. 449 * @return the default signature algorithm. Returns null if a default 450 * signature algorithm cannot be found. In this case, 451 * {@link #signatureAlgorithm} must be called to specify a 452 * signature algorithm. Otherwise, the {@link #build} method 453 * will throw an {@link IllegalArgumentException}. 454 */ 455 public static String getDefaultSignatureAlgorithm(PrivateKey key) { 456 return AlgorithmId.getDefaultSigAlgForKey(Objects.requireNonNull(key)); 457 } 458 459 /** 460 * Builds a {@code JarSigner} object from the parameters set by the 461 * setter methods. 462 * <p> 463 * This method does not modify internal state of this {@code Builder} 464 * object and can be called multiple times to generate multiple 465 * {@code JarSigner} objects. After this method is called, calling 466 * any method on this {@code Builder} will have no effect on 467 * the newly built {@code JarSigner} object. 468 * 469 * @return the {@code JarSigner} object. 470 * @throws IllegalArgumentException if a signature algorithm is not 471 * set and cannot be derived from the private key using the 472 * {@link #getDefaultSignatureAlgorithm} method. 473 */ 474 public JarSigner build() { 475 return new JarSigner(this); 476 } 477 } 478 479 private static final String META_INF = "META-INF/"; 480 481 // All fields in Builder are duplicated here as final. Those not 482 // provided but has a default value will be filled with default value. 483 484 // Precisely, a final array field can still be modified if only 485 // reference is copied, no clone is done because we are concerned about 486 // casual change instead of malicious attack. 487 488 // Signer materials: 489 private final PrivateKey privateKey; 490 private final X509Certificate[] certChain; 491 492 // JarSigner options: 493 private final String[] digestalg; 494 private final String sigalg; 495 private final Provider digestProvider; 496 private final Provider sigProvider; 497 private final URI tsaUrl; 498 private final String signerName; 499 private final BiConsumer<String,String> handler; 500 501 // Implementation-specific properties: 502 private final String tSAPolicyID; 503 private final String tSADigestAlg; 504 private final boolean signManifest; // "sign" the whole manifest 505 private final boolean externalSF; // leave the .SF out of the PKCS7 block 506 private final String altSignerPath; 507 private final String altSigner; 508 private boolean extraAttrsDetected; 509 510 private JarSigner(JarSigner.Builder builder) { 511 512 this.privateKey = builder.privateKey; 513 this.certChain = builder.certChain; 514 if (builder.digestalg != null) { 515 // No need to clone because builder only accepts one alg now 516 this.digestalg = builder.digestalg; 517 } else { 518 this.digestalg = new String[] { 519 Builder.getDefaultDigestAlgorithm() }; 520 } 521 this.digestProvider = builder.digestProvider; 522 if (builder.sigalg != null) { 523 this.sigalg = builder.sigalg; 524 } else { 525 this.sigalg = JarSigner.Builder 526 .getDefaultSignatureAlgorithm(privateKey); 527 if (this.sigalg == null) { 528 throw new IllegalArgumentException( 529 "No signature alg for " + privateKey.getAlgorithm()); 530 } 531 } 532 this.sigProvider = builder.sigProvider; 533 this.tsaUrl = builder.tsaUrl; 534 535 if (builder.signerName == null) { 536 this.signerName = "SIGNER"; 537 } else { 538 this.signerName = builder.signerName; 539 } 540 this.handler = builder.handler; 541 542 if (builder.tSADigestAlg != null) { 543 this.tSADigestAlg = builder.tSADigestAlg; 544 } else { 545 this.tSADigestAlg = Builder.getDefaultDigestAlgorithm(); 546 } 547 this.tSAPolicyID = builder.tSAPolicyID; 548 this.signManifest = builder.signManifest; 549 this.externalSF = builder.externalSF; 550 this.altSigner = builder.altSigner; 551 this.altSignerPath = builder.altSignerPath; 552 } 553 554 /** 555 * Signs a file into an {@link OutputStream}. This method will not close 556 * {@code file} or {@code os}. 557 * <p> 558 * If an I/O error or signing error occurs during the signing, then it may 559 * do so after some bytes have been written. Consequently, the output 560 * stream may be in an inconsistent state. It is strongly recommended that 561 * it be promptly closed in this case. 562 * 563 * @param file the file to sign. 564 * @param os the output stream. 565 * @throws JarSignerException if the signing fails. 566 */ 567 public void sign(ZipFile file, OutputStream os) { 568 try { 569 sign0(Objects.requireNonNull(file), 570 Objects.requireNonNull(os)); 571 } catch (SocketTimeoutException | CertificateException e) { 572 // CertificateException is thrown when the received cert from TSA 573 // has no id-kp-timeStamping in its Extended Key Usages extension. 574 throw new JarSignerException("Error applying timestamp", e); 575 } catch (IOException ioe) { 576 throw new JarSignerException("I/O error", ioe); 577 } catch (NoSuchAlgorithmException | InvalidKeyException e) { 578 throw new JarSignerException("Error in signer materials", e); 579 } catch (SignatureException se) { 580 throw new JarSignerException("Error creating signature", se); 581 } 582 } 583 584 /** 585 * Returns the digest algorithm for this {@code JarSigner}. 586 * <p> 587 * The return value is never null. 588 * 589 * @return the digest algorithm. 590 */ 591 public String getDigestAlgorithm() { 592 return digestalg[0]; 593 } 594 595 /** 596 * Returns the signature algorithm for this {@code JarSigner}. 597 * <p> 598 * The return value is never null. 599 * 600 * @return the signature algorithm. 601 */ 602 public String getSignatureAlgorithm() { 603 return sigalg; 604 } 605 606 /** 607 * Returns the URI of the Time Stamping Authority (TSA). 608 * 609 * @return the URI of the TSA. 610 */ 611 public URI getTsa() { 612 return tsaUrl; 613 } 614 615 /** 616 * Returns the signer name of this {@code JarSigner}. 617 * <p> 618 * The return value is never null. 619 * 620 * @return the signer name. 621 */ 622 public String getSignerName() { 623 return signerName; 624 } 625 626 /** 627 * Returns the value of an additional implementation-specific property 628 * indicated by the specified key. If a property is not set but has a 629 * default value, the default value will be returned. 630 * 631 * @implNote See {@link JarSigner.Builder#setProperty} for a list of 632 * properties this implementation supports. All property names are 633 * case-insensitive. 634 * 635 * @param key the name of the property. 636 * @return the value for the property. 637 * @throws UnsupportedOperationException if the key is not supported 638 * by this implementation. 639 */ 640 public String getProperty(String key) { 641 Objects.requireNonNull(key); 642 switch (key.toLowerCase(Locale.US)) { 643 case "tsadigestalg": 644 return tSADigestAlg; 645 case "tsapolicyid": 646 return tSAPolicyID; 647 case "internalsf": 648 return Boolean.toString(!externalSF); 649 case "sectionsonly": 650 return Boolean.toString(!signManifest); 651 case "altsignerpath": 652 return altSignerPath; 653 case "altsigner": 654 return altSigner; 655 default: 656 throw new UnsupportedOperationException( 657 "Unsupported key " + key); 658 } 659 } 660 661 private void sign0(ZipFile zipFile, OutputStream os) 662 throws IOException, CertificateException, NoSuchAlgorithmException, 663 SignatureException, InvalidKeyException { 664 MessageDigest[] digests; 665 try { 666 digests = new MessageDigest[digestalg.length]; 667 for (int i = 0; i < digestalg.length; i++) { 668 if (digestProvider == null) { 669 digests[i] = MessageDigest.getInstance(digestalg[i]); 670 } else { 671 digests[i] = MessageDigest.getInstance( 672 digestalg[i], digestProvider); 673 } 674 } 675 } catch (NoSuchAlgorithmException asae) { 676 // Should not happen. User provided alg were checked, and default 677 // alg should always be available. 678 throw new AssertionError(asae); 679 } 680 681 ZipOutputStream zos = new ZipOutputStream(os); 682 683 Manifest manifest = new Manifest(); 684 byte[] mfRawBytes = null; 685 686 // Check if manifest exists 687 ZipEntry mfFile = getManifestFile(zipFile); 688 boolean mfCreated = mfFile == null; 689 if (!mfCreated) { 690 // Manifest exists. Read its raw bytes. 691 mfRawBytes = zipFile.getInputStream(mfFile).readAllBytes(); 692 manifest.read(new ByteArrayInputStream(mfRawBytes)); 693 } else { 694 // Create new manifest 695 Attributes mattr = manifest.getMainAttributes(); 696 mattr.putValue(Attributes.Name.MANIFEST_VERSION.toString(), 697 "1.0"); 698 String javaVendor = System.getProperty("java.vendor"); 699 String jdkVersion = System.getProperty("java.version"); 700 mattr.putValue("Created-By", jdkVersion + " (" + javaVendor 701 + ")"); 702 mfFile = new ZipEntry(JarFile.MANIFEST_NAME); 703 } 704 705 /* 706 * For each entry in jar 707 * (except for signature-related META-INF entries), 708 * do the following: 709 * 710 * - if entry is not contained in manifest, add it to manifest; 711 * - if entry is contained in manifest, calculate its hash and 712 * compare it with the one in the manifest; if they are 713 * different, replace the hash in the manifest with the newly 714 * generated one. (This may invalidate existing signatures!) 715 */ 716 Vector<ZipEntry> mfFiles = new Vector<>(); 717 718 boolean wasSigned = false; 719 720 for (Enumeration<? extends ZipEntry> enum_ = zipFile.entries(); 721 enum_.hasMoreElements(); ) { 722 ZipEntry ze = enum_.nextElement(); 723 724 if (ze.getName().startsWith(META_INF)) { 725 // Store META-INF files in vector, so they can be written 726 // out first 727 mfFiles.addElement(ze); 728 729 String zeNameUp = ze.getName().toUpperCase(Locale.ENGLISH); 730 if (SignatureFileVerifier.isBlockOrSF(zeNameUp) 731 // no need to preserve binary manifest portions 732 // if the only existing signature will be replaced 733 && !zeNameUp.startsWith(SignatureFile 734 .getBaseSignatureFilesName(signerName))) { 735 wasSigned = true; 736 } 737 738 if (SignatureFileVerifier.isSigningRelated(ze.getName())) { 739 // ignore signature-related and manifest files 740 continue; 741 } 742 } 743 744 if (manifest.getAttributes(ze.getName()) != null) { 745 // jar entry is contained in manifest, check and 746 // possibly update its digest attributes 747 updateDigests(ze, zipFile, digests, manifest); 748 } else if (!ze.isDirectory()) { 749 // Add entry to manifest 750 Attributes attrs = getDigestAttributes(ze, zipFile, digests); 751 manifest.getEntries().put(ze.getName(), attrs); 752 } 753 } 754 755 /* 756 * Note: 757 * 758 * The Attributes object is based on HashMap and can handle 759 * continuation lines. Therefore, even if the contents are not changed 760 * (in a Map view), the bytes that it write() may be different from 761 * the original bytes that it read() from. Since the signature is 762 * based on raw bytes, we must retain the exact bytes. 763 */ 764 boolean mfModified; 765 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 766 if (mfCreated || !wasSigned) { 767 mfModified = true; 768 manifest.write(baos); 769 mfRawBytes = baos.toByteArray(); 770 } else { 771 772 // the manifest before updating 773 Manifest oldManifest = new Manifest( 774 new ByteArrayInputStream(mfRawBytes)); 775 mfModified = !oldManifest.equals(manifest); 776 if (!mfModified) { 777 // leave whole manifest (mfRawBytes) unmodified 778 } else { 779 // reproduce the manifest raw bytes for unmodified sections 780 manifest.write(baos); 781 byte[] mfNewRawBytes = baos.toByteArray(); 782 baos.reset(); 783 784 ManifestDigester oldMd = new ManifestDigester(mfRawBytes); 785 ManifestDigester newMd = new ManifestDigester(mfNewRawBytes); 786 787 // main attributes 788 if (manifest.getMainAttributes().equals( 789 oldManifest.getMainAttributes()) 790 && (manifest.getEntries().isEmpty() || 791 oldMd.getMainAttsEntry().isProperlyDelimited())) { 792 oldMd.getMainAttsEntry().reproduceRaw(baos); 793 } else { 794 newMd.getMainAttsEntry().reproduceRaw(baos); 795 } 796 797 // individual sections 798 for (Map.Entry<String,Attributes> entry : 799 manifest.getEntries().entrySet()) { 800 String sectionName = entry.getKey(); 801 Attributes entryAtts = entry.getValue(); 802 if (entryAtts.equals(oldManifest.getAttributes(sectionName)) 803 && oldMd.get(sectionName).isProperlyDelimited()) { 804 oldMd.get(sectionName).reproduceRaw(baos); 805 } else { 806 newMd.get(sectionName).reproduceRaw(baos); 807 } 808 } 809 810 mfRawBytes = baos.toByteArray(); 811 } 812 } 813 814 // Write out the manifest 815 if (mfModified) { 816 // manifest file has new length 817 mfFile = new ZipEntry(JarFile.MANIFEST_NAME); 818 } 819 if (handler != null) { 820 if (mfCreated || !mfModified) { 821 handler.accept("adding", mfFile.getName()); 822 } else { 823 handler.accept("updating", mfFile.getName()); 824 } 825 } 826 zos.putNextEntry(mfFile); 827 zos.write(mfRawBytes); 828 829 // Calculate SignatureFile (".SF") and SignatureBlockFile 830 ManifestDigester manDig = new ManifestDigester(mfRawBytes); 831 SignatureFile sf = new SignatureFile(digests, manifest, manDig, 832 signerName, signManifest); 833 834 byte[] block; 835 836 Signature signer; 837 if (sigProvider == null ) { 838 signer = Signature.getInstance(sigalg); 839 } else { 840 signer = Signature.getInstance(sigalg, sigProvider); 841 } 842 signer.initSign(privateKey); 843 844 baos.reset(); 845 sf.write(baos); 846 byte[] content = baos.toByteArray(); 847 848 signer.update(content); 849 byte[] signature = signer.sign(); 850 851 @SuppressWarnings("removal") 852 ContentSigner signingMechanism = null; 853 if (altSigner != null) { 854 signingMechanism = loadSigningMechanism(altSigner, 855 altSignerPath); 856 } 857 858 @SuppressWarnings("removal") 859 ContentSignerParameters params = 860 new JarSignerParameters(null, tsaUrl, tSAPolicyID, 861 tSADigestAlg, signature, 862 signer.getAlgorithm(), certChain, content, zipFile); 863 block = sf.generateBlock(params, externalSF, signingMechanism); 864 865 String sfFilename = sf.getMetaName(); 866 String bkFilename = sf.getBlockName(privateKey); 867 868 ZipEntry sfFile = new ZipEntry(sfFilename); 869 ZipEntry bkFile = new ZipEntry(bkFilename); 870 871 long time = System.currentTimeMillis(); 872 sfFile.setTime(time); 873 bkFile.setTime(time); 874 875 // signature file 876 zos.putNextEntry(sfFile); 877 sf.write(zos); 878 879 if (handler != null) { 880 if (zipFile.getEntry(sfFilename) != null) { 881 handler.accept("updating", sfFilename); 882 } else { 883 handler.accept("adding", sfFilename); 884 } 885 } 886 887 // signature block file 888 zos.putNextEntry(bkFile); 889 zos.write(block); 890 891 if (handler != null) { 892 if (zipFile.getEntry(bkFilename) != null) { 893 handler.accept("updating", bkFilename); 894 } else { 895 handler.accept("adding", bkFilename); 896 } 897 } 898 899 // Write out all other META-INF files that we stored in the 900 // vector 901 for (int i = 0; i < mfFiles.size(); i++) { 902 ZipEntry ze = mfFiles.elementAt(i); 903 if (!ze.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME) 904 && !ze.getName().equalsIgnoreCase(sfFilename) 905 && !ze.getName().equalsIgnoreCase(bkFilename)) { 906 if (ze.getName().startsWith(SignatureFile 907 .getBaseSignatureFilesName(signerName)) 908 && SignatureFileVerifier.isBlockOrSF(ze.getName())) { 909 if (handler != null) { 910 handler.accept("updating", ze.getName()); 911 } 912 continue; 913 } 914 if (handler != null) { 915 if (manifest.getAttributes(ze.getName()) != null) { 916 handler.accept("signing", ze.getName()); 917 } else if (!ze.isDirectory()) { 918 handler.accept("adding", ze.getName()); 919 } 920 } 921 writeEntry(zipFile, zos, ze); 922 } 923 } 924 925 // Write out all other files 926 for (Enumeration<? extends ZipEntry> enum_ = zipFile.entries(); 927 enum_.hasMoreElements(); ) { 928 ZipEntry ze = enum_.nextElement(); 929 930 if (!ze.getName().startsWith(META_INF)) { 931 if (handler != null) { 932 if (manifest.getAttributes(ze.getName()) != null) { 933 handler.accept("signing", ze.getName()); 934 } else { 935 handler.accept("adding", ze.getName()); 936 } 937 } 938 writeEntry(zipFile, zos, ze); 939 } 940 } 941 zipFile.close(); 942 zos.close(); 943 } 944 945 private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze) 946 throws IOException { 947 ZipEntry ze2 = new ZipEntry(ze.getName()); 948 ze2.setMethod(ze.getMethod()); 949 ze2.setTime(ze.getTime()); 950 ze2.setComment(ze.getComment()); 951 ze2.setExtra(ze.getExtra()); 952 int extraAttrs = JUZFA.getExtraAttributes(ze); 953 System.out.println("extraAttrs:" + extraAttrs); 954 if (!extraAttrsDetected && extraAttrs != -1) { 955 extraAttrsDetected = true; 956 Event.report(Event.ReporterCategory.ZIPFILEATTRS, "detected"); 957 } 958 JUZFA.setExtraAttributes(ze2, extraAttrs); 959 if (ze.getMethod() == ZipEntry.STORED) { 960 ze2.setSize(ze.getSize()); 961 ze2.setCrc(ze.getCrc()); 962 } 963 os.putNextEntry(ze2); 964 writeBytes(zf, ze, os); 965 } 966 967 private void writeBytes 968 (ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException { 969 try (InputStream is = zf.getInputStream(ze)) { 970 is.transferTo(os); 971 } 972 } 973 974 private void updateDigests(ZipEntry ze, ZipFile zf, 975 MessageDigest[] digests, 976 Manifest mf) throws IOException { 977 Attributes attrs = mf.getAttributes(ze.getName()); 978 String[] base64Digests = getDigests(ze, zf, digests); 979 980 for (int i = 0; i < digests.length; i++) { 981 // The entry name to be written into attrs 982 String name = null; 983 try { 984 // Find if the digest already exists. An algorithm could have 985 // different names. For example, last time it was SHA, and this 986 // time it's SHA-1. 987 AlgorithmId aid = AlgorithmId.get(digests[i].getAlgorithm()); 988 for (Object key : attrs.keySet()) { 989 if (key instanceof Attributes.Name) { 990 String n = key.toString(); 991 if (n.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) { 992 String tmp = n.substring(0, n.length() - 7); 993 if (AlgorithmId.get(tmp).equals(aid)) { 994 name = n; 995 break; 996 } 997 } 998 } 999 } 1000 } catch (NoSuchAlgorithmException nsae) { 1001 // Ignored. Writing new digest entry. 1002 } 1003 1004 if (name == null) { 1005 name = digests[i].getAlgorithm() + "-Digest"; 1006 } 1007 attrs.putValue(name, base64Digests[i]); 1008 } 1009 } 1010 1011 private Attributes getDigestAttributes( 1012 ZipEntry ze, ZipFile zf, MessageDigest[] digests) 1013 throws IOException { 1014 1015 String[] base64Digests = getDigests(ze, zf, digests); 1016 Attributes attrs = new Attributes(); 1017 1018 for (int i = 0; i < digests.length; i++) { 1019 attrs.putValue(digests[i].getAlgorithm() + "-Digest", 1020 base64Digests[i]); 1021 } 1022 return attrs; 1023 } 1024 1025 /* 1026 * Returns manifest entry from given jar file, or null if given jar file 1027 * does not have a manifest entry. 1028 */ 1029 private ZipEntry getManifestFile(ZipFile zf) { 1030 ZipEntry ze = zf.getEntry(JarFile.MANIFEST_NAME); 1031 if (ze == null) { 1032 // Check all entries for matching name 1033 Enumeration<? extends ZipEntry> enum_ = zf.entries(); 1034 while (enum_.hasMoreElements() && ze == null) { 1035 ze = enum_.nextElement(); 1036 if (!JarFile.MANIFEST_NAME.equalsIgnoreCase 1037 (ze.getName())) { 1038 ze = null; 1039 } 1040 } 1041 } 1042 return ze; 1043 } 1044 1045 private String[] getDigests( 1046 ZipEntry ze, ZipFile zf, MessageDigest[] digests) 1047 throws IOException { 1048 1049 int n, i; 1050 try (InputStream is = zf.getInputStream(ze)) { 1051 long left = ze.getSize(); 1052 byte[] buffer = new byte[8192]; 1053 while ((left > 0) 1054 && (n = is.read(buffer, 0, buffer.length)) != -1) { 1055 for (i = 0; i < digests.length; i++) { 1056 digests[i].update(buffer, 0, n); 1057 } 1058 left -= n; 1059 } 1060 } 1061 1062 // complete the digests 1063 String[] base64Digests = new String[digests.length]; 1064 for (i = 0; i < digests.length; i++) { 1065 base64Digests[i] = Base64.getEncoder() 1066 .encodeToString(digests[i].digest()); 1067 } 1068 return base64Digests; 1069 } 1070 1071 /* 1072 * Try to load the specified signing mechanism. 1073 * The URL class loader is used. 1074 */ 1075 @SuppressWarnings("removal") 1076 private ContentSigner loadSigningMechanism(String signerClassName, 1077 String signerClassPath) { 1078 1079 // If there is no signerClassPath provided, search from here 1080 if (signerClassPath == null) { 1081 signerClassPath = "."; 1082 } 1083 1084 // construct class loader 1085 String cpString; // make sure env.class.path defaults to dot 1086 1087 // do prepends to get correct ordering 1088 cpString = PathList.appendPath( 1089 System.getProperty("env.class.path"), null); 1090 cpString = PathList.appendPath( 1091 System.getProperty("java.class.path"), cpString); 1092 cpString = PathList.appendPath(signerClassPath, cpString); 1093 URL[] urls = PathList.pathToURLs(cpString); 1094 ClassLoader appClassLoader = new URLClassLoader(urls); 1095 1096 try { 1097 // attempt to find signer 1098 Class<?> signerClass = appClassLoader.loadClass(signerClassName); 1099 Object signer = signerClass.getDeclaredConstructor().newInstance(); 1100 return (ContentSigner) signer; 1101 } catch (ClassNotFoundException|InstantiationException| 1102 IllegalAccessException|ClassCastException| 1103 NoSuchMethodException| InvocationTargetException e) { 1104 throw new IllegalArgumentException( 1105 "Invalid altSigner or altSignerPath", e); 1106 } 1107 } 1108 1109 static class SignatureFile { 1110 1111 /** 1112 * SignatureFile 1113 */ 1114 Manifest sf; 1115 1116 /** 1117 * .SF base name 1118 */ 1119 String baseName; 1120 1121 public SignatureFile(MessageDigest digests[], 1122 Manifest mf, 1123 ManifestDigester md, 1124 String baseName, 1125 boolean signManifest) { 1126 1127 this.baseName = baseName; 1128 1129 String version = System.getProperty("java.version"); 1130 String javaVendor = System.getProperty("java.vendor"); 1131 1132 sf = new Manifest(); 1133 Attributes mattr = sf.getMainAttributes(); 1134 1135 mattr.putValue(Attributes.Name.SIGNATURE_VERSION.toString(), "1.0"); 1136 mattr.putValue("Created-By", version + " (" + javaVendor + ")"); 1137 1138 if (signManifest) { 1139 for (MessageDigest digest: digests) { 1140 mattr.putValue(digest.getAlgorithm() + "-Digest-Manifest", 1141 Base64.getEncoder().encodeToString( 1142 md.manifestDigest(digest))); 1143 } 1144 } 1145 1146 // create digest of the manifest main attributes 1147 ManifestDigester.Entry mde = md.getMainAttsEntry(false); 1148 if (mde != null) { 1149 for (MessageDigest digest : digests) { 1150 mattr.putValue(digest.getAlgorithm() + "-Digest-" + 1151 ManifestDigester.MF_MAIN_ATTRS, 1152 Base64.getEncoder().encodeToString(mde.digest(digest))); 1153 } 1154 } else { 1155 throw new IllegalStateException 1156 ("ManifestDigester failed to create " + 1157 "Manifest-Main-Attribute entry"); 1158 } 1159 1160 // go through the manifest entries and create the digests 1161 Map<String, Attributes> entries = sf.getEntries(); 1162 for (String name: mf.getEntries().keySet()) { 1163 mde = md.get(name, false); 1164 if (mde != null) { 1165 Attributes attr = new Attributes(); 1166 for (MessageDigest digest: digests) { 1167 attr.putValue(digest.getAlgorithm() + "-Digest", 1168 Base64.getEncoder().encodeToString( 1169 mde.digest(digest))); 1170 } 1171 entries.put(name, attr); 1172 } 1173 } 1174 } 1175 1176 // Write .SF file 1177 public void write(OutputStream out) throws IOException { 1178 sf.write(out); 1179 } 1180 1181 private static String getBaseSignatureFilesName(String baseName) { 1182 return "META-INF/" + baseName + "."; 1183 } 1184 1185 // get .SF file name 1186 public String getMetaName() { 1187 return getBaseSignatureFilesName(baseName) + "SF"; 1188 } 1189 1190 // get .DSA (or .DSA, .EC) file name 1191 public String getBlockName(PrivateKey privateKey) { 1192 String keyAlgorithm = privateKey.getAlgorithm(); 1193 return getBaseSignatureFilesName(baseName) + keyAlgorithm; 1194 } 1195 1196 // Generates the PKCS#7 content of block file 1197 @SuppressWarnings("removal") 1198 public byte[] generateBlock(ContentSignerParameters params, 1199 boolean externalSF, 1200 ContentSigner signingMechanism) 1201 throws NoSuchAlgorithmException, 1202 IOException, CertificateException { 1203 1204 if (signingMechanism == null) { 1205 signingMechanism = new TimestampedSigner(); 1206 } 1207 return signingMechanism.generateSignedData( 1208 params, 1209 externalSF, 1210 params.getTimestampingAuthority() != null 1211 || params.getTimestampingAuthorityCertificate() != null); 1212 } 1213 } 1214 1215 @SuppressWarnings("removal") 1216 class JarSignerParameters implements ContentSignerParameters { 1217 1218 private String[] args; 1219 private URI tsa; 1220 private byte[] signature; 1221 private String signatureAlgorithm; 1222 private X509Certificate[] signerCertificateChain; 1223 private byte[] content; 1224 private ZipFile source; 1225 private String tSAPolicyID; 1226 private String tSADigestAlg; 1227 1228 JarSignerParameters(String[] args, URI tsa, 1229 String tSAPolicyID, String tSADigestAlg, 1230 byte[] signature, String signatureAlgorithm, 1231 X509Certificate[] signerCertificateChain, 1232 byte[] content, ZipFile source) { 1233 1234 Objects.requireNonNull(signature); 1235 Objects.requireNonNull(signatureAlgorithm); 1236 Objects.requireNonNull(signerCertificateChain); 1237 1238 this.args = args; 1239 this.tsa = tsa; 1240 this.tSAPolicyID = tSAPolicyID; 1241 this.tSADigestAlg = tSADigestAlg; 1242 this.signature = signature; 1243 this.signatureAlgorithm = signatureAlgorithm; 1244 this.signerCertificateChain = signerCertificateChain; 1245 this.content = content; 1246 this.source = source; 1247 } 1248 1249 public String[] getCommandLine() { 1250 return args; 1251 } 1252 1253 public URI getTimestampingAuthority() { 1254 return tsa; 1255 } 1256 1257 public X509Certificate getTimestampingAuthorityCertificate() { 1258 // We don't use this param. Always provide tsaURI. 1259 return null; 1260 } 1261 1262 public String getTSAPolicyID() { 1263 return tSAPolicyID; 1264 } 1265 1266 public String getTSADigestAlg() { 1267 return tSADigestAlg; 1268 } 1269 1270 public byte[] getSignature() { 1271 return signature; 1272 } 1273 1274 public String getSignatureAlgorithm() { 1275 return signatureAlgorithm; 1276 } 1277 1278 public X509Certificate[] getSignerCertificateChain() { 1279 return signerCertificateChain; 1280 } 1281 1282 public byte[] getContent() { 1283 return content; 1284 } 1285 1286 public ZipFile getSource() { 1287 return source; 1288 } 1289 } 1290 }