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 }