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