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