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 1.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 1.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 */ 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 */ 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}/../technotes/guides/security/StandardNames.html#MessageDigest"> 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 */ 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}/../technotes/guides/security/StandardNames.html#MessageDigest"> 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 */ 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}/../technotes/guides/security/StandardNames.html#Signature"> 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 */ 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}/../technotes/guides/security/StandardNames.html#Signature"> 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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.3. 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 */ 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 */ 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 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 * 551 * @param file the file to sign. 552 * @param os the output stream. 553 * @throws JarSignerException if the signing fails. 554 */ 555 public void sign(ZipFile file, OutputStream os) { 556 try { 557 sign0(Objects.requireNonNull(file), 558 Objects.requireNonNull(os)); 559 } catch (SocketTimeoutException | CertificateException e) { 560 // CertificateException is thrown when the received cert from TSA 561 // has no id-kp-timeStamping in its Extended Key Usages extension. 562 throw new JarSignerException("Error applying timestamp", e); 563 } catch (IOException ioe) { 564 throw new JarSignerException("I/O error", ioe); 565 } catch (NoSuchAlgorithmException | InvalidKeyException e) { 566 throw new JarSignerException("Error in signer materials", e); 567 } catch (SignatureException se) { 568 throw new JarSignerException("Error creating signature", se); 569 } 570 } 571 572 /** 573 * Returns the digest algorithm for this {@code JarSigner}. 574 * <p> 575 * The return value is never null. 576 * 577 * @return the digest algorithm. 578 */ 579 public String getDigestAlgorithm() { 580 return digestalg[0]; 581 } 582 583 /** 584 * Returns the signature algorithm for this {@code JarSigner}. 585 * <p> 586 * The return value is never null. 587 * 588 * @return the signature algorithm. 589 */ 590 public String getSignatureAlgorithm() { 591 return sigalg; 592 } 593 594 /** 595 * Returns the URI of the Time Stamping Authority (TSA). 596 * 597 * @return the URI of the TSA. 598 */ 599 public URI getTsa() { 600 return tsaUrl; 601 } 602 603 /** 604 * Returns the signer name of this {@code JarSigner}. 605 * <p> 606 * The return value is never null. 607 * 608 * @return the signer name. 609 */ 610 public String getSignerName() { 611 return signerName; 612 } 613 614 /** 615 * Returns the value of an additional implementation-specific property 616 * indicated by the specified key. If a property is not set but has a 617 * default value, the default value will be returned. 618 * 619 * @implNote See {@link JarSigner.Builder#setProperty} for a list of 620 * properties this implementation supports. All property names are 621 * case-insensitive. 622 * 623 * @param key the name of the property. 624 * @return the value for the property. 625 * @throws UnsupportedOperationException if the key is not supported 626 * by this implementation. 627 */ 628 public String getProperty(String key) { 629 Objects.requireNonNull(key); 630 switch (key.toLowerCase(Locale.US)) { 631 case "tsadigestalg": 632 return tSADigestAlg; 633 case "tsapolicyid": 634 return tSAPolicyID; 635 case "internalsf": 636 return Boolean.toString(!externalSF); 637 case "sectionsonly": 638 return Boolean.toString(!signManifest); 639 case "altsignerpath": 640 return altSignerPath; 641 case "altsigner": 642 return altSigner; 643 default: 644 throw new UnsupportedOperationException( 645 "Unsupported key " + key); 646 } 647 } 648 649 private void sign0(ZipFile zipFile, OutputStream os) 650 throws IOException, CertificateException, NoSuchAlgorithmException, 651 SignatureException, InvalidKeyException { 652 MessageDigest[] digests; 653 try { 654 digests = new MessageDigest[digestalg.length]; 655 for (int i = 0; i < digestalg.length; i++) { 656 if (digestProvider == null) { 657 digests[i] = MessageDigest.getInstance(digestalg[i]); 658 } else { 659 digests[i] = MessageDigest.getInstance( 660 digestalg[i], digestProvider); 661 } 662 } 663 } catch (NoSuchAlgorithmException asae) { 664 // Should not happen. User provided alg were checked, and default 665 // alg should always be available. 666 throw new AssertionError(asae); 667 } 668 669 PrintStream ps = new PrintStream(os); 670 ZipOutputStream zos = new ZipOutputStream(ps); 671 672 Manifest manifest = new Manifest(); 673 Map<String, Attributes> mfEntries = manifest.getEntries(); 674 675 // The Attributes of manifest before updating 676 Attributes oldAttr = null; 677 678 boolean mfModified = false; 679 boolean mfCreated = false; 680 byte[] mfRawBytes = null; 681 682 // Check if manifest exists 683 ZipEntry mfFile; 684 if ((mfFile = getManifestFile(zipFile)) != null) { 685 // Manifest exists. Read its raw bytes. 686 mfRawBytes = zipFile.getInputStream(mfFile).readAllBytes(); 687 manifest.read(new ByteArrayInputStream(mfRawBytes)); 688 oldAttr = (Attributes) (manifest.getMainAttributes().clone()); 689 } else { 690 // Create new manifest 691 Attributes mattr = manifest.getMainAttributes(); 692 mattr.putValue(Attributes.Name.MANIFEST_VERSION.toString(), 693 "1.0"); 694 String javaVendor = System.getProperty("java.vendor"); 695 String jdkVersion = System.getProperty("java.version"); 696 mattr.putValue("Created-By", jdkVersion + " (" + javaVendor 697 + ")"); 698 mfFile = new ZipEntry(JarFile.MANIFEST_NAME); 699 mfCreated = true; 700 } 701 702 /* 703 * For each entry in jar 704 * (except for signature-related META-INF entries), 705 * do the following: 706 * 707 * - if entry is not contained in manifest, add it to manifest; 708 * - if entry is contained in manifest, calculate its hash and 709 * compare it with the one in the manifest; if they are 710 * different, replace the hash in the manifest with the newly 711 * generated one. (This may invalidate existing signatures!) 712 */ 713 Vector<ZipEntry> mfFiles = new Vector<>(); 714 715 boolean wasSigned = false; 716 717 for (Enumeration<? extends ZipEntry> enum_ = zipFile.entries(); 718 enum_.hasMoreElements(); ) { 719 ZipEntry ze = enum_.nextElement(); 720 721 if (ze.getName().startsWith(META_INF)) { 722 // Store META-INF files in vector, so they can be written 723 // out first 724 mfFiles.addElement(ze); 725 726 if (SignatureFileVerifier.isBlockOrSF( 727 ze.getName().toUpperCase(Locale.ENGLISH))) { 728 wasSigned = true; 729 } 730 731 if (SignatureFileVerifier.isSigningRelated(ze.getName())) { 732 // ignore signature-related and manifest files 733 continue; 734 } 735 } 736 737 if (manifest.getAttributes(ze.getName()) != null) { 738 // jar entry is contained in manifest, check and 739 // possibly update its digest attributes 740 if (updateDigests(ze, zipFile, digests, 741 manifest)) { 742 mfModified = true; 743 } 744 } else if (!ze.isDirectory()) { 745 // Add entry to manifest 746 Attributes attrs = getDigestAttributes(ze, zipFile, digests); 747 mfEntries.put(ze.getName(), attrs); 748 mfModified = true; 749 } 750 } 751 752 // Recalculate the manifest raw bytes if necessary 753 if (mfModified) { 754 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 755 manifest.write(baos); 756 if (wasSigned) { 757 byte[] newBytes = baos.toByteArray(); 758 if (mfRawBytes != null 759 && oldAttr.equals(manifest.getMainAttributes())) { 760 761 /* 762 * Note: 763 * 764 * The Attributes object is based on HashMap and can handle 765 * continuation columns. Therefore, even if the contents are 766 * not changed (in a Map view), the bytes that it write() 767 * may be different from the original bytes that it read() 768 * from. Since the signature on the main attributes is based 769 * on raw bytes, we must retain the exact bytes. 770 */ 771 772 int newPos = findHeaderEnd(newBytes); 773 int oldPos = findHeaderEnd(mfRawBytes); 774 775 if (newPos == oldPos) { 776 System.arraycopy(mfRawBytes, 0, newBytes, 0, oldPos); 777 } else { 778 // cat oldHead newTail > newBytes 779 byte[] lastBytes = new byte[oldPos + 780 newBytes.length - newPos]; 781 System.arraycopy(mfRawBytes, 0, lastBytes, 0, oldPos); 782 System.arraycopy(newBytes, newPos, lastBytes, oldPos, 783 newBytes.length - newPos); 784 newBytes = lastBytes; 785 } 786 } 787 mfRawBytes = newBytes; 788 } else { 789 mfRawBytes = baos.toByteArray(); 790 } 791 } 792 793 // Write out the manifest 794 if (mfModified) { 795 // manifest file has new length 796 mfFile = new ZipEntry(JarFile.MANIFEST_NAME); 797 } 798 if (handler != null) { 799 if (mfCreated) { 800 handler.accept("adding", mfFile.getName()); 801 } else if (mfModified) { 802 handler.accept("updating", mfFile.getName()); 803 } 804 } 805 806 zos.putNextEntry(mfFile); 807 zos.write(mfRawBytes); 808 809 // Calculate SignatureFile (".SF") and SignatureBlockFile 810 ManifestDigester manDig = new ManifestDigester(mfRawBytes); 811 SignatureFile sf = new SignatureFile(digests, manifest, manDig, 812 signerName, signManifest); 813 814 byte[] block; 815 816 Signature signer; 817 if (sigProvider == null ) { 818 signer = Signature.getInstance(sigalg); 819 } else { 820 signer = Signature.getInstance(sigalg, sigProvider); 821 } 822 signer.initSign(privateKey); 823 824 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 825 sf.write(baos); 826 827 byte[] content = baos.toByteArray(); 828 829 signer.update(content); 830 byte[] signature = signer.sign(); 831 832 @SuppressWarnings("deprecation") 833 ContentSigner signingMechanism = null; 834 if (altSigner != null) { 835 signingMechanism = loadSigningMechanism(altSigner, 836 altSignerPath); 837 } 838 839 @SuppressWarnings("deprecation") 840 ContentSignerParameters params = 841 new JarSignerParameters(null, tsaUrl, tSAPolicyID, 842 tSADigestAlg, signature, 843 signer.getAlgorithm(), certChain, content, zipFile); 844 block = sf.generateBlock(params, externalSF, signingMechanism); 845 846 String sfFilename = sf.getMetaName(); 847 String bkFilename = sf.getBlockName(privateKey); 848 849 ZipEntry sfFile = new ZipEntry(sfFilename); 850 ZipEntry bkFile = new ZipEntry(bkFilename); 851 852 long time = System.currentTimeMillis(); 853 sfFile.setTime(time); 854 bkFile.setTime(time); 855 856 // signature file 857 zos.putNextEntry(sfFile); 858 sf.write(zos); 859 860 if (handler != null) { 861 if (zipFile.getEntry(sfFilename) != null) { 862 handler.accept("updating", sfFilename); 863 } else { 864 handler.accept("adding", sfFilename); 865 } 866 } 867 868 // signature block file 869 zos.putNextEntry(bkFile); 870 zos.write(block); 871 872 if (handler != null) { 873 if (zipFile.getEntry(bkFilename) != null) { 874 handler.accept("updating", bkFilename); 875 } else { 876 handler.accept("adding", bkFilename); 877 } 878 } 879 880 // Write out all other META-INF files that we stored in the 881 // vector 882 for (int i = 0; i < mfFiles.size(); i++) { 883 ZipEntry ze = mfFiles.elementAt(i); 884 if (!ze.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME) 885 && !ze.getName().equalsIgnoreCase(sfFilename) 886 && !ze.getName().equalsIgnoreCase(bkFilename)) { 887 if (handler != null) { 888 if (manifest.getAttributes(ze.getName()) != null) { 889 handler.accept("signing", ze.getName()); 890 } else if (!ze.isDirectory()) { 891 handler.accept("adding", ze.getName()); 892 } 893 } 894 writeEntry(zipFile, zos, ze); 895 } 896 } 897 898 // Write out all other files 899 for (Enumeration<? extends ZipEntry> enum_ = zipFile.entries(); 900 enum_.hasMoreElements(); ) { 901 ZipEntry ze = enum_.nextElement(); 902 903 if (!ze.getName().startsWith(META_INF)) { 904 if (handler != null) { 905 if (manifest.getAttributes(ze.getName()) != null) { 906 handler.accept("signing", ze.getName()); 907 } else { 908 handler.accept("adding", ze.getName()); 909 } 910 } 911 writeEntry(zipFile, zos, ze); 912 } 913 } 914 zipFile.close(); 915 zos.close(); 916 } 917 918 private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze) 919 throws IOException { 920 ZipEntry ze2 = new ZipEntry(ze.getName()); 921 ze2.setMethod(ze.getMethod()); 922 ze2.setTime(ze.getTime()); 923 ze2.setComment(ze.getComment()); 924 ze2.setExtra(ze.getExtra()); 925 if (ze.getMethod() == ZipEntry.STORED) { 926 ze2.setSize(ze.getSize()); 927 ze2.setCrc(ze.getCrc()); 928 } 929 os.putNextEntry(ze2); 930 writeBytes(zf, ze, os); 931 } 932 933 private void writeBytes 934 (ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException { 935 try (InputStream is = zf.getInputStream(ze)) { 936 is.transferTo(os); 937 } 938 } 939 940 private boolean updateDigests(ZipEntry ze, ZipFile zf, 941 MessageDigest[] digests, 942 Manifest mf) throws IOException { 943 boolean update = false; 944 945 Attributes attrs = mf.getAttributes(ze.getName()); 946 String[] base64Digests = getDigests(ze, zf, digests); 947 948 for (int i = 0; i < digests.length; i++) { 949 // The entry name to be written into attrs 950 String name = null; 951 try { 952 // Find if the digest already exists. An algorithm could have 953 // different names. For example, last time it was SHA, and this 954 // time it's SHA-1. 955 AlgorithmId aid = AlgorithmId.get(digests[i].getAlgorithm()); 956 for (Object key : attrs.keySet()) { 957 if (key instanceof Attributes.Name) { 958 String n = key.toString(); 959 if (n.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) { 960 String tmp = n.substring(0, n.length() - 7); 961 if (AlgorithmId.get(tmp).equals(aid)) { 962 name = n; 963 break; 964 } 965 } 966 } 967 } 968 } catch (NoSuchAlgorithmException nsae) { 969 // Ignored. Writing new digest entry. 970 } 971 972 if (name == null) { 973 name = digests[i].getAlgorithm() + "-Digest"; 974 attrs.putValue(name, base64Digests[i]); 975 update = true; 976 } else { 977 // compare digests, and replace the one in the manifest 978 // if they are different 979 String mfDigest = attrs.getValue(name); 980 if (!mfDigest.equalsIgnoreCase(base64Digests[i])) { 981 attrs.putValue(name, base64Digests[i]); 982 update = true; 983 } 984 } 985 } 986 return update; 987 } 988 989 private Attributes getDigestAttributes( 990 ZipEntry ze, ZipFile zf, MessageDigest[] digests) 991 throws IOException { 992 993 String[] base64Digests = getDigests(ze, zf, digests); 994 Attributes attrs = new Attributes(); 995 996 for (int i = 0; i < digests.length; i++) { 997 attrs.putValue(digests[i].getAlgorithm() + "-Digest", 998 base64Digests[i]); 999 } 1000 return attrs; 1001 } 1002 1003 /* 1004 * Returns manifest entry from given jar file, or null if given jar file 1005 * does not have a manifest entry. 1006 */ 1007 private ZipEntry getManifestFile(ZipFile zf) { 1008 ZipEntry ze = zf.getEntry(JarFile.MANIFEST_NAME); 1009 if (ze == null) { 1010 // Check all entries for matching name 1011 Enumeration<? extends ZipEntry> enum_ = zf.entries(); 1012 while (enum_.hasMoreElements() && ze == null) { 1013 ze = enum_.nextElement(); 1014 if (!JarFile.MANIFEST_NAME.equalsIgnoreCase 1015 (ze.getName())) { 1016 ze = null; 1017 } 1018 } 1019 } 1020 return ze; 1021 } 1022 1023 private String[] getDigests( 1024 ZipEntry ze, ZipFile zf, MessageDigest[] digests) 1025 throws IOException { 1026 1027 int n, i; 1028 try (InputStream is = zf.getInputStream(ze)) { 1029 long left = ze.getSize(); 1030 byte[] buffer = new byte[8192]; 1031 while ((left > 0) 1032 && (n = is.read(buffer, 0, buffer.length)) != -1) { 1033 for (i = 0; i < digests.length; i++) { 1034 digests[i].update(buffer, 0, n); 1035 } 1036 left -= n; 1037 } 1038 } 1039 1040 // complete the digests 1041 String[] base64Digests = new String[digests.length]; 1042 for (i = 0; i < digests.length; i++) { 1043 base64Digests[i] = Base64.getEncoder() 1044 .encodeToString(digests[i].digest()); 1045 } 1046 return base64Digests; 1047 } 1048 1049 @SuppressWarnings("fallthrough") 1050 private int findHeaderEnd(byte[] bs) { 1051 // Initial state true to deal with empty header 1052 boolean newline = true; // just met a newline 1053 int len = bs.length; 1054 for (int i = 0; i < len; i++) { 1055 switch (bs[i]) { 1056 case '\r': 1057 if (i < len - 1 && bs[i + 1] == '\n') i++; 1058 // fallthrough 1059 case '\n': 1060 if (newline) return i + 1; //+1 to get length 1061 newline = true; 1062 break; 1063 default: 1064 newline = false; 1065 } 1066 } 1067 // If header end is not found, it means the MANIFEST.MF has only 1068 // the main attributes section and it does not end with 2 newlines. 1069 // Returns the whole length so that it can be completely replaced. 1070 return len; 1071 } 1072 1073 /* 1074 * Try to load the specified signing mechanism. 1075 * The URL class loader is used. 1076 */ 1077 @SuppressWarnings("deprecation") 1078 private ContentSigner loadSigningMechanism(String signerClassName, 1079 String signerClassPath) { 1080 1081 // construct class loader 1082 String cpString; // make sure env.class.path defaults to dot 1083 1084 // do prepends to get correct ordering 1085 cpString = PathList.appendPath( 1086 System.getProperty("env.class.path"), null); 1087 cpString = PathList.appendPath( 1088 System.getProperty("java.class.path"), cpString); 1089 cpString = PathList.appendPath(signerClassPath, cpString); 1090 URL[] urls = PathList.pathToURLs(cpString); 1091 ClassLoader appClassLoader = new URLClassLoader(urls); 1092 1093 try { 1094 // attempt to find signer 1095 Class<?> signerClass = appClassLoader.loadClass(signerClassName); 1096 Object signer = signerClass.newInstance(); 1097 return (ContentSigner) signer; 1098 } catch (ClassNotFoundException|InstantiationException| 1099 IllegalAccessException|ClassCastException e) { 1100 throw new IllegalArgumentException( 1101 "Invalid altSigner or altSignerPath", e); 1102 } 1103 } 1104 1105 static class SignatureFile { 1106 1107 /** 1108 * SignatureFile 1109 */ 1110 Manifest sf; 1111 1112 /** 1113 * .SF base name 1114 */ 1115 String baseName; 1116 1117 public SignatureFile(MessageDigest digests[], 1118 Manifest mf, 1119 ManifestDigester md, 1120 String baseName, 1121 boolean signManifest) { 1122 1123 this.baseName = baseName; 1124 1125 String version = System.getProperty("java.version"); 1126 String javaVendor = System.getProperty("java.vendor"); 1127 1128 sf = new Manifest(); 1129 Attributes mattr = sf.getMainAttributes(); 1130 1131 mattr.putValue(Attributes.Name.SIGNATURE_VERSION.toString(), "1.0"); 1132 mattr.putValue("Created-By", version + " (" + javaVendor + ")"); 1133 1134 if (signManifest) { 1135 for (MessageDigest digest: digests) { 1136 mattr.putValue(digest.getAlgorithm() + "-Digest-Manifest", 1137 Base64.getEncoder().encodeToString( 1138 md.manifestDigest(digest))); 1139 } 1140 } 1141 1142 // create digest of the manifest main attributes 1143 ManifestDigester.Entry mde = 1144 md.get(ManifestDigester.MF_MAIN_ATTRS, false); 1145 if (mde != null) { 1146 for (MessageDigest digest: digests) { 1147 mattr.putValue(digest.getAlgorithm() + 1148 "-Digest-" + ManifestDigester.MF_MAIN_ATTRS, 1149 Base64.getEncoder().encodeToString( 1150 mde.digest(digest))); 1151 } 1152 } else { 1153 throw new IllegalStateException 1154 ("ManifestDigester failed to create " + 1155 "Manifest-Main-Attribute entry"); 1156 } 1157 1158 // go through the manifest entries and create the digests 1159 Map<String, Attributes> entries = sf.getEntries(); 1160 for (String name: mf.getEntries().keySet()) { 1161 mde = md.get(name, false); 1162 if (mde != null) { 1163 Attributes attr = new Attributes(); 1164 for (MessageDigest digest: digests) { 1165 attr.putValue(digest.getAlgorithm() + "-Digest", 1166 Base64.getEncoder().encodeToString( 1167 mde.digest(digest))); 1168 } 1169 entries.put(name, attr); 1170 } 1171 } 1172 } 1173 1174 // Write .SF file 1175 public void write(OutputStream out) throws IOException { 1176 sf.write(out); 1177 } 1178 1179 // get .SF file name 1180 public String getMetaName() { 1181 return "META-INF/" + baseName + ".SF"; 1182 } 1183 1184 // get .DSA (or .DSA, .EC) file name 1185 public String getBlockName(PrivateKey privateKey) { 1186 String keyAlgorithm = privateKey.getAlgorithm(); 1187 return "META-INF/" + baseName + "." + keyAlgorithm; 1188 } 1189 1190 // Generates the PKCS#7 content of block file 1191 @SuppressWarnings("deprecation") 1192 public byte[] generateBlock(ContentSignerParameters params, 1193 boolean externalSF, 1194 ContentSigner signingMechanism) 1195 throws NoSuchAlgorithmException, 1196 IOException, CertificateException { 1197 1198 if (signingMechanism == null) { 1199 signingMechanism = new TimestampedSigner(); 1200 } 1201 return signingMechanism.generateSignedData( 1202 params, 1203 externalSF, 1204 params.getTimestampingAuthority() != null 1205 || params.getTimestampingAuthorityCertificate() != null); 1206 } 1207 } 1208 1209 @SuppressWarnings("deprecation") 1210 class JarSignerParameters implements ContentSignerParameters { 1211 1212 private String[] args; 1213 private URI tsa; 1214 private byte[] signature; 1215 private String signatureAlgorithm; 1216 private X509Certificate[] signerCertificateChain; 1217 private byte[] content; 1218 private ZipFile source; 1219 private String tSAPolicyID; 1220 private String tSADigestAlg; 1221 1222 JarSignerParameters(String[] args, URI tsa, 1223 String tSAPolicyID, String tSADigestAlg, 1224 byte[] signature, String signatureAlgorithm, 1225 X509Certificate[] signerCertificateChain, 1226 byte[] content, ZipFile source) { 1227 1228 Objects.requireNonNull(signature); 1229 Objects.requireNonNull(signatureAlgorithm); 1230 Objects.requireNonNull(signerCertificateChain); 1231 1232 this.args = args; 1233 this.tsa = tsa; 1234 this.tSAPolicyID = tSAPolicyID; 1235 this.tSADigestAlg = tSADigestAlg; 1236 this.signature = signature; 1237 this.signatureAlgorithm = signatureAlgorithm; 1238 this.signerCertificateChain = signerCertificateChain; 1239 this.content = content; 1240 this.source = source; 1241 } 1242 1243 public String[] getCommandLine() { 1244 return args; 1245 } 1246 1247 public URI getTimestampingAuthority() { 1248 return tsa; 1249 } 1250 1251 public X509Certificate getTimestampingAuthorityCertificate() { 1252 // We don't use this param. Always provide tsaURI. 1253 return null; 1254 } 1255 1256 public String getTSAPolicyID() { 1257 return tSAPolicyID; 1258 } 1259 1260 public String getTSADigestAlg() { 1261 return tSADigestAlg; 1262 } 1263 1264 public byte[] getSignature() { 1265 return signature; 1266 } 1267 1268 public String getSignatureAlgorithm() { 1269 return signatureAlgorithm; 1270 } 1271 1272 public X509Certificate[] getSignerCertificateChain() { 1273 return signerCertificateChain; 1274 } 1275 1276 public byte[] getContent() { 1277 return content; 1278 } 1279 1280 public ZipFile getSource() { 1281 return source; 1282 } 1283 } 1284 }