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