8056174: New APIs for jar signing
authorweijun
Fri, 20 Nov 2015 08:34:04 +0800
changeset 33872 94e3836950ec
parent 33871 73d87430102d
child 33873 32ba74411aba
8056174: New APIs for jar signing Reviewed-by: mullan
jdk/src/java.base/share/classes/sun/security/x509/AlgorithmId.java
jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java
jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSignerException.java
jdk/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java
jdk/test/jdk/security/jarsigner/Function.java
jdk/test/jdk/security/jarsigner/Spec.java
jdk/test/sun/security/tools/jarsigner/Options.java
--- a/jdk/src/java.base/share/classes/sun/security/x509/AlgorithmId.java	Thu Nov 19 12:57:59 2015 -0800
+++ b/jdk/src/java.base/share/classes/sun/security/x509/AlgorithmId.java	Fri Nov 20 08:34:04 2015 +0800
@@ -977,4 +977,69 @@
         }
         return null;
     }
+
+    /**
+     * Checks if a signature algorithm matches a key algorithm, i.e. a
+     * signature can be initialized with a key.
+     *
+     * @param kAlg must not be null
+     * @param sAlg must not be null
+     * @throws IllegalArgumentException if they do not match
+     */
+    public static void checkKeyAndSigAlgMatch(String kAlg, String sAlg) {
+        String sAlgUp = sAlg.toUpperCase(Locale.US);
+        if ((sAlgUp.endsWith("WITHRSA") && !kAlg.equalsIgnoreCase("RSA")) ||
+                (sAlgUp.endsWith("WITHECDSA") && !kAlg.equalsIgnoreCase("EC")) ||
+                (sAlgUp.endsWith("WITHDSA") && !kAlg.equalsIgnoreCase("DSA"))) {
+            throw new IllegalArgumentException(
+                    "key algorithm not compatible with signature algorithm");
+        }
+    }
+
+    /**
+     * Returns the default signature algorithm for a private key. The digest
+     * part might evolve with time. Remember to update the spec of
+     * {@link jdk.security.jarsigner.JarSigner.Builder#getDefaultSignatureAlgorithm(PrivateKey)}
+     * if updated.
+     *
+     * @param k cannot be null
+     * @return the default alg, might be null if unsupported
+     */
+    public static String getDefaultSigAlgForKey(PrivateKey k) {
+        switch (k.getAlgorithm().toUpperCase()) {
+            case "EC":
+                return ecStrength(KeyUtil.getKeySize(k))
+                    + "withECDSA";
+            case "DSA":
+                return ifcFfcStrength(KeyUtil.getKeySize(k))
+                    + "withDSA";
+            case "RSA":
+                return ifcFfcStrength(KeyUtil.getKeySize(k))
+                    + "withRSA";
+            default:
+                return null;
+        }
+    }
+
+    // Values from SP800-57 part 1 rev 3 tables 2 and three
+    private static String ecStrength (int bitLength) {
+        if (bitLength >= 512) { // 256 bits of strength
+            return "SHA512";
+        } else if (bitLength >= 384) {  // 192 bits of strength
+            return "SHA384";
+        } else { // 128 bits of strength and less
+            return "SHA256";
+        }
+    }
+
+    // same values for RSA and DSA
+    private static String ifcFfcStrength (int bitLength) {
+        if (bitLength > 7680) { // 256 bits
+            return "SHA512";
+        } else if (bitLength > 3072) {  // 192 bits
+            return "SHA384";
+        } else  { // 128 bits and less
+            return "SHA256";
+        }
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java	Fri Nov 20 08:34:04 2015 +0800
@@ -0,0 +1,1286 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.security.jarsigner;
+
+import com.sun.jarsigner.ContentSigner;
+import com.sun.jarsigner.ContentSignerParameters;
+import sun.security.tools.PathList;
+import sun.security.tools.jarsigner.TimestampedSigner;
+import sun.security.util.ManifestDigester;
+import sun.security.util.SignatureFileVerifier;
+import sun.security.x509.AlgorithmId;
+
+import java.io.*;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.*;
+import java.security.cert.CertPath;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * An immutable utility class to sign a jar file.
+ * <p>
+ * A caller creates a {@code JarSigner.Builder} object, (optionally) sets
+ * some parameters, and calls {@link JarSigner.Builder#build build} to create
+ * a {@code JarSigner} object. This {@code JarSigner} object can then
+ * be used to sign a jar file.
+ * <p>
+ * Unless otherwise stated, calling a method of {@code JarSigner} or
+ * {@code JarSigner.Builder} with a null argument will throw
+ * a {@link NullPointerException}.
+ * <p>
+ * Example:
+ * <pre>
+ * JarSigner signer = new JarSigner.Builder(key, certPath)
+ *         .digestAlgorithm("SHA-1")
+ *         .signatureAlgorithm("SHA1withDSA")
+ *         .build();
+ * try (ZipFile in = new ZipFile(inputFile);
+ *         FileOutputStream out = new FileOutputStream(outputFile)) {
+ *     signer.sign(in, out);
+ * }
+ * </pre>
+ *
+ * @since 1.9
+ */
+@jdk.Exported
+public final class JarSigner {
+
+    /**
+     * A mutable builder class that can create an immutable {@code JarSigner}
+     * from various signing-related parameters.
+     *
+     * @since 1.9
+     */
+    @jdk.Exported
+    public static class Builder {
+
+        // Signer materials:
+        final PrivateKey privateKey;
+        final X509Certificate[] certChain;
+
+        // JarSigner options:
+        // Support multiple digestalg internally. Can be null, but not empty
+        String[] digestalg;
+        String sigalg;
+        // Precisely should be one provider for each digestalg, maybe later
+        Provider digestProvider;
+        Provider sigProvider;
+        URI tsaUrl;
+        String signerName;
+        BiConsumer<String,String> handler;
+
+        // Implementation-specific properties:
+        String tSAPolicyID;
+        String tSADigestAlg;
+        boolean signManifest = true;
+        boolean externalSF = true;
+        String altSignerPath;
+        String altSigner;
+
+        /**
+         * Creates a {@code JarSigner.Builder} object with
+         * a {@link KeyStore.PrivateKeyEntry} object.
+         *
+         * @param entry the {@link KeyStore.PrivateKeyEntry} of the signer.
+         */
+        public Builder(KeyStore.PrivateKeyEntry entry) {
+            this.privateKey = entry.getPrivateKey();
+            try {
+                // called internally, no need to clone
+                Certificate[] certs = entry.getCertificateChain();
+                this.certChain = Arrays.copyOf(certs, certs.length,
+                        X509Certificate[].class);
+            } catch (ArrayStoreException ase) {
+                // Wrong type, not X509Certificate. Won't document.
+                throw new IllegalArgumentException(
+                        "Entry does not contain X509Certificate");
+            }
+        }
+
+        /**
+         * Creates a {@code JarSigner.Builder} object with a private key and
+         * a certification path.
+         *
+         * @param privateKey the private key of the signer.
+         * @param certPath the certification path of the signer.
+         * @throws IllegalArgumentException if {@code certPath} is empty, or
+         *      the {@code privateKey} algorithm does not match the algorithm
+         *      of the {@code PublicKey} in the end entity certificate
+         *      (the first certificate in {@code certPath}).
+         */
+        public Builder(PrivateKey privateKey, CertPath certPath) {
+            List<? extends Certificate> certs = certPath.getCertificates();
+            if (certs.isEmpty()) {
+                throw new IllegalArgumentException("certPath cannot be empty");
+            }
+            if (!privateKey.getAlgorithm().equals
+                    (certs.get(0).getPublicKey().getAlgorithm())) {
+                throw new IllegalArgumentException
+                        ("private key algorithm does not match " +
+                                "algorithm of public key in end entity " +
+                                "certificate (the 1st in certPath)");
+            }
+            this.privateKey = privateKey;
+            try {
+                this.certChain = certs.toArray(new X509Certificate[certs.size()]);
+            } catch (ArrayStoreException ase) {
+                // Wrong type, not X509Certificate.
+                throw new IllegalArgumentException(
+                        "Entry does not contain X509Certificate");
+            }
+        }
+
+        /**
+         * Sets the digest algorithm. If no digest algorithm is specified,
+         * the default algorithm returned by {@link #getDefaultDigestAlgorithm}
+         * will be used.
+         *
+         * @param algorithm the standard name of the algorithm. See
+         *      the {@code MessageDigest} section in the <a href=
+         *      "{@docRoot}/../technotes/guides/security/StandardNames.html#MessageDigest">
+         *      Java Cryptography Architecture Standard Algorithm Name
+         *      Documentation</a> for information about standard algorithm names.
+         * @return the {@code JarSigner.Builder} itself.
+         * @throws NoSuchAlgorithmException if {@code algorithm} is not available.
+         */
+        public Builder digestAlgorithm(String algorithm) throws NoSuchAlgorithmException {
+            MessageDigest.getInstance(Objects.requireNonNull(algorithm));
+            this.digestalg = new String[]{algorithm};
+            this.digestProvider = null;
+            return this;
+        }
+
+        /**
+         * Sets the digest algorithm from the specified provider.
+         * If no digest algorithm is specified, the default algorithm
+         * returned by {@link #getDefaultDigestAlgorithm} will be used.
+         *
+         * @param algorithm the standard name of the algorithm. See
+         *      the {@code MessageDigest} section in the <a href=
+         *      "{@docRoot}/../technotes/guides/security/StandardNames.html#MessageDigest">
+         *      Java Cryptography Architecture Standard Algorithm Name
+         *      Documentation</a> for information about standard algorithm names.
+         * @param provider the provider.
+         * @return the {@code JarSigner.Builder} itself.
+         * @throws NoSuchAlgorithmException if {@code algorithm} is not
+         *      available in the specified provider.
+         */
+        public Builder digestAlgorithm(String algorithm, Provider provider)
+                throws NoSuchAlgorithmException {
+            MessageDigest.getInstance(
+                    Objects.requireNonNull(algorithm),
+                    Objects.requireNonNull(provider));
+            this.digestalg = new String[]{algorithm};
+            this.digestProvider = provider;
+            return this;
+        }
+
+        /**
+         * Sets the signature algorithm. If no signature algorithm
+         * is specified, the default signature algorithm returned by
+         * {@link #getDefaultSignatureAlgorithm} for the private key
+         * will be used.
+         *
+         * @param algorithm the standard name of the algorithm. See
+         *      the {@code Signature} section in the <a href=
+         *      "{@docRoot}/../technotes/guides/security/StandardNames.html#Signature">
+         *      Java Cryptography Architecture Standard Algorithm Name
+         *      Documentation</a> for information about standard algorithm names.
+         * @return the {@code JarSigner.Builder} itself.
+         * @throws NoSuchAlgorithmException if {@code algorithm} is not available.
+         * @throws IllegalArgumentException if {@code algorithm} is not
+         *      compatible with the algorithm of the signer's private key.
+         */
+        public Builder signatureAlgorithm(String algorithm)
+                throws NoSuchAlgorithmException {
+            // Check availability
+            Signature.getInstance(Objects.requireNonNull(algorithm));
+            AlgorithmId.checkKeyAndSigAlgMatch(
+                    privateKey.getAlgorithm(), algorithm);
+            this.sigalg = algorithm;
+            this.sigProvider = null;
+            return this;
+        }
+
+        /**
+         * Sets the signature algorithm from the specified provider. If no
+         * signature algorithm is specified, the default signature algorithm
+         * returned by {@link #getDefaultSignatureAlgorithm} for the private
+         * key will be used.
+         *
+         * @param algorithm the standard name of the algorithm. See
+         *      the {@code Signature} section in the <a href=
+         *      "{@docRoot}/../technotes/guides/security/StandardNames.html#Signature">
+         *      Java Cryptography Architecture Standard Algorithm Name
+         *      Documentation</a> for information about standard algorithm names.
+         * @param provider  the provider.
+         * @return the {@code JarSigner.Builder} itself.
+         * @throws NoSuchAlgorithmException if {@code algorithm} is not
+         *      available in the specified provider.
+         * @throws IllegalArgumentException if {@code algorithm} is not
+         *      compatible with the algorithm of the signer's private key.
+         */
+        public Builder signatureAlgorithm(String algorithm, Provider provider)
+                throws NoSuchAlgorithmException {
+            // Check availability
+            Signature.getInstance(
+                    Objects.requireNonNull(algorithm),
+                    Objects.requireNonNull(provider));
+            AlgorithmId.checkKeyAndSigAlgMatch(
+                    privateKey.getAlgorithm(), algorithm);
+            this.sigalg = algorithm;
+            this.sigProvider = provider;
+            return this;
+        }
+
+        /**
+         * Sets the URI of the Time Stamping Authority (TSA).
+         *
+         * @param uri the URI.
+         * @return the {@code JarSigner.Builder} itself.
+         */
+        public Builder tsa(URI uri) {
+            this.tsaUrl = Objects.requireNonNull(uri);
+            return this;
+        }
+
+        /**
+         * Sets the signer name. The name will be used as the base name for
+         * the signature files. All lowercase characters will be converted to
+         * uppercase for signature file names. If a signer name is not
+         * specified, the string "SIGNER" will be used.
+         *
+         * @param name the signer name.
+         * @return the {@code JarSigner.Builder} itself.
+         * @throws IllegalArgumentException if {@code name} is empty or has
+         *      a size bigger than 8, or it contains characters not from the
+         *      set "a-zA-Z0-9_-".
+         */
+        public Builder signerName(String name) {
+            if (name.isEmpty() || name.length() > 8) {
+                throw new IllegalArgumentException("Name too long");
+            }
+
+            name = name.toUpperCase(Locale.ENGLISH);
+
+            for (int j = 0; j < name.length(); j++) {
+                char c = name.charAt(j);
+                if (!
+                        ((c >= 'A' && c <= 'Z') ||
+                                (c >= '0' && c <= '9') ||
+                                (c == '-') ||
+                                (c == '_'))) {
+                    throw new IllegalArgumentException(
+                            "Invalid characters in name");
+                }
+            }
+            this.signerName = name;
+            return this;
+        }
+
+        /**
+         * Sets en event handler that will be triggered when a {@link JarEntry}
+         * is to be added, signed, or updated during the signing process.
+         * <p>
+         * The handler can be used to display signing progress. The first
+         * argument of the handler can be "adding", "signing", or "updating",
+         * and the second argument is the name of the {@link JarEntry}
+         * being processed.
+         *
+         * @param handler the event handler.
+         * @return the {@code JarSigner.Builder} itself.
+         */
+        public Builder eventHandler(BiConsumer<String,String> handler) {
+            this.handler = Objects.requireNonNull(handler);
+            return this;
+        }
+
+        /**
+         * Sets an additional implementation-specific property indicated by
+         * the specified key.
+         *
+         * @implNote This implementation supports the following properties:
+         * <ul>
+         * <li>"tsaDigestAlg": algorithm of digest data in the timestamping
+         * request. The default value is the same as the result of
+         * {@link #getDefaultDigestAlgorithm}.
+         * <li>"tsaPolicyId": TSAPolicyID for Timestamping Authority.
+         * No default value.
+         * <li>"internalsf": "true" if the .SF file is included inside the
+         * signature block, "false" otherwise. Default "false".
+         * <li>"sectionsonly": "true" if the .SF file only contains the hash
+         * value for each section of the manifest and not for the whole
+         * manifest, "false" otherwise. Default "false".
+         * </ul>
+         * All property names are case-insensitive.
+         *
+         * @param key the name of the property.
+         * @param value the value of the property.
+         * @return the {@code JarSigner.Builder} itself.
+         * @throws UnsupportedOperationException if the key is not supported
+         *      by this implementation.
+         * @throws IllegalArgumentException if the value is not accepted as
+         *      a legal value for this key.
+         */
+        public Builder setProperty(String key, String value) {
+            Objects.requireNonNull(key);
+            Objects.requireNonNull(value);
+            switch (key.toLowerCase(Locale.US)) {
+                case "tsadigestalg":
+                    try {
+                        MessageDigest.getInstance(value);
+                    } catch (NoSuchAlgorithmException nsae) {
+                        throw new IllegalArgumentException(
+                                "Invalid tsadigestalg", nsae);
+                    }
+                    this.tSADigestAlg = value;
+                    break;
+                case "tsapolicyid":
+                    this.tSAPolicyID = value;
+                    break;
+                case "internalsf":
+                    switch (value) {
+                        case "true":
+                            externalSF = false;
+                            break;
+                        case "false":
+                            externalSF = true;
+                            break;
+                        default:
+                            throw new IllegalArgumentException(
+                                "Invalid internalsf value");
+                    }
+                    break;
+                case "sectionsonly":
+                    switch (value) {
+                        case "true":
+                            signManifest = false;
+                            break;
+                        case "false":
+                            signManifest = true;
+                            break;
+                        default:
+                            throw new IllegalArgumentException(
+                                "Invalid signManifest value");
+                    }
+                    break;
+                case "altsignerpath":
+                    altSignerPath = value;
+                    break;
+                case "altsigner":
+                    altSigner = value;
+                    break;
+                default:
+                    throw new UnsupportedOperationException(
+                            "Unsupported key " + key);
+            }
+            return this;
+        }
+
+        /**
+         * Gets the default digest algorithm.
+         *
+         * @implNote This implementation returns "SHA-256". The value may
+         * change in the future.
+         *
+         * @return the default digest algorithm.
+         */
+        public static String getDefaultDigestAlgorithm() {
+            return "SHA-256";
+        }
+
+        /**
+         * Gets the default signature algorithm for a private key.
+         * For example, SHA256withRSA for a 2048-bit RSA key, and
+         * SHA384withECDSA for a 384-bit EC key.
+         *
+         * @implNote This implementation makes use of comparable strengths
+         * as defined in Tables 2 and 3 of NIST SP 800-57 Part 1-Rev.3.
+         * Specifically, if a DSA or RSA key with a key size greater than 7680
+         * bits, or an EC key with a key size greater than or equal to 512 bits,
+         * SHA-512 will be used as the hash function for the signature.
+         * If a DSA or RSA key has a key size greater than 3072 bits, or an
+         * EC key has a key size greater than or equal to 384 bits, SHA-384 will
+         * be used. Otherwise, SHA-256 will be used. The value may
+         * change in the future.
+         *
+         * @param key the private key.
+         * @return the default signature algorithm. Returns null if a default
+         *      signature algorithm cannot be found. In this case,
+         *      {@link #signatureAlgorithm} must be called to specify a
+         *      signature algorithm. Otherwise, the {@link #build} method
+         *      will throw an {@link IllegalArgumentException}.
+         */
+        public static String getDefaultSignatureAlgorithm(PrivateKey key) {
+            return AlgorithmId.getDefaultSigAlgForKey(Objects.requireNonNull(key));
+        }
+
+        /**
+         * Builds a {@code JarSigner} object from the parameters set by the
+         * setter methods.
+         * <p>
+         * This method does not modify internal state of this {@code Builder}
+         * object and can be called multiple times to generate multiple
+         * {@code JarSigner} objects. After this method is called, calling
+         * any method on this {@code Builder} will have no effect on
+         * the newly built {@code JarSigner} object.
+         *
+         * @return the {@code JarSigner} object.
+         * @throws IllegalArgumentException if a signature algorithm is not
+         *      set and cannot be derived from the private key using the
+         *      {@link #getDefaultSignatureAlgorithm} method.
+         */
+        public JarSigner build() {
+            return new JarSigner(this);
+        }
+    }
+
+    private static final String META_INF = "META-INF/";
+
+    // All fields in Builder are duplicated here as final. Those not
+    // provided but has a default value will be filled with default value.
+
+    // Precisely, a final array field can still be modified if only
+    // reference is copied, no clone is done because we are concerned about
+    // casual change instead of malicious attack.
+
+    // Signer materials:
+    private final PrivateKey privateKey;
+    private final X509Certificate[] certChain;
+
+    // JarSigner options:
+    private final String[] digestalg;
+    private final String sigalg;
+    private final Provider digestProvider;
+    private final Provider sigProvider;
+    private final URI tsaUrl;
+    private final String signerName;
+    private final BiConsumer<String,String> handler;
+
+    // Implementation-specific properties:
+    private final String tSAPolicyID;
+    private final String tSADigestAlg;
+    private final boolean signManifest; // "sign" the whole manifest
+    private final boolean externalSF; // leave the .SF out of the PKCS7 block
+    private final String altSignerPath;
+    private final String altSigner;
+
+    private JarSigner(JarSigner.Builder builder) {
+
+        this.privateKey = builder.privateKey;
+        this.certChain = builder.certChain;
+        if (builder.digestalg != null) {
+            // No need to clone because builder only accepts one alg now
+            this.digestalg = builder.digestalg;
+        } else {
+            this.digestalg = new String[] {
+                    Builder.getDefaultDigestAlgorithm() };
+        }
+        this.digestProvider = builder.digestProvider;
+        if (builder.sigalg != null) {
+            this.sigalg = builder.sigalg;
+        } else {
+            this.sigalg = JarSigner.Builder
+                    .getDefaultSignatureAlgorithm(privateKey);
+            if (this.sigalg == null) {
+                throw new IllegalArgumentException(
+                        "No signature alg for " + privateKey.getAlgorithm());
+            }
+        }
+        this.sigProvider = builder.sigProvider;
+        this.tsaUrl = builder.tsaUrl;
+
+        if (builder.signerName == null) {
+            this.signerName = "SIGNER";
+        } else {
+            this.signerName = builder.signerName;
+        }
+        this.handler = builder.handler;
+
+        if (builder.tSADigestAlg != null) {
+            this.tSADigestAlg = builder.tSADigestAlg;
+        } else {
+            this.tSADigestAlg = Builder.getDefaultDigestAlgorithm();
+        }
+        this.tSAPolicyID = builder.tSAPolicyID;
+        this.signManifest = builder.signManifest;
+        this.externalSF = builder.externalSF;
+        this.altSigner = builder.altSigner;
+        this.altSignerPath = builder.altSignerPath;
+    }
+
+    /**
+     * Signs a file into an {@link OutputStream}. This method will not close
+     * {@code file} or {@code os}.
+     *
+     * @param file the file to sign.
+     * @param os the output stream.
+     * @throws JarSignerException if the signing fails.
+     */
+    public void sign(ZipFile file, OutputStream os) {
+        try {
+            sign0(Objects.requireNonNull(file),
+                    Objects.requireNonNull(os));
+        } catch (SocketTimeoutException | CertificateException e) {
+            // CertificateException is thrown when the received cert from TSA
+            // has no id-kp-timeStamping in its Extended Key Usages extension.
+            throw new JarSignerException("Error applying timestamp", e);
+        } catch (IOException ioe) {
+            throw new JarSignerException("I/O error", ioe);
+        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+            throw new JarSignerException("Error in signer materials", e);
+        } catch (SignatureException se) {
+            throw new JarSignerException("Error creating signature", se);
+        }
+    }
+
+    /**
+     * Returns the digest algorithm for this {@code JarSigner}.
+     * <p>
+     * The return value is never null.
+     *
+     * @return the digest algorithm.
+     */
+    public String getDigestAlgorithm() {
+        return digestalg[0];
+    }
+
+    /**
+     * Returns the signature algorithm for this {@code JarSigner}.
+     * <p>
+     * The return value is never null.
+     *
+     * @return the signature algorithm.
+     */
+    public String getSignatureAlgorithm() {
+        return sigalg;
+    }
+
+    /**
+     * Returns the URI of the Time Stamping Authority (TSA).
+     *
+     * @return the URI of the TSA.
+     */
+    public URI getTsa() {
+        return tsaUrl;
+    }
+
+    /**
+     * Returns the signer name of this {@code JarSigner}.
+     * <p>
+     * The return value is never null.
+     *
+     * @return the signer name.
+     */
+    public String getSignerName() {
+        return signerName;
+    }
+
+    /**
+     * Returns the value of an additional implementation-specific property
+     * indicated by the specified key. If a property is not set but has a
+     * default value, the default value will be returned.
+     *
+     * @implNote See {@link JarSigner.Builder#setProperty} for a list of
+     * properties this implementation supports. All property names are
+     * case-insensitive.
+     *
+     * @param key the name of the property.
+     * @return the value for the property.
+     * @throws UnsupportedOperationException if the key is not supported
+     *      by this implementation.
+     */
+    public String getProperty(String key) {
+        Objects.requireNonNull(key);
+        switch (key.toLowerCase(Locale.US)) {
+            case "tsadigestalg":
+                return tSADigestAlg;
+            case "tsapolicyid":
+                return tSAPolicyID;
+            case "internalsf":
+                return Boolean.toString(!externalSF);
+            case "sectionsonly":
+                return Boolean.toString(!signManifest);
+            case "altsignerpath":
+                return altSignerPath;
+            case "altsigner":
+                return altSigner;
+            default:
+                throw new UnsupportedOperationException(
+                        "Unsupported key " + key);
+        }
+    }
+
+    private void sign0(ZipFile zipFile, OutputStream os)
+            throws IOException, CertificateException, NoSuchAlgorithmException,
+            SignatureException, InvalidKeyException {
+        MessageDigest[] digests;
+        try {
+            digests = new MessageDigest[digestalg.length];
+            for (int i = 0; i < digestalg.length; i++) {
+                if (digestProvider == null) {
+                    digests[i] = MessageDigest.getInstance(digestalg[i]);
+                } else {
+                    digests[i] = MessageDigest.getInstance(
+                            digestalg[i], digestProvider);
+                }
+            }
+        } catch (NoSuchAlgorithmException asae) {
+            // Should not happen. User provided alg were checked, and default
+            // alg should always be available.
+            throw new AssertionError(asae);
+        }
+
+        PrintStream ps = new PrintStream(os);
+        ZipOutputStream zos = new ZipOutputStream(ps);
+
+        Manifest manifest = new Manifest();
+        Map<String, Attributes> mfEntries = manifest.getEntries();
+
+        // The Attributes of manifest before updating
+        Attributes oldAttr = null;
+
+        boolean mfModified = false;
+        boolean mfCreated = false;
+        byte[] mfRawBytes = null;
+
+        // Check if manifest exists
+        ZipEntry mfFile;
+        if ((mfFile = getManifestFile(zipFile)) != null) {
+            // Manifest exists. Read its raw bytes.
+            mfRawBytes = zipFile.getInputStream(mfFile).readAllBytes();
+            manifest.read(new ByteArrayInputStream(mfRawBytes));
+            oldAttr = (Attributes) (manifest.getMainAttributes().clone());
+        } else {
+            // Create new manifest
+            Attributes mattr = manifest.getMainAttributes();
+            mattr.putValue(Attributes.Name.MANIFEST_VERSION.toString(),
+                    "1.0");
+            String javaVendor = System.getProperty("java.vendor");
+            String jdkVersion = System.getProperty("java.version");
+            mattr.putValue("Created-By", jdkVersion + " (" + javaVendor
+                    + ")");
+            mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
+            mfCreated = true;
+        }
+
+        /*
+         * For each entry in jar
+         * (except for signature-related META-INF entries),
+         * do the following:
+         *
+         * - if entry is not contained in manifest, add it to manifest;
+         * - if entry is contained in manifest, calculate its hash and
+         *   compare it with the one in the manifest; if they are
+         *   different, replace the hash in the manifest with the newly
+         *   generated one. (This may invalidate existing signatures!)
+         */
+        Vector<ZipEntry> mfFiles = new Vector<>();
+
+        boolean wasSigned = false;
+
+        for (Enumeration<? extends ZipEntry> enum_ = zipFile.entries();
+             enum_.hasMoreElements(); ) {
+            ZipEntry ze = enum_.nextElement();
+
+            if (ze.getName().startsWith(META_INF)) {
+                // Store META-INF files in vector, so they can be written
+                // out first
+                mfFiles.addElement(ze);
+
+                if (SignatureFileVerifier.isBlockOrSF(
+                        ze.getName().toUpperCase(Locale.ENGLISH))) {
+                    wasSigned = true;
+                }
+
+                if (SignatureFileVerifier.isSigningRelated(ze.getName())) {
+                    // ignore signature-related and manifest files
+                    continue;
+                }
+            }
+
+            if (manifest.getAttributes(ze.getName()) != null) {
+                // jar entry is contained in manifest, check and
+                // possibly update its digest attributes
+                if (updateDigests(ze, zipFile, digests,
+                        manifest)) {
+                    mfModified = true;
+                }
+            } else if (!ze.isDirectory()) {
+                // Add entry to manifest
+                Attributes attrs = getDigestAttributes(ze, zipFile, digests);
+                mfEntries.put(ze.getName(), attrs);
+                mfModified = true;
+            }
+        }
+
+        // Recalculate the manifest raw bytes if necessary
+        if (mfModified) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            manifest.write(baos);
+            if (wasSigned) {
+                byte[] newBytes = baos.toByteArray();
+                if (mfRawBytes != null
+                        && oldAttr.equals(manifest.getMainAttributes())) {
+
+                    /*
+                     * Note:
+                     *
+                     * The Attributes object is based on HashMap and can handle
+                     * continuation columns. Therefore, even if the contents are
+                     * not changed (in a Map view), the bytes that it write()
+                     * may be different from the original bytes that it read()
+                     * from. Since the signature on the main attributes is based
+                     * on raw bytes, we must retain the exact bytes.
+                     */
+
+                    int newPos = findHeaderEnd(newBytes);
+                    int oldPos = findHeaderEnd(mfRawBytes);
+
+                    if (newPos == oldPos) {
+                        System.arraycopy(mfRawBytes, 0, newBytes, 0, oldPos);
+                    } else {
+                        // cat oldHead newTail > newBytes
+                        byte[] lastBytes = new byte[oldPos +
+                                newBytes.length - newPos];
+                        System.arraycopy(mfRawBytes, 0, lastBytes, 0, oldPos);
+                        System.arraycopy(newBytes, newPos, lastBytes, oldPos,
+                                newBytes.length - newPos);
+                        newBytes = lastBytes;
+                    }
+                }
+                mfRawBytes = newBytes;
+            } else {
+                mfRawBytes = baos.toByteArray();
+            }
+        }
+
+        // Write out the manifest
+        if (mfModified) {
+            // manifest file has new length
+            mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
+        }
+        if (handler != null) {
+            if (mfCreated) {
+                handler.accept("adding", mfFile.getName());
+            } else if (mfModified) {
+                handler.accept("updating", mfFile.getName());
+            }
+        }
+
+        zos.putNextEntry(mfFile);
+        zos.write(mfRawBytes);
+
+        // Calculate SignatureFile (".SF") and SignatureBlockFile
+        ManifestDigester manDig = new ManifestDigester(mfRawBytes);
+        SignatureFile sf = new SignatureFile(digests, manifest, manDig,
+                signerName, signManifest);
+
+        byte[] block;
+
+        Signature signer;
+        if (sigProvider == null ) {
+            signer = Signature.getInstance(sigalg);
+        } else {
+            signer = Signature.getInstance(sigalg, sigProvider);
+        }
+        signer.initSign(privateKey);
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        sf.write(baos);
+
+        byte[] content = baos.toByteArray();
+
+        signer.update(content);
+        byte[] signature = signer.sign();
+
+        @SuppressWarnings("deprecation")
+        ContentSigner signingMechanism = null;
+        if (altSigner != null) {
+            signingMechanism = loadSigningMechanism(altSigner,
+                    altSignerPath);
+        }
+
+        @SuppressWarnings("deprecation")
+        ContentSignerParameters params =
+                new JarSignerParameters(null, tsaUrl, tSAPolicyID,
+                        tSADigestAlg, signature,
+                        signer.getAlgorithm(), certChain, content, zipFile);
+        block = sf.generateBlock(params, externalSF, signingMechanism);
+
+        String sfFilename = sf.getMetaName();
+        String bkFilename = sf.getBlockName(privateKey);
+
+        ZipEntry sfFile = new ZipEntry(sfFilename);
+        ZipEntry bkFile = new ZipEntry(bkFilename);
+
+        long time = System.currentTimeMillis();
+        sfFile.setTime(time);
+        bkFile.setTime(time);
+
+        // signature file
+        zos.putNextEntry(sfFile);
+        sf.write(zos);
+
+        if (handler != null) {
+            if (zipFile.getEntry(sfFilename) != null) {
+                handler.accept("updating", sfFilename);
+            } else {
+                handler.accept("adding", sfFilename);
+            }
+        }
+
+        // signature block file
+        zos.putNextEntry(bkFile);
+        zos.write(block);
+
+        if (handler != null) {
+            if (zipFile.getEntry(bkFilename) != null) {
+                handler.accept("updating", bkFilename);
+            } else {
+                handler.accept("adding", bkFilename);
+            }
+        }
+
+        // Write out all other META-INF files that we stored in the
+        // vector
+        for (int i = 0; i < mfFiles.size(); i++) {
+            ZipEntry ze = mfFiles.elementAt(i);
+            if (!ze.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)
+                    && !ze.getName().equalsIgnoreCase(sfFilename)
+                    && !ze.getName().equalsIgnoreCase(bkFilename)) {
+                if (handler != null) {
+                    if (manifest.getAttributes(ze.getName()) != null) {
+                        handler.accept("signing", ze.getName());
+                    } else if (!ze.isDirectory()) {
+                        handler.accept("adding", ze.getName());
+                    }
+                }
+                writeEntry(zipFile, zos, ze);
+            }
+        }
+
+        // Write out all other files
+        for (Enumeration<? extends ZipEntry> enum_ = zipFile.entries();
+             enum_.hasMoreElements(); ) {
+            ZipEntry ze = enum_.nextElement();
+
+            if (!ze.getName().startsWith(META_INF)) {
+                if (handler != null) {
+                    if (manifest.getAttributes(ze.getName()) != null) {
+                        handler.accept("signing", ze.getName());
+                    } else {
+                        handler.accept("adding", ze.getName());
+                    }
+                }
+                writeEntry(zipFile, zos, ze);
+            }
+        }
+        zipFile.close();
+        zos.close();
+    }
+
+    private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze)
+            throws IOException {
+        ZipEntry ze2 = new ZipEntry(ze.getName());
+        ze2.setMethod(ze.getMethod());
+        ze2.setTime(ze.getTime());
+        ze2.setComment(ze.getComment());
+        ze2.setExtra(ze.getExtra());
+        if (ze.getMethod() == ZipEntry.STORED) {
+            ze2.setSize(ze.getSize());
+            ze2.setCrc(ze.getCrc());
+        }
+        os.putNextEntry(ze2);
+        writeBytes(zf, ze, os);
+    }
+
+    private void writeBytes
+            (ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException {
+        try (InputStream is = zf.getInputStream(ze)) {
+            is.transferTo(os);
+        }
+    }
+
+    private boolean updateDigests(ZipEntry ze, ZipFile zf,
+                                  MessageDigest[] digests,
+                                  Manifest mf) throws IOException {
+        boolean update = false;
+
+        Attributes attrs = mf.getAttributes(ze.getName());
+        String[] base64Digests = getDigests(ze, zf, digests);
+
+        for (int i = 0; i < digests.length; i++) {
+            // The entry name to be written into attrs
+            String name = null;
+            try {
+                // Find if the digest already exists. An algorithm could have
+                // different names. For example, last time it was SHA, and this
+                // time it's SHA-1.
+                AlgorithmId aid = AlgorithmId.get(digests[i].getAlgorithm());
+                for (Object key : attrs.keySet()) {
+                    if (key instanceof Attributes.Name) {
+                        String n = key.toString();
+                        if (n.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {
+                            String tmp = n.substring(0, n.length() - 7);
+                            if (AlgorithmId.get(tmp).equals(aid)) {
+                                name = n;
+                                break;
+                            }
+                        }
+                    }
+                }
+            } catch (NoSuchAlgorithmException nsae) {
+                // Ignored. Writing new digest entry.
+            }
+
+            if (name == null) {
+                name = digests[i].getAlgorithm() + "-Digest";
+                attrs.putValue(name, base64Digests[i]);
+                update = true;
+            } else {
+                // compare digests, and replace the one in the manifest
+                // if they are different
+                String mfDigest = attrs.getValue(name);
+                if (!mfDigest.equalsIgnoreCase(base64Digests[i])) {
+                    attrs.putValue(name, base64Digests[i]);
+                    update = true;
+                }
+            }
+        }
+        return update;
+    }
+
+    private Attributes getDigestAttributes(
+            ZipEntry ze, ZipFile zf, MessageDigest[] digests)
+            throws IOException {
+
+        String[] base64Digests = getDigests(ze, zf, digests);
+        Attributes attrs = new Attributes();
+
+        for (int i = 0; i < digests.length; i++) {
+            attrs.putValue(digests[i].getAlgorithm() + "-Digest",
+                    base64Digests[i]);
+        }
+        return attrs;
+    }
+
+    /*
+     * Returns manifest entry from given jar file, or null if given jar file
+     * does not have a manifest entry.
+     */
+    private ZipEntry getManifestFile(ZipFile zf) {
+        ZipEntry ze = zf.getEntry(JarFile.MANIFEST_NAME);
+        if (ze == null) {
+            // Check all entries for matching name
+            Enumeration<? extends ZipEntry> enum_ = zf.entries();
+            while (enum_.hasMoreElements() && ze == null) {
+                ze = enum_.nextElement();
+                if (!JarFile.MANIFEST_NAME.equalsIgnoreCase
+                        (ze.getName())) {
+                    ze = null;
+                }
+            }
+        }
+        return ze;
+    }
+
+    private String[] getDigests(
+            ZipEntry ze, ZipFile zf, MessageDigest[] digests)
+            throws IOException {
+
+        int n, i;
+        try (InputStream is = zf.getInputStream(ze)) {
+            long left = ze.getSize();
+            byte[] buffer = new byte[8192];
+            while ((left > 0)
+                    && (n = is.read(buffer, 0, buffer.length)) != -1) {
+                for (i = 0; i < digests.length; i++) {
+                    digests[i].update(buffer, 0, n);
+                }
+                left -= n;
+            }
+        }
+
+        // complete the digests
+        String[] base64Digests = new String[digests.length];
+        for (i = 0; i < digests.length; i++) {
+            base64Digests[i] = Base64.getEncoder()
+                    .encodeToString(digests[i].digest());
+        }
+        return base64Digests;
+    }
+
+    @SuppressWarnings("fallthrough")
+    private int findHeaderEnd(byte[] bs) {
+        // Initial state true to deal with empty header
+        boolean newline = true;     // just met a newline
+        int len = bs.length;
+        for (int i = 0; i < len; i++) {
+            switch (bs[i]) {
+                case '\r':
+                    if (i < len - 1 && bs[i + 1] == '\n') i++;
+                    // fallthrough
+                case '\n':
+                    if (newline) return i + 1;    //+1 to get length
+                    newline = true;
+                    break;
+                default:
+                    newline = false;
+            }
+        }
+        // If header end is not found, it means the MANIFEST.MF has only
+        // the main attributes section and it does not end with 2 newlines.
+        // Returns the whole length so that it can be completely replaced.
+        return len;
+    }
+
+    /*
+     * Try to load the specified signing mechanism.
+     * The URL class loader is used.
+     */
+    @SuppressWarnings("deprecation")
+    private ContentSigner loadSigningMechanism(String signerClassName,
+                                               String signerClassPath) {
+
+        // construct class loader
+        String cpString;   // make sure env.class.path defaults to dot
+
+        // do prepends to get correct ordering
+        cpString = PathList.appendPath(
+                System.getProperty("env.class.path"), null);
+        cpString = PathList.appendPath(
+                System.getProperty("java.class.path"), cpString);
+        cpString = PathList.appendPath(signerClassPath, cpString);
+        URL[] urls = PathList.pathToURLs(cpString);
+        ClassLoader appClassLoader = new URLClassLoader(urls);
+
+        try {
+            // attempt to find signer
+            Class<?> signerClass = appClassLoader.loadClass(signerClassName);
+            Object signer = signerClass.newInstance();
+            return (ContentSigner) signer;
+        } catch (ClassNotFoundException|InstantiationException|
+                IllegalAccessException|ClassCastException e) {
+            throw new IllegalArgumentException(
+                    "Invalid altSigner or altSignerPath", e);
+        }
+    }
+
+    static class SignatureFile {
+
+        /**
+         * SignatureFile
+         */
+        Manifest sf;
+
+        /**
+         * .SF base name
+         */
+        String baseName;
+
+        public SignatureFile(MessageDigest digests[],
+                             Manifest mf,
+                             ManifestDigester md,
+                             String baseName,
+                             boolean signManifest) {
+
+            this.baseName = baseName;
+
+            String version = System.getProperty("java.version");
+            String javaVendor = System.getProperty("java.vendor");
+
+            sf = new Manifest();
+            Attributes mattr = sf.getMainAttributes();
+
+            mattr.putValue(Attributes.Name.SIGNATURE_VERSION.toString(), "1.0");
+            mattr.putValue("Created-By", version + " (" + javaVendor + ")");
+
+            if (signManifest) {
+                for (MessageDigest digest: digests) {
+                    mattr.putValue(digest.getAlgorithm() + "-Digest-Manifest",
+                            Base64.getEncoder().encodeToString(
+                                    md.manifestDigest(digest)));
+                }
+            }
+
+            // create digest of the manifest main attributes
+            ManifestDigester.Entry mde =
+                    md.get(ManifestDigester.MF_MAIN_ATTRS, false);
+            if (mde != null) {
+                for (MessageDigest digest: digests) {
+                    mattr.putValue(digest.getAlgorithm() +
+                                    "-Digest-" + ManifestDigester.MF_MAIN_ATTRS,
+                            Base64.getEncoder().encodeToString(
+                                    mde.digest(digest)));
+                }
+            } else {
+                throw new IllegalStateException
+                        ("ManifestDigester failed to create " +
+                                "Manifest-Main-Attribute entry");
+            }
+
+            // go through the manifest entries and create the digests
+            Map<String, Attributes> entries = sf.getEntries();
+            for (String name: mf.getEntries().keySet()) {
+                mde = md.get(name, false);
+                if (mde != null) {
+                    Attributes attr = new Attributes();
+                    for (MessageDigest digest: digests) {
+                        attr.putValue(digest.getAlgorithm() + "-Digest",
+                                Base64.getEncoder().encodeToString(
+                                        mde.digest(digest)));
+                    }
+                    entries.put(name, attr);
+                }
+            }
+        }
+
+        // Write .SF file
+        public void write(OutputStream out) throws IOException {
+            sf.write(out);
+        }
+
+        // get .SF file name
+        public String getMetaName() {
+            return "META-INF/" + baseName + ".SF";
+        }
+
+        // get .DSA (or .DSA, .EC) file name
+        public String getBlockName(PrivateKey privateKey) {
+            String keyAlgorithm = privateKey.getAlgorithm();
+            return "META-INF/" + baseName + "." + keyAlgorithm;
+        }
+
+        // Generates the PKCS#7 content of block file
+        @SuppressWarnings("deprecation")
+        public byte[] generateBlock(ContentSignerParameters params,
+                                    boolean externalSF,
+                                    ContentSigner signingMechanism)
+                throws NoSuchAlgorithmException,
+                       IOException, CertificateException {
+
+            if (signingMechanism == null) {
+                signingMechanism = new TimestampedSigner();
+            }
+            return signingMechanism.generateSignedData(
+                    params,
+                    externalSF,
+                    params.getTimestampingAuthority() != null
+                        || params.getTimestampingAuthorityCertificate() != null);
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    class JarSignerParameters implements ContentSignerParameters {
+
+        private String[] args;
+        private URI tsa;
+        private byte[] signature;
+        private String signatureAlgorithm;
+        private X509Certificate[] signerCertificateChain;
+        private byte[] content;
+        private ZipFile source;
+        private String tSAPolicyID;
+        private String tSADigestAlg;
+
+        JarSignerParameters(String[] args, URI tsa,
+                            String tSAPolicyID, String tSADigestAlg,
+                            byte[] signature, String signatureAlgorithm,
+                            X509Certificate[] signerCertificateChain,
+                            byte[] content, ZipFile source) {
+
+            Objects.requireNonNull(signature);
+            Objects.requireNonNull(signatureAlgorithm);
+            Objects.requireNonNull(signerCertificateChain);
+
+            this.args = args;
+            this.tsa = tsa;
+            this.tSAPolicyID = tSAPolicyID;
+            this.tSADigestAlg = tSADigestAlg;
+            this.signature = signature;
+            this.signatureAlgorithm = signatureAlgorithm;
+            this.signerCertificateChain = signerCertificateChain;
+            this.content = content;
+            this.source = source;
+        }
+
+        public String[] getCommandLine() {
+            return args;
+        }
+
+        public URI getTimestampingAuthority() {
+            return tsa;
+        }
+
+        public X509Certificate getTimestampingAuthorityCertificate() {
+            // We don't use this param. Always provide tsaURI.
+            return null;
+        }
+
+        public String getTSAPolicyID() {
+            return tSAPolicyID;
+        }
+
+        public String getTSADigestAlg() {
+            return tSADigestAlg;
+        }
+
+        public byte[] getSignature() {
+            return signature;
+        }
+
+        public String getSignatureAlgorithm() {
+            return signatureAlgorithm;
+        }
+
+        public X509Certificate[] getSignerCertificateChain() {
+            return signerCertificateChain;
+        }
+
+        public byte[] getContent() {
+            return content;
+        }
+
+        public ZipFile getSource() {
+            return source;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSignerException.java	Fri Nov 20 08:34:04 2015 +0800
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.security.jarsigner;
+
+/**
+ * This exception is thrown when {@link JarSigner#sign} fails.
+ *
+ * @since 1.9
+ */
+@jdk.Exported
+public class JarSignerException extends RuntimeException {
+
+    private static final long serialVersionUID = -4732217075689309530L;
+
+    /**
+     * Constructs a new {@code JarSignerException} with the specified detail
+     * message and cause.
+     * <p>
+     * Note that the detail message associated with
+     * {@code cause} is <i>not</i> automatically incorporated in
+     * this {@code JarSignerException}'s detail message.
+     *
+     * @param message the detail message (which is saved for later retrieval
+     *      by the {@link #getMessage()} method).
+     * @param cause the cause (which is saved for later retrieval by the
+     *      {@link #getCause()} method).  (A {@code null} value is permitted,
+     *      and indicates that the cause is nonexistent or unknown.)
+     */
+    public JarSignerException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
+
--- a/jdk/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java	Thu Nov 19 12:57:59 2015 -0800
+++ b/jdk/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java	Fri Nov 20 08:34:04 2015 +0800
@@ -29,22 +29,16 @@
 import java.util.*;
 import java.util.zip.*;
 import java.util.jar.*;
-import java.math.BigInteger;
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.text.Collator;
 import java.text.MessageFormat;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.security.cert.CertificateException;
 import java.security.*;
-import java.lang.reflect.Constructor;
 
-import com.sun.jarsigner.ContentSigner;
-import com.sun.jarsigner.ContentSignerParameters;
 import java.net.SocketTimeoutException;
 import java.net.URL;
-import java.net.URLClassLoader;
 import java.security.cert.CertPath;
 import java.security.cert.CertPathValidator;
 import java.security.cert.CertificateExpiredException;
@@ -53,11 +47,12 @@
 import java.security.cert.PKIXParameters;
 import java.security.cert.TrustAnchor;
 import java.util.Map.Entry;
+
+import jdk.security.jarsigner.JarSigner;
+import jdk.security.jarsigner.JarSignerException;
 import sun.security.tools.KeyStoreUtil;
-import sun.security.tools.PathList;
 import sun.security.x509.*;
 import sun.security.util.*;
-import java.util.Base64;
 
 
 /**
@@ -88,10 +83,6 @@
         collator.setStrength(Collator.PRIMARY);
     }
 
-    private static final String META_INF = "META-INF/";
-
-    private static final Class<?>[] PARAM_STRING = { String.class };
-
     private static final String NONE = "NONE";
     private static final String P11KEYSTORE = "PKCS11";
 
@@ -133,13 +124,13 @@
     char[] keypass; // private key password
     String sigfile; // name of .SF file
     String sigalg; // name of signature algorithm
-    String digestalg = "SHA-256"; // name of digest algorithm
+    String digestalg; // name of digest algorithm
     String signedjar; // output filename
     String tsaUrl; // location of the Timestamping Authority
     String tsaAlias; // alias for the Timestamping Authority's certificate
     String altCertChain; // file to read alternative cert chain from
     String tSAPolicyID;
-    String tSADigestAlg = "SHA-256";
+    String tSADigestAlg;
     boolean verify = false; // verify the jar
     String verbose = null; // verbose output when signing/verifying
     boolean showcerts = false; // show certs when verifying
@@ -149,9 +140,6 @@
     boolean strict = false;  // treat warnings as error
 
     // read zip entry raw bytes
-    private ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
-    private byte[] buffer = new byte[8192];
-    private ContentSigner signingMechanism = null;
     private String altSignerClass = null;
     private String altSignerClasspath = null;
     private ZipFile zipFile = null;
@@ -216,6 +204,9 @@
                     if ((keystore != null) || (storepass != null)) {
                         System.out.println(rb.getString("jarsigner.error.") +
                                         e.getMessage());
+                        if (debug) {
+                            e.printStackTrace();
+                        }
                         System.exit(1);
                     }
                 }
@@ -229,12 +220,7 @@
                 loadKeyStore(keystore, true);
                 getAliasInfo(alias);
 
-                // load the alternative signing mechanism
-                if (altSignerClass != null) {
-                    signingMechanism = loadSigningMechanism(altSignerClass,
-                        altSignerClasspath);
-                }
-                signJar(jarfile, alias, args);
+                signJar(jarfile, alias);
             }
         } catch (Exception e) {
             System.out.println(rb.getString("jarsigner.error.") + e);
@@ -626,8 +612,7 @@
                 InputStream is = null;
                 try {
                     is = jf.getInputStream(je);
-                    int n;
-                    while ((n = is.read(buffer, 0, buffer.length)) != -1) {
+                    while (is.read(buffer, 0, buffer.length) != -1) {
                         // we just read. this will throw a SecurityException
                         // if  a signature/digest check fails.
                     }
@@ -1035,7 +1020,6 @@
             return cacheForInKS.get(signer);
         }
 
-        boolean found = false;
         int result = 0;
         List<? extends Certificate> certs = signer.getSignerCertPath().getCertificates();
         for (Certificate c : certs) {
@@ -1058,7 +1042,6 @@
                     }
                     if (alias != null) {
                         storeHash.put(c, "(" + alias + ")");
-                        found = true;
                         result |= IN_KEYSTORE;
                     }
                 }
@@ -1090,7 +1073,7 @@
         return output;
     }
 
-    void signJar(String jarName, String alias, String[] args)
+    void signJar(String jarName, String alias)
         throws Exception {
         boolean aliasUsed = false;
         X509Certificate tsaCert = null;
@@ -1110,17 +1093,17 @@
         for (int j = 0; j < sigfile.length(); j++) {
             char c = sigfile.charAt(j);
             if (!
-                ((c>= 'A' && c<= 'Z') ||
-                (c>= '0' && c<= '9') ||
-                (c == '-') ||
-                (c == '_'))) {
+                    ((c>= 'A' && c<= 'Z') ||
+                            (c>= '0' && c<= '9') ||
+                            (c == '-') ||
+                            (c == '_'))) {
                 if (aliasUsed) {
                     // convert illegal characters from the alias to be _'s
                     c = '_';
                 } else {
-                 throw new
-                   RuntimeException(rb.getString
-                        ("signature.filename.must.consist.of.the.following.characters.A.Z.0.9.or."));
+                    throw new
+                            RuntimeException(rb.getString
+                            ("signature.filename.must.consist.of.the.following.characters.A.Z.0.9.or."));
                 }
             }
             tmpSigFile.append(c);
@@ -1149,275 +1132,88 @@
             error(rb.getString("unable.to.create.")+tmpJarName, ioe);
         }
 
-        PrintStream ps = new PrintStream(fos);
-        ZipOutputStream zos = new ZipOutputStream(ps);
-
-        /* First guess at what they might be - we don't xclude RSA ones. */
-        String sfFilename = (META_INF + sigfile + ".SF").toUpperCase(Locale.ENGLISH);
-        String bkFilename = (META_INF + sigfile + ".DSA").toUpperCase(Locale.ENGLISH);
-
-        Manifest manifest = new Manifest();
-        Map<String,Attributes> mfEntries = manifest.getEntries();
-
-        // The Attributes of manifest before updating
-        Attributes oldAttr = null;
-
-        boolean mfModified = false;
-        boolean mfCreated = false;
-        byte[] mfRawBytes = null;
-
-        try {
-            MessageDigest digests[] = { MessageDigest.getInstance(digestalg) };
+        CertPath cp = CertificateFactory.getInstance("X.509")
+                .generateCertPath(Arrays.asList(certChain));
+        JarSigner.Builder builder = new JarSigner.Builder(privateKey, cp);
 
-            // Check if manifest exists
-            ZipEntry mfFile;
-            if ((mfFile = getManifestFile(zipFile)) != null) {
-                // Manifest exists. Read its raw bytes.
-                mfRawBytes = getBytes(zipFile, mfFile);
-                manifest.read(new ByteArrayInputStream(mfRawBytes));
-                oldAttr = (Attributes)(manifest.getMainAttributes().clone());
-            } else {
-                // Create new manifest
-                Attributes mattr = manifest.getMainAttributes();
-                mattr.putValue(Attributes.Name.MANIFEST_VERSION.toString(),
-                               "1.0");
-                String javaVendor = System.getProperty("java.vendor");
-                String jdkVersion = System.getProperty("java.version");
-                mattr.putValue("Created-By", jdkVersion + " (" +javaVendor
-                               + ")");
-                mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
-                mfCreated = true;
-            }
+        if (verbose != null) {
+            builder.eventHandler((action, file) -> {
+                System.out.println(rb.getString("." + action + ".") + file);
+            });
+        }
+
+        if (digestalg != null) {
+            builder.digestAlgorithm(digestalg);
+        }
+        if (sigalg != null) {
+            builder.signatureAlgorithm(sigalg);
+        }
 
-            /*
-             * For each entry in jar
-             * (except for signature-related META-INF entries),
-             * do the following:
-             *
-             * - if entry is not contained in manifest, add it to manifest;
-             * - if entry is contained in manifest, calculate its hash and
-             *   compare it with the one in the manifest; if they are
-             *   different, replace the hash in the manifest with the newly
-             *   generated one. (This may invalidate existing signatures!)
-             */
-            Vector<ZipEntry> mfFiles = new Vector<>();
+        URI tsaURI = null;
 
-            boolean wasSigned = false;
-
-            for (Enumeration<? extends ZipEntry> enum_=zipFile.entries();
-                        enum_.hasMoreElements();) {
-                ZipEntry ze = enum_.nextElement();
-
-                if (ze.getName().startsWith(META_INF)) {
-                    // Store META-INF files in vector, so they can be written
-                    // out first
-                    mfFiles.addElement(ze);
+        if (tsaUrl != null) {
+            tsaURI = new URI(tsaUrl);
+        } else if (tsaAlias != null) {
+            tsaCert = getTsaCert(tsaAlias);
+            tsaURI = TimestampedSigner.getTimestampingURI(tsaCert);
+        }
 
-                    if (SignatureFileVerifier.isBlockOrSF(
-                            ze.getName().toUpperCase(Locale.ENGLISH))) {
-                        wasSigned = true;
-                    }
-
-                    if (signatureRelated(ze.getName())) {
-                        // ignore signature-related and manifest files
-                        continue;
-                    }
-                }
-
-                if (manifest.getAttributes(ze.getName()) != null) {
-                    // jar entry is contained in manifest, check and
-                    // possibly update its digest attributes
-                    if (updateDigests(ze, zipFile, digests,
-                                      manifest) == true) {
-                        mfModified = true;
-                    }
-                } else if (!ze.isDirectory()) {
-                    // Add entry to manifest
-                    Attributes attrs = getDigestAttributes(ze, zipFile,
-                                                           digests);
-                    mfEntries.put(ze.getName(), attrs);
-                    mfModified = true;
+        if (tsaURI != null) {
+            if (verbose != null) {
+                System.out.println(
+                        rb.getString("requesting.a.signature.timestamp"));
+                if (tsaUrl != null) {
+                    System.out.println(rb.getString("TSA.location.") + tsaUrl);
+                } else if (tsaCert != null) {
+                    System.out.println(rb.getString("TSA.certificate.") +
+                            printCert("", tsaCert, false, null, false));
                 }
             }
-
-            // Recalculate the manifest raw bytes if necessary
-            if (mfModified) {
-                ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                manifest.write(baos);
-                if (wasSigned) {
-                    byte[] newBytes = baos.toByteArray();
-                    if (mfRawBytes != null
-                            && oldAttr.equals(manifest.getMainAttributes())) {
-
-                        /*
-                         * Note:
-                         *
-                         * The Attributes object is based on HashMap and can handle
-                         * continuation columns. Therefore, even if the contents are
-                         * not changed (in a Map view), the bytes that it write()
-                         * may be different from the original bytes that it read()
-                         * from. Since the signature on the main attributes is based
-                         * on raw bytes, we must retain the exact bytes.
-                         */
-
-                        int newPos = findHeaderEnd(newBytes);
-                        int oldPos = findHeaderEnd(mfRawBytes);
-
-                        if (newPos == oldPos) {
-                            System.arraycopy(mfRawBytes, 0, newBytes, 0, oldPos);
-                        } else {
-                            // cat oldHead newTail > newBytes
-                            byte[] lastBytes = new byte[oldPos +
-                                    newBytes.length - newPos];
-                            System.arraycopy(mfRawBytes, 0, lastBytes, 0, oldPos);
-                            System.arraycopy(newBytes, newPos, lastBytes, oldPos,
-                                    newBytes.length - newPos);
-                            newBytes = lastBytes;
-                        }
-                    }
-                    mfRawBytes = newBytes;
-                } else {
-                    mfRawBytes = baos.toByteArray();
-                }
+            builder.tsa(tsaURI);
+            if (tSADigestAlg != null) {
+                builder.setProperty("tsaDigestAlg", tSADigestAlg);
             }
 
-            // Write out the manifest
-            if (mfModified) {
-                // manifest file has new length
-                mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
+            if (tSAPolicyID != null) {
+                builder.setProperty("tsaPolicyId", tSAPolicyID);
             }
+        } else {
+            noTimestamp = true;
+        }
+
+        if (altSignerClass != null) {
+            builder.setProperty("altSigner", altSignerClass);
             if (verbose != null) {
-                if (mfCreated) {
-                    System.out.println(rb.getString(".adding.") +
-                                        mfFile.getName());
-                } else if (mfModified) {
-                    System.out.println(rb.getString(".updating.") +
-                                        mfFile.getName());
-                }
+                System.out.println(
+                        rb.getString("using.an.alternative.signing.mechanism"));
             }
-            zos.putNextEntry(mfFile);
-            zos.write(mfRawBytes);
+        }
 
-            // Calculate SignatureFile (".SF") and SignatureBlockFile
-            ManifestDigester manDig = new ManifestDigester(mfRawBytes);
-            SignatureFile sf = new SignatureFile(digests, manifest, manDig,
-                                                 sigfile, signManifest);
+        if (altSignerClasspath != null) {
+            builder.setProperty("altSignerPath", altSignerClasspath);
+        }
 
-            if (tsaAlias != null) {
-                tsaCert = getTsaCert(tsaAlias);
-            }
+        builder.signerName(sigfile);
 
-            if (tsaUrl == null && tsaCert == null) {
-                noTimestamp = true;
-            }
-
-            SignatureFile.Block block = null;
+        builder.setProperty("sectionsOnly", Boolean.toString(!signManifest));
+        builder.setProperty("internalSF", Boolean.toString(!externalSF));
 
-            try {
-                block =
-                    sf.generateBlock(privateKey, sigalg, certChain,
-                        externalSF, tsaUrl, tsaCert, tSAPolicyID, tSADigestAlg,
-                        signingMechanism, args, zipFile);
-            } catch (SocketTimeoutException e) {
+        try {
+            builder.build().sign(zipFile, fos);
+        } catch (JarSignerException e) {
+            Throwable cause = e.getCause();
+            if (cause != null && cause instanceof SocketTimeoutException) {
                 // Provide a helpful message when TSA is beyond a firewall
                 error(rb.getString("unable.to.sign.jar.") +
-                rb.getString("no.response.from.the.Timestamping.Authority.") +
-                "\n  -J-Dhttp.proxyHost=<hostname>" +
-                "\n  -J-Dhttp.proxyPort=<portnumber>\n" +
-                rb.getString("or") +
-                "\n  -J-Dhttps.proxyHost=<hostname> " +
-                "\n  -J-Dhttps.proxyPort=<portnumber> ", e);
-            }
-
-            sfFilename = sf.getMetaName();
-            bkFilename = block.getMetaName();
-
-            ZipEntry sfFile = new ZipEntry(sfFilename);
-            ZipEntry bkFile = new ZipEntry(bkFilename);
-
-            long time = System.currentTimeMillis();
-            sfFile.setTime(time);
-            bkFile.setTime(time);
-
-            // signature file
-            zos.putNextEntry(sfFile);
-            sf.write(zos);
-            if (verbose != null) {
-                if (zipFile.getEntry(sfFilename) != null) {
-                    System.out.println(rb.getString(".updating.") +
-                                sfFilename);
-                } else {
-                    System.out.println(rb.getString(".adding.") +
-                                sfFilename);
-                }
+                        rb.getString("no.response.from.the.Timestamping.Authority.") +
+                        "\n  -J-Dhttp.proxyHost=<hostname>" +
+                        "\n  -J-Dhttp.proxyPort=<portnumber>\n" +
+                        rb.getString("or") +
+                        "\n  -J-Dhttps.proxyHost=<hostname> " +
+                        "\n  -J-Dhttps.proxyPort=<portnumber> ", e);
+            } else {
+                error(rb.getString("unable.to.sign.jar.")+e.getCause(), e.getCause());
             }
-
-            if (verbose != null) {
-                if (tsaUrl != null || tsaCert != null) {
-                    System.out.println(
-                        rb.getString("requesting.a.signature.timestamp"));
-                }
-                if (tsaUrl != null) {
-                    System.out.println(rb.getString("TSA.location.") + tsaUrl);
-                }
-                if (tsaCert != null) {
-                    URI tsaURI = TimestampedSigner.getTimestampingURI(tsaCert);
-                    if (tsaURI != null) {
-                        System.out.println(rb.getString("TSA.location.") +
-                            tsaURI);
-                    }
-                    System.out.println(rb.getString("TSA.certificate.") +
-                        printCert("", tsaCert, false, null, false));
-                }
-                if (signingMechanism != null) {
-                    System.out.println(
-                        rb.getString("using.an.alternative.signing.mechanism"));
-                }
-            }
-
-            // signature block file
-            zos.putNextEntry(bkFile);
-            block.write(zos);
-            if (verbose != null) {
-                if (zipFile.getEntry(bkFilename) != null) {
-                    System.out.println(rb.getString(".updating.") +
-                        bkFilename);
-                } else {
-                    System.out.println(rb.getString(".adding.") +
-                        bkFilename);
-                }
-            }
-
-            // Write out all other META-INF files that we stored in the
-            // vector
-            for (int i=0; i<mfFiles.size(); i++) {
-                ZipEntry ze = mfFiles.elementAt(i);
-                if (!ze.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)
-                    && !ze.getName().equalsIgnoreCase(sfFilename)
-                    && !ze.getName().equalsIgnoreCase(bkFilename)) {
-                    writeEntry(zipFile, zos, ze);
-                }
-            }
-
-            // Write out all other files
-            for (Enumeration<? extends ZipEntry> enum_=zipFile.entries();
-                        enum_.hasMoreElements();) {
-                ZipEntry ze = enum_.nextElement();
-
-                if (!ze.getName().startsWith(META_INF)) {
-                    if (verbose != null) {
-                        if (manifest.getAttributes(ze.getName()) != null)
-                          System.out.println(rb.getString(".signing.") +
-                                ze.getName());
-                        else
-                          System.out.println(rb.getString(".adding.") +
-                                ze.getName());
-                    }
-                    writeEntry(zipFile, zos, ze);
-                }
-            }
-        } catch(IOException ioe) {
-            error(rb.getString("unable.to.sign.jar.")+ioe, ioe);
         } finally {
             // close the resouces
             if (zipFile != null) {
@@ -1425,8 +1221,8 @@
                 zipFile = null;
             }
 
-            if (zos != null) {
-                zos.close();
+            if (fos != null) {
+                fos.close();
             }
         }
 
@@ -1527,35 +1323,6 @@
     }
 
     /**
-     * Find the length of header inside bs. The header is a multiple (>=0)
-     * lines of attributes plus an empty line. The empty line is included
-     * in the header.
-     */
-    @SuppressWarnings("fallthrough")
-    private int findHeaderEnd(byte[] bs) {
-        // Initial state true to deal with empty header
-        boolean newline = true;     // just met a newline
-        int len = bs.length;
-        for (int i=0; i<len; i++) {
-            switch (bs[i]) {
-                case '\r':
-                    if (i < len - 1 && bs[i+1] == '\n') i++;
-                    // fallthrough
-                case '\n':
-                    if (newline) return i+1;    //+1 to get length
-                    newline = true;
-                    break;
-                default:
-                    newline = false;
-            }
-        }
-        // If header end is not found, it means the MANIFEST.MF has only
-        // the main attributes section and it does not end with 2 newlines.
-        // Returns the whole length so that it can be completely replaced.
-        return len;
-    }
-
-    /**
      * signature-related files include:
      * . META-INF/MANIFEST.MF
      * . META-INF/SIG-*
@@ -1619,45 +1386,6 @@
         return result;
     }
 
-    private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze)
-    throws IOException
-    {
-        ZipEntry ze2 = new ZipEntry(ze.getName());
-        ze2.setMethod(ze.getMethod());
-        ze2.setTime(ze.getTime());
-        ze2.setComment(ze.getComment());
-        ze2.setExtra(ze.getExtra());
-        if (ze.getMethod() == ZipEntry.STORED) {
-            ze2.setSize(ze.getSize());
-            ze2.setCrc(ze.getCrc());
-        }
-        os.putNextEntry(ze2);
-        writeBytes(zf, ze, os);
-    }
-
-    /**
-     * Writes all the bytes for a given entry to the specified output stream.
-     */
-    private synchronized void writeBytes
-        (ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException {
-        int n;
-
-        InputStream is = null;
-        try {
-            is = zf.getInputStream(ze);
-            long left = ze.getSize();
-
-            while((left > 0) && (n = is.read(buffer, 0, buffer.length)) != -1) {
-                os.write(buffer, 0, n);
-                left -= n;
-            }
-        } finally {
-            if (is != null) {
-                is.close();
-            }
-        }
-    }
-
     void loadKeyStore(String keyStoreName, boolean prompt) {
 
         if (!nullStream && keyStoreName == null) {
@@ -1958,15 +1686,13 @@
         }
     }
 
-    void error(String message)
-    {
+    void error(String message) {
         System.out.println(rb.getString("jarsigner.")+message);
         System.exit(1);
     }
 
 
-    void error(String message, Exception e)
-    {
+    void error(String message, Throwable e) {
         System.out.println(rb.getString("jarsigner.")+message);
         if (debug) {
             e.printStackTrace();
@@ -1990,8 +1716,7 @@
         }
     }
 
-    char[] getPass(String prompt)
-    {
+    char[] getPass(String prompt) {
         System.err.print(prompt);
         System.err.flush();
         try {
@@ -2008,569 +1733,4 @@
         // this shouldn't happen
         return null;
     }
-
-    /*
-     * Reads all the bytes for a given zip entry.
-     */
-    private synchronized byte[] getBytes(ZipFile zf,
-                                         ZipEntry ze) throws IOException {
-        int n;
-
-        InputStream is = null;
-        try {
-            is = zf.getInputStream(ze);
-            baos.reset();
-            long left = ze.getSize();
-
-            while((left > 0) && (n = is.read(buffer, 0, buffer.length)) != -1) {
-                baos.write(buffer, 0, n);
-                left -= n;
-            }
-        } finally {
-            if (is != null) {
-                is.close();
-            }
-        }
-
-        return baos.toByteArray();
-    }
-
-    /*
-     * Returns manifest entry from given jar file, or null if given jar file
-     * does not have a manifest entry.
-     */
-    private ZipEntry getManifestFile(ZipFile zf) {
-        ZipEntry ze = zf.getEntry(JarFile.MANIFEST_NAME);
-        if (ze == null) {
-            // Check all entries for matching name
-            Enumeration<? extends ZipEntry> enum_ = zf.entries();
-            while (enum_.hasMoreElements() && ze == null) {
-                ze = enum_.nextElement();
-                if (!JarFile.MANIFEST_NAME.equalsIgnoreCase
-                    (ze.getName())) {
-                    ze = null;
-                }
-            }
-        }
-        return ze;
-    }
-
-    /*
-     * Computes the digests of a zip entry, and returns them as an array
-     * of base64-encoded strings.
-     */
-    private synchronized String[] getDigests(ZipEntry ze, ZipFile zf,
-                                             MessageDigest[] digests)
-        throws IOException {
-
-        int n, i;
-        InputStream is = null;
-        try {
-            is = zf.getInputStream(ze);
-            long left = ze.getSize();
-            while((left > 0)
-                && (n = is.read(buffer, 0, buffer.length)) != -1) {
-                for (i=0; i<digests.length; i++) {
-                    digests[i].update(buffer, 0, n);
-                }
-                left -= n;
-            }
-        } finally {
-            if (is != null) {
-                is.close();
-            }
-        }
-
-        // complete the digests
-        String[] base64Digests = new String[digests.length];
-        for (i=0; i<digests.length; i++) {
-            base64Digests[i] = Base64.getEncoder().encodeToString(digests[i].digest());
-        }
-        return base64Digests;
-    }
-
-    /*
-     * Computes the digests of a zip entry, and returns them as a list of
-     * attributes
-     */
-    private Attributes getDigestAttributes(ZipEntry ze, ZipFile zf,
-                                           MessageDigest[] digests)
-        throws IOException {
-
-        String[] base64Digests = getDigests(ze, zf, digests);
-        Attributes attrs = new Attributes();
-
-        for (int i=0; i<digests.length; i++) {
-            attrs.putValue(digests[i].getAlgorithm()+"-Digest",
-                           base64Digests[i]);
-        }
-        return attrs;
-    }
-
-    /*
-     * Updates the digest attributes of a manifest entry, by adding or
-     * replacing digest values.
-     * A digest value is added if the manifest entry does not contain a digest
-     * for that particular algorithm.
-     * A digest value is replaced if it is obsolete.
-     *
-     * Returns true if the manifest entry has been changed, and false
-     * otherwise.
-     */
-    private boolean updateDigests(ZipEntry ze, ZipFile zf,
-                                  MessageDigest[] digests,
-                                  Manifest mf) throws IOException {
-        boolean update = false;
-
-        Attributes attrs = mf.getAttributes(ze.getName());
-        String[] base64Digests = getDigests(ze, zf, digests);
-
-        for (int i=0; i<digests.length; i++) {
-            // The entry name to be written into attrs
-            String name = null;
-            try {
-                // Find if the digest already exists
-                AlgorithmId aid = AlgorithmId.get(digests[i].getAlgorithm());
-                for (Object key: attrs.keySet()) {
-                    if (key instanceof Attributes.Name) {
-                        String n = ((Attributes.Name)key).toString();
-                        if (n.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {
-                            String tmp = n.substring(0, n.length() - 7);
-                            if (AlgorithmId.get(tmp).equals(aid)) {
-                                name = n;
-                                break;
-                            }
-                        }
-                    }
-                }
-            } catch (NoSuchAlgorithmException nsae) {
-                // Ignored. Writing new digest entry.
-            }
-
-            if (name == null) {
-                name = digests[i].getAlgorithm()+"-Digest";
-                attrs.putValue(name, base64Digests[i]);
-                update=true;
-            } else {
-                // compare digests, and replace the one in the manifest
-                // if they are different
-                String mfDigest = attrs.getValue(name);
-                if (!mfDigest.equalsIgnoreCase(base64Digests[i])) {
-                    attrs.putValue(name, base64Digests[i]);
-                    update=true;
-                }
-            }
-        }
-        return update;
-    }
-
-    /*
-     * Try to load the specified signing mechanism.
-     * The URL class loader is used.
-     */
-    private ContentSigner loadSigningMechanism(String signerClassName,
-        String signerClassPath) throws Exception {
-
-        // construct class loader
-        String cpString = null;   // make sure env.class.path defaults to dot
-
-        // do prepends to get correct ordering
-        cpString = PathList.appendPath(System.getProperty("env.class.path"), cpString);
-        cpString = PathList.appendPath(System.getProperty("java.class.path"), cpString);
-        cpString = PathList.appendPath(signerClassPath, cpString);
-        URL[] urls = PathList.pathToURLs(cpString);
-        ClassLoader appClassLoader = new URLClassLoader(urls);
-
-        // attempt to find signer
-        Class<?> signerClass = appClassLoader.loadClass(signerClassName);
-
-        // Check that it implements ContentSigner
-        Object signer = signerClass.newInstance();
-        if (!(signer instanceof ContentSigner)) {
-            MessageFormat form = new MessageFormat(
-                rb.getString("signerClass.is.not.a.signing.mechanism"));
-            Object[] source = {signerClass.getName()};
-            throw new IllegalArgumentException(form.format(source));
-        }
-        return (ContentSigner)signer;
-    }
 }
-
-class SignatureFile {
-
-    /** SignatureFile */
-    Manifest sf;
-
-    /** .SF base name */
-    String baseName;
-
-    public SignatureFile(MessageDigest digests[],
-                         Manifest mf,
-                         ManifestDigester md,
-                         String baseName,
-                         boolean signManifest)
-
-    {
-        this.baseName = baseName;
-
-        String version = System.getProperty("java.version");
-        String javaVendor = System.getProperty("java.vendor");
-
-        sf = new Manifest();
-        Attributes mattr = sf.getMainAttributes();
-
-        mattr.putValue(Attributes.Name.SIGNATURE_VERSION.toString(), "1.0");
-        mattr.putValue("Created-By", version + " (" + javaVendor + ")");
-
-        if (signManifest) {
-            // sign the whole manifest
-            for (int i=0; i < digests.length; i++) {
-                mattr.putValue(digests[i].getAlgorithm()+"-Digest-Manifest",
-                               Base64.getEncoder().encodeToString(md.manifestDigest(digests[i])));
-            }
-        }
-
-        // create digest of the manifest main attributes
-        ManifestDigester.Entry mde =
-                md.get(ManifestDigester.MF_MAIN_ATTRS, false);
-        if (mde != null) {
-            for (int i=0; i < digests.length; i++) {
-                mattr.putValue(digests[i].getAlgorithm() +
-                        "-Digest-" + ManifestDigester.MF_MAIN_ATTRS,
-                        Base64.getEncoder().encodeToString(mde.digest(digests[i])));
-            }
-        } else {
-            throw new IllegalStateException
-                ("ManifestDigester failed to create " +
-                "Manifest-Main-Attribute entry");
-        }
-
-        /* go through the manifest entries and create the digests */
-
-        Map<String,Attributes> entries = sf.getEntries();
-        Iterator<Map.Entry<String,Attributes>> mit =
-                                mf.getEntries().entrySet().iterator();
-        while(mit.hasNext()) {
-            Map.Entry<String,Attributes> e = mit.next();
-            String name = e.getKey();
-            mde = md.get(name, false);
-            if (mde != null) {
-                Attributes attr = new Attributes();
-                for (int i=0; i < digests.length; i++) {
-                    attr.putValue(digests[i].getAlgorithm()+"-Digest",
-                                  Base64.getEncoder().encodeToString(mde.digest(digests[i])));
-                }
-                entries.put(name, attr);
-            }
-        }
-    }
-
-    /**
-     * Writes the SignatureFile to the specified OutputStream.
-     *
-     * @param out the output stream
-     * @exception IOException if an I/O error has occurred
-     */
-
-    public void write(OutputStream out) throws IOException
-    {
-        sf.write(out);
-    }
-
-    /**
-     * get .SF file name
-     */
-    public String getMetaName()
-    {
-        return "META-INF/"+ baseName + ".SF";
-    }
-
-    /**
-     * get base file name
-     */
-    public String getBaseName()
-    {
-        return baseName;
-    }
-
-    /*
-     * Generate a signed data block.
-     * If a URL or a certificate (containing a URL) for a Timestamping
-     * Authority is supplied then a signature timestamp is generated and
-     * inserted into the signed data block.
-     *
-     * @param sigalg signature algorithm to use, or null to use default
-     * @param tsaUrl The location of the Timestamping Authority. If null
-     *               then no timestamp is requested.
-     * @param tsaCert The certificate for the Timestamping Authority. If null
-     *               then no timestamp is requested.
-     * @param signingMechanism The signing mechanism to use.
-     * @param args The command-line arguments to jarsigner.
-     * @param zipFile The original source Zip file.
-     */
-    @SuppressWarnings("deprecation")
-    public Block generateBlock(PrivateKey privateKey,
-                               String sigalg,
-                               X509Certificate[] certChain,
-                               boolean externalSF, String tsaUrl,
-                               X509Certificate tsaCert,
-                               String tSAPolicyID,
-                               String tSADigestAlg,
-                               ContentSigner signingMechanism,
-                               String[] args, ZipFile zipFile)
-        throws NoSuchAlgorithmException, InvalidKeyException, IOException,
-            SignatureException, CertificateException
-    {
-        return new Block(this, privateKey, sigalg, certChain, externalSF,
-                tsaUrl, tsaCert, tSAPolicyID, tSADigestAlg, signingMechanism, args, zipFile);
-    }
-
-
-    public static class Block {
-
-        private byte[] block;
-        private String blockFileName;
-
-        /*
-         * Construct a new signature block.
-         */
-        @SuppressWarnings("deprecation")
-        Block(SignatureFile sfg, PrivateKey privateKey, String sigalg,
-            X509Certificate[] certChain, boolean externalSF, String tsaUrl,
-            X509Certificate tsaCert, String tSAPolicyID, String tSADigestAlg,
-            ContentSigner signingMechanism, String[] args, ZipFile zipFile)
-            throws NoSuchAlgorithmException, InvalidKeyException, IOException,
-            SignatureException, CertificateException {
-
-            Principal issuerName = certChain[0].getIssuerDN();
-            if (!(issuerName instanceof X500Name)) {
-                // must extract the original encoded form of DN for subsequent
-                // name comparison checks (converting to a String and back to
-                // an encoded DN could cause the types of String attribute
-                // values to be changed)
-                X509CertInfo tbsCert = new
-                    X509CertInfo(certChain[0].getTBSCertificate());
-                issuerName = (Principal)
-                    tbsCert.get(X509CertInfo.ISSUER + "." +
-                                X509CertInfo.DN_NAME);
-                }
-            BigInteger serial = certChain[0].getSerialNumber();
-
-            String signatureAlgorithm;
-            String keyAlgorithm = privateKey.getAlgorithm();
-            /*
-             * If no signature algorithm was specified, we choose a
-             * default that is compatible with the private key algorithm.
-             */
-            if (sigalg == null) {
-
-                if (keyAlgorithm.equalsIgnoreCase("DSA"))
-                    signatureAlgorithm = "SHA256withDSA";
-                else if (keyAlgorithm.equalsIgnoreCase("RSA"))
-                    signatureAlgorithm = "SHA256withRSA";
-                else if (keyAlgorithm.equalsIgnoreCase("EC"))
-                    signatureAlgorithm = "SHA256withECDSA";
-                else
-                    throw new RuntimeException("private key is not a DSA or "
-                                               + "RSA key");
-            } else {
-                signatureAlgorithm = sigalg;
-            }
-
-            // check common invalid key/signature algorithm combinations
-            String sigAlgUpperCase = signatureAlgorithm.toUpperCase(Locale.ENGLISH);
-            if ((sigAlgUpperCase.endsWith("WITHRSA") &&
-                !keyAlgorithm.equalsIgnoreCase("RSA")) ||
-                (sigAlgUpperCase.endsWith("WITHECDSA") &&
-                !keyAlgorithm.equalsIgnoreCase("EC")) ||
-                (sigAlgUpperCase.endsWith("WITHDSA") &&
-                !keyAlgorithm.equalsIgnoreCase("DSA"))) {
-                throw new SignatureException
-                    ("private key algorithm is not compatible with signature algorithm");
-            }
-
-            blockFileName = "META-INF/"+sfg.getBaseName()+"."+keyAlgorithm;
-
-            AlgorithmId sigAlg = AlgorithmId.get(signatureAlgorithm);
-            AlgorithmId digEncrAlg = AlgorithmId.get(keyAlgorithm);
-
-            Signature sig = Signature.getInstance(signatureAlgorithm);
-            sig.initSign(privateKey);
-
-            ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            sfg.write(baos);
-
-            byte[] content = baos.toByteArray();
-
-            sig.update(content);
-            byte[] signature = sig.sign();
-
-            // Timestamp the signature and generate the signature block file
-            if (signingMechanism == null) {
-                signingMechanism = new TimestampedSigner();
-            }
-            URI tsaUri = null;
-            try {
-                if (tsaUrl != null) {
-                    tsaUri = new URI(tsaUrl);
-                }
-            } catch (URISyntaxException e) {
-                throw new IOException(e);
-            }
-
-            // Assemble parameters for the signing mechanism
-            ContentSignerParameters params =
-                new JarSignerParameters(args, tsaUri, tsaCert, tSAPolicyID,
-                        tSADigestAlg, signature,
-                    signatureAlgorithm, certChain, content, zipFile);
-
-            // Generate the signature block
-            block = signingMechanism.generateSignedData(
-                    params, externalSF, (tsaUrl != null || tsaCert != null));
-        }
-
-        /*
-         * get block file name.
-         */
-        public String getMetaName()
-        {
-            return blockFileName;
-        }
-
-        /**
-         * Writes the block file to the specified OutputStream.
-         *
-         * @param out the output stream
-         * @exception IOException if an I/O error has occurred
-         */
-
-        public void write(OutputStream out) throws IOException
-        {
-            out.write(block);
-        }
-    }
-}
-
-
-/*
- * This object encapsulates the parameters used to perform content signing.
- */
-@SuppressWarnings("deprecation")
-class JarSignerParameters implements ContentSignerParameters {
-
-    private String[] args;
-    private URI tsa;
-    private X509Certificate tsaCertificate;
-    private byte[] signature;
-    private String signatureAlgorithm;
-    private X509Certificate[] signerCertificateChain;
-    private byte[] content;
-    private ZipFile source;
-    private String tSAPolicyID;
-    private String tSADigestAlg;
-
-    /**
-     * Create a new object.
-     */
-    JarSignerParameters(String[] args, URI tsa, X509Certificate tsaCertificate,
-        String tSAPolicyID, String tSADigestAlg,
-        byte[] signature, String signatureAlgorithm,
-        X509Certificate[] signerCertificateChain, byte[] content,
-        ZipFile source) {
-
-        if (signature == null || signatureAlgorithm == null ||
-            signerCertificateChain == null || tSADigestAlg == null) {
-            throw new NullPointerException();
-        }
-        this.args = args;
-        this.tsa = tsa;
-        this.tsaCertificate = tsaCertificate;
-        this.tSAPolicyID = tSAPolicyID;
-        this.tSADigestAlg = tSADigestAlg;
-        this.signature = signature;
-        this.signatureAlgorithm = signatureAlgorithm;
-        this.signerCertificateChain = signerCertificateChain;
-        this.content = content;
-        this.source = source;
-    }
-
-    /**
-     * Retrieves the command-line arguments.
-     *
-     * @return The command-line arguments. May be null.
-     */
-    public String[] getCommandLine() {
-        return args;
-    }
-
-    /**
-     * Retrieves the identifier for a Timestamping Authority (TSA).
-     *
-     * @return The TSA identifier. May be null.
-     */
-    public URI getTimestampingAuthority() {
-        return tsa;
-    }
-
-    /**
-     * Retrieves the certificate for a Timestamping Authority (TSA).
-     *
-     * @return The TSA certificate. May be null.
-     */
-    public X509Certificate getTimestampingAuthorityCertificate() {
-        return tsaCertificate;
-    }
-
-    public String getTSAPolicyID() {
-        return tSAPolicyID;
-    }
-
-    public String getTSADigestAlg() {
-        return tSADigestAlg;
-    }
-
-    /**
-     * Retrieves the signature.
-     *
-     * @return The non-null signature bytes.
-     */
-    public byte[] getSignature() {
-        return signature;
-    }
-
-    /**
-     * Retrieves the name of the signature algorithm.
-     *
-     * @return The non-null string name of the signature algorithm.
-     */
-    public String getSignatureAlgorithm() {
-        return signatureAlgorithm;
-    }
-
-    /**
-     * Retrieves the signer's X.509 certificate chain.
-     *
-     * @return The non-null array of X.509 public-key certificates.
-     */
-    public X509Certificate[] getSignerCertificateChain() {
-        return signerCertificateChain;
-    }
-
-    /**
-     * Retrieves the content that was signed.
-     *
-     * @return The content bytes. May be null.
-     */
-    public byte[] getContent() {
-        return content;
-    }
-
-    /**
-     * Retrieves the original source ZIP file before it was signed.
-     *
-     * @return The original ZIP file. May be null.
-     */
-    public ZipFile getSource() {
-        return source;
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/jdk/security/jarsigner/Function.java	Fri Nov 20 08:34:04 2015 +0800
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8056174
+ * @summary test the functions of JarSigner API
+ * @modules java.base/sun.security.tools.keytool
+ *          jdk.jartool
+ */
+
+import jdk.security.jarsigner.JarSigner;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.MessageDigest;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+public class Function {
+    public static void main(String[] args) throws Exception {
+
+        try (FileOutputStream fout =new FileOutputStream("src.zip");
+                ZipOutputStream zout = new ZipOutputStream(fout)) {
+            zout.putNextEntry(new ZipEntry("x"));
+            zout.write(new byte[10]);
+            zout.closeEntry();
+        }
+
+        sun.security.tools.keytool.Main.main(
+                ("-storetype jks -keystore ks -storepass changeit" +
+                        " -keypass changeit -dname" +
+                        " CN=RSA -alias r -genkeypair -keyalg rsa").split(" "));
+
+        KeyStore ks = KeyStore.getInstance("JKS");
+        ks.load(new FileInputStream("ks"), "changeit".toCharArray());
+        PrivateKey key = (PrivateKey)ks.getKey("r", "changeit".toCharArray());
+        Certificate cert = ks.getCertificate("r");
+        JarSigner.Builder jsb = new JarSigner.Builder(key,
+                CertificateFactory.getInstance("X.509").generateCertPath(
+                        Collections.singletonList(cert)));
+
+        jsb.digestAlgorithm("SHA1");
+        jsb.signatureAlgorithm("SHA1withRSA");
+
+        AtomicInteger counter = new AtomicInteger(0);
+        StringBuilder sb = new StringBuilder();
+        jsb.eventHandler(
+                (a, f)->{
+                    counter.incrementAndGet();
+                    sb.append(a).append(' ').append(f).append('\n');
+                });
+
+        OutputStream blackHole = new OutputStream() {
+            @Override
+            public void write(int b) throws IOException { }
+        };
+
+        try (ZipFile src = new ZipFile("src.zip")) {
+            jsb.build().sign(src, blackHole);
+        }
+
+        if (counter.get() != 4) {
+            throw new Exception("Event number is " + counter.get()
+                    + ":\n" + sb.toString());
+        }
+
+        // Provider test.
+        Provider p = new MyProvider();
+        jsb.digestAlgorithm("Five", p);
+        jsb.signatureAlgorithm("SHA1WithRSA", p);
+        try (ZipFile src = new ZipFile("src.zip");
+                FileOutputStream out = new FileOutputStream("out.jar")) {
+            jsb.build().sign(src, out);
+        }
+
+        try (JarFile signed = new JarFile("out.jar")) {
+            Manifest man = signed.getManifest();
+            assertTrue(man.getAttributes("x").getValue("Five-Digest").equals("FAKE"));
+
+            Manifest sf = new Manifest(signed.getInputStream(
+                    signed.getJarEntry("META-INF/SIGNER.SF")));
+            assertTrue(sf.getMainAttributes().getValue("Five-Digest-Manifest")
+                    .equals("FAKE"));
+            assertTrue(sf.getAttributes("x").getValue("Five-Digest").equals("FAKE"));
+
+            try (InputStream sig = signed.getInputStream(
+                    signed.getJarEntry("META-INF/SIGNER.RSA"))) {
+                byte[] data = sig.readAllBytes();
+                assertTrue(Arrays.equals(
+                        Arrays.copyOfRange(data, data.length-8, data.length),
+                        "FAKEFAKE".getBytes()));
+            }
+        }
+    }
+
+    private static void assertTrue(boolean v) {
+        if (!v) {
+            throw new AssertionError();
+        }
+    }
+
+    public static class MyProvider extends Provider {
+        MyProvider() {
+            super("MY", 1.0d, null);
+            put("MessageDigest.Five", Five.class.getName());
+            put("Signature.SHA1WithRSA", SHA1WithRSA.class.getName());
+        }
+    }
+
+    // "Five" is a MessageDigest always returns the same value
+    public static class Five extends MessageDigest {
+        static final byte[] dig = {0x14, 0x02, (byte)0x84}; //base64 -> FAKE
+        public Five() { super("Five"); }
+        protected void engineUpdate(byte input) { }
+        protected void engineUpdate(byte[] input, int offset, int len) { }
+        protected byte[] engineDigest() { return dig; }
+        protected void engineReset() { }
+    }
+
+    // This fake "SHA1withRSA" is a Signature always returns the same value.
+    // An existing name must be used otherwise PKCS7 does not which OID to use.
+    public static class SHA1WithRSA extends Signature {
+        static final byte[] sig = "FAKEFAKE".getBytes();
+        public SHA1WithRSA() { super("SHA1WithRSA"); }
+        protected void engineInitVerify(PublicKey publicKey)
+                throws InvalidKeyException { }
+        protected void engineInitSign(PrivateKey privateKey)
+                throws InvalidKeyException { }
+        protected void engineUpdate(byte b) throws SignatureException { }
+        protected void engineUpdate(byte[] b, int off, int len)
+                throws SignatureException { }
+        protected byte[] engineSign() throws SignatureException { return sig; }
+        protected boolean engineVerify(byte[] sigBytes)
+                throws SignatureException {
+            return Arrays.equals(sigBytes, sig);
+        }
+        protected void engineSetParameter(String param, Object value)
+                throws InvalidParameterException { }
+        protected Object engineGetParameter(String param)
+                throws InvalidParameterException { return null; }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/jdk/security/jarsigner/Spec.java	Fri Nov 20 08:34:04 2015 +0800
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8056174
+ * @summary Make sure JarSigner impl conforms to spec
+ * @library /lib/testlibrary
+ * @modules java.base/sun.security.tools.keytool
+ *          java.base/sun.security.provider.certpath
+ *          jdk.jartool
+ */
+
+import com.sun.jarsigner.ContentSigner;
+import com.sun.jarsigner.ContentSignerParameters;
+import jdk.security.jarsigner.JarSigner;
+import jdk.testlibrary.JarUtils;
+import sun.security.provider.certpath.X509CertPath;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.*;
+import java.security.cert.CertPath;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.function.BiConsumer;
+
+public class Spec {
+
+    public static void main(String[] args) throws Exception {
+
+        // Prepares raw file
+        Files.write(Paths.get("a"), "a".getBytes());
+
+        // Pack
+        JarUtils.createJar("a.jar", "a");
+
+        // Prepare a keystore
+        sun.security.tools.keytool.Main.main(
+                ("-keystore ks -storepass changeit -keypass changeit -dname" +
+                        " CN=RSA -alias r -genkeypair -keyalg rsa").split(" "));
+        sun.security.tools.keytool.Main.main(
+                ("-keystore ks -storepass changeit -keypass changeit -dname" +
+                        " CN=DSA -alias d -genkeypair -keyalg dsa").split(" "));
+
+        char[] pass = "changeit".toCharArray();
+
+        KeyStore ks = KeyStore.getInstance(
+                new File("ks"), pass);
+        PrivateKey pkr = (PrivateKey)ks.getKey("r", pass);
+        PrivateKey pkd = (PrivateKey)ks.getKey("d", pass);
+        CertPath cp = CertificateFactory.getInstance("X.509")
+                .generateCertPath(Arrays.asList(ks.getCertificateChain("r")));
+
+        Provider sun = Security.getProvider("SUN");
+
+        // throws
+        npe(()->new JarSigner.Builder(null));
+        npe(()->new JarSigner.Builder(null, cp));
+        iae(()->new JarSigner.Builder(
+                pkr, new X509CertPath(Collections.emptyList())));
+        iae(()->new JarSigner.Builder(pkd, cp));    // unmatched certs alg
+
+        JarSigner.Builder b1 = new JarSigner.Builder(pkr, cp);
+
+        npe(()->b1.digestAlgorithm(null));
+        nsae(()->b1.digestAlgorithm("HAHA"));
+        b1.digestAlgorithm("SHA-256");
+
+        npe(()->b1.digestAlgorithm("SHA-256", null));
+        npe(()->b1.digestAlgorithm(null, sun));
+        nsae(()->b1.digestAlgorithm("HAHA", sun));
+        b1.digestAlgorithm("SHA-256", sun);
+
+        npe(()->b1.signatureAlgorithm(null));
+        nsae(()->b1.signatureAlgorithm("HAHAwithHEHE"));
+        iae(()->b1.signatureAlgorithm("SHA256withECDSA"));
+
+        npe(()->b1.signatureAlgorithm(null, sun));
+        npe(()->b1.signatureAlgorithm("SHA256withRSA", null));
+        nsae(()->b1.signatureAlgorithm("HAHAwithHEHE", sun));
+        iae(()->b1.signatureAlgorithm("SHA256withDSA", sun));
+
+        npe(()->b1.tsa(null));
+
+        npe(()->b1.signerName(null));
+        iae(()->b1.signerName(""));
+        iae(()->b1.signerName("123456789"));
+        iae(()->b1.signerName("a+b"));
+
+        npe(()->b1.setProperty(null, ""));
+        uoe(()->b1.setProperty("what", ""));
+        npe(()->b1.setProperty("tsadigestalg", null));
+        iae(()->b1.setProperty("tsadigestalg", "HAHA"));
+        npe(()->b1.setProperty("tsapolicyid", null));
+        npe(()->b1.setProperty("internalsf", null));
+        iae(()->b1.setProperty("internalsf", "Hello"));
+        npe(()->b1.setProperty("sectionsonly", null));
+        iae(()->b1.setProperty("sectionsonly", "OK"));
+        npe(()->b1.setProperty("altsigner", null));
+        npe(()->b1.eventHandler(null));
+
+        // default values
+        JarSigner.Builder b2 = new JarSigner.Builder(pkr, cp);
+        JarSigner js2 = b2.build();
+
+        assertTrue(js2.getDigestAlgorithm().equals(
+                JarSigner.Builder.getDefaultDigestAlgorithm()));
+        assertTrue(js2.getSignatureAlgorithm().equals(
+                JarSigner.Builder.getDefaultSignatureAlgorithm(pkr)));
+        assertTrue(js2.getTsa() == null);
+        assertTrue(js2.getSignerName().equals("SIGNER"));
+        assertTrue(js2.getProperty("tsadigestalg").equals(
+                JarSigner.Builder.getDefaultDigestAlgorithm()));
+        assertTrue(js2.getProperty("tsapolicyid") == null);
+        assertTrue(js2.getProperty("internalsf").equals("false"));
+        assertTrue(js2.getProperty("sectionsonly").equals("false"));
+        assertTrue(js2.getProperty("altsigner") == null);
+        uoe(()->js2.getProperty("invalid"));
+
+        // default values
+        BiConsumer<String,String> myeh = (a,s)->{};
+        URI tsa = new URI("https://tsa.com");
+
+        JarSigner.Builder b3 = new JarSigner.Builder(pkr, cp)
+                .digestAlgorithm("SHA-1")
+                .signatureAlgorithm("SHA1withRSA")
+                .signerName("Duke")
+                .tsa(tsa)
+                .setProperty("tsadigestalg", "SHA-512")
+                .setProperty("tsapolicyid", "1.2.3.4")
+                .setProperty("internalsf", "true")
+                .setProperty("sectionsonly", "true")
+                .setProperty("altsigner", "MyContentSigner")
+                .eventHandler(myeh);
+        JarSigner js3 = b3.build();
+
+        assertTrue(js3.getDigestAlgorithm().equals("SHA-1"));
+        assertTrue(js3.getSignatureAlgorithm().equals("SHA1withRSA"));
+        assertTrue(js3.getTsa().equals(tsa));
+        assertTrue(js3.getSignerName().equals("DUKE"));
+        assertTrue(js3.getProperty("tsadigestalg").equals("SHA-512"));
+        assertTrue(js3.getProperty("tsapolicyid").equals("1.2.3.4"));
+        assertTrue(js3.getProperty("internalsf").equals("true"));
+        assertTrue(js3.getProperty("sectionsonly").equals("true"));
+        assertTrue(js3.getProperty("altsigner").equals("MyContentSigner"));
+        assertTrue(js3.getProperty("altsignerpath") == null);
+
+        assertTrue(JarSigner.Builder.getDefaultDigestAlgorithm().equals("SHA-256"));
+
+        // Calculating large DSA and RSA keys are too slow.
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+        kpg.initialize(1024);
+        assertTrue(JarSigner.Builder
+                .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate())
+                    .equals("SHA256withRSA"));
+
+        kpg = KeyPairGenerator.getInstance("DSA");
+        kpg.initialize(1024);
+        assertTrue(JarSigner.Builder
+                .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate())
+                .equals("SHA256withDSA"));
+
+        kpg = KeyPairGenerator.getInstance("EC");
+        kpg.initialize(192);
+        assertTrue(JarSigner.Builder
+                .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate())
+                .equals("SHA256withECDSA"));
+        kpg.initialize(384);
+        assertTrue(JarSigner.Builder
+                .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate())
+                .equals("SHA384withECDSA"));
+        kpg.initialize(571);
+        assertTrue(JarSigner.Builder
+                .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate())
+                .equals("SHA512withECDSA"));
+    }
+
+    interface RunnableWithException {
+        void run() throws Exception;
+    }
+
+    static void uoe(RunnableWithException r) throws Exception {
+        checkException(r, UnsupportedOperationException.class);
+    }
+
+    static void nsae(RunnableWithException r) throws Exception {
+        checkException(r, NoSuchAlgorithmException.class);
+    }
+
+    static void npe(RunnableWithException r) throws Exception {
+        checkException(r, NullPointerException.class);
+    }
+
+    static void iae(RunnableWithException r) throws Exception {
+        checkException(r, IllegalArgumentException.class);
+    }
+
+    static void checkException(RunnableWithException r, Class ex)
+            throws Exception {
+        try {
+            r.run();
+        } catch (Exception e) {
+            if (ex.isAssignableFrom(e.getClass())) {
+                return;
+            }
+            throw e;
+        }
+        throw new Exception("No exception thrown");
+    }
+
+    static void assertTrue(boolean x) throws Exception {
+        if (!x) throw new Exception("Not true");
+    }
+
+    static class MyContentSigner extends ContentSigner {
+        @Override
+        public byte[] generateSignedData(
+                ContentSignerParameters parameters,
+                boolean omitContent,
+                boolean applyTimestamp) throws NoSuchAlgorithmException,
+                CertificateException, IOException {
+            return new byte[0];
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/tools/jarsigner/Options.java	Fri Nov 20 08:34:04 2015 +0800
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8056174
+ * @summary Make sure the jarsigner tool still works after it's modified to
+ *          be based on JarSigner API
+ * @library /lib/testlibrary
+ * @modules java.base/sun.security.tools.keytool
+ *          jdk.jartool/sun.security.tools.jarsigner
+ *          java.base/sun.security.pkcs
+ *          java.base/sun.security.x509
+ */
+
+import com.sun.jarsigner.ContentSigner;
+import com.sun.jarsigner.ContentSignerParameters;
+import jdk.testlibrary.JarUtils;
+import sun.security.pkcs.PKCS7;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.util.*;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+public class Options {
+
+    public static void main(String[] args) throws Exception {
+
+        // Prepares raw file
+        Files.write(Paths.get("a"), "a".getBytes());
+
+        // Pack
+        JarUtils.createJar("a.jar", "a");
+
+        // Prepare a keystore
+        sun.security.tools.keytool.Main.main(
+                ("-keystore jks -storepass changeit -keypass changeit -dname" +
+                        " CN=A -alias a -genkeypair -keyalg rsa").split(" "));
+
+        // -altsign
+        sun.security.tools.jarsigner.Main.main(
+                ("-debug -signedjar altsign.jar -keystore jks -storepass changeit" +
+                        " -altsigner Options$X a.jar a").split(" "));
+
+        try (JarFile jf = new JarFile("altsign.jar")) {
+            JarEntry je = jf.getJarEntry("META-INF/A.RSA");
+            try (InputStream is = jf.getInputStream(je)) {
+                if (!Arrays.equals(is.readAllBytes(), "1234".getBytes())) {
+                    throw new Exception("altsign go wrong");
+                }
+            }
+        }
+
+        // -sigfile, -digestalg, -sigalg, -internalsf, -sectionsonly
+        sun.security.tools.jarsigner.Main.main(
+                ("-debug -signedjar new.jar -keystore jks -storepass changeit" +
+                " -sigfile olala -digestalg SHA1 -sigalg SHA224withRSA" +
+                " -internalsf -sectionsonly a.jar a").split(" "));
+
+        try (JarFile jf = new JarFile("new.jar")) {
+            JarEntry je = jf.getJarEntry("META-INF/OLALA.SF");
+            Objects.requireNonNull(je);     // check -sigfile
+            byte[] sf = null;               // content of .SF
+            try (InputStream is = jf.getInputStream(je)) {
+                sf = is.readAllBytes();     // save for later comparison
+                Attributes attrs = new Manifest(new ByteArrayInputStream(sf))
+                        .getMainAttributes();
+                // check -digestalg
+                if (!attrs.containsKey(new Attributes.Name(
+                        "SHA1-Digest-Manifest-Main-Attributes"))) {
+                    throw new Exception("digestalg incorrect");
+                }
+                // check -sectionsonly
+                if (attrs.containsKey(new Attributes.Name(
+                        "SHA1-Digest-Manifest"))) {
+                    throw new Exception("SF should not have file digest");
+                }
+            }
+
+            je = jf.getJarEntry("META-INF/OLALA.RSA");
+            try (InputStream is = jf.getInputStream(je)) {
+                PKCS7 p7 = new PKCS7(is.readAllBytes());
+                String alg = p7.getSignerInfos()[0]
+                        .getDigestAlgorithmId().getName();
+                if (!alg.equals("SHA-224")) {   // check -sigalg
+                    throw new Exception("PKCS7 signing is using " + alg);
+                }
+                // check -internalsf
+                if (!Arrays.equals(sf, p7.getContentInfo().getData())) {
+                    throw new Exception("SF not in RSA");
+                }
+            }
+
+        }
+
+        // TSA-related ones are checked in ts.sh
+    }
+
+    public static class X extends ContentSigner {
+        @Override
+        public byte[] generateSignedData(ContentSignerParameters parameters,
+                boolean omitContent, boolean applyTimestamp)
+                throws NoSuchAlgorithmException, CertificateException,
+                        IOException {
+            return "1234".getBytes();
+        }
+    }
+}