# HG changeset patch
# User weijun
# Date 1427873324 -28800
#      Wed Apr 01 15:28:44 2015 +0800
# Node ID 97622267d085a05a2e14878173b6641dff2dff51
# Parent  243a90de252dc0fb2c2db8f2786cb1e1fd4df694
8056174: New APIs for jar signing

diff --git a/src/java.base/share/classes/sun/security/x509/AlgorithmId.java b/src/java.base/share/classes/sun/security/x509/AlgorithmId.java
--- a/src/java.base/share/classes/sun/security/x509/AlgorithmId.java
+++ b/src/java.base/share/classes/sun/security/x509/AlgorithmId.java
@@ -977,4 +977,30 @@
         }
         return null;
     }
+
+    // Both arg cannot be null
+    public static void checkKeyAndSigAlgMatch(String keyAlgorithm, String signatureAlgorithm) {
+        String sigAlgUpperCase = signatureAlgorithm.toUpperCase(Locale.US);
+        if ((sigAlgUpperCase.endsWith("WITHRSA") &&
+                !keyAlgorithm.equalsIgnoreCase("RSA")) ||
+                (sigAlgUpperCase.endsWith("WITHECDSA") &&
+                        !keyAlgorithm.equalsIgnoreCase("EC")) ||
+                (sigAlgUpperCase.endsWith("WITHDSA") &&
+                        !keyAlgorithm.equalsIgnoreCase("DSA"))) {
+            throw new IllegalArgumentException(
+                    "key algorithm not compatible with signature algorithm");
+        }
+    }
+
+    // Arg cannot be null, might return null
+    public static String getDefaultSigAlgForKeyAlg(String keyAlgorithm) {
+        if (keyAlgorithm.equalsIgnoreCase("DSA"))
+            return "SHA256withDSA";
+        else if (keyAlgorithm.equalsIgnoreCase("RSA"))
+            return "SHA256withRSA";
+        else if (keyAlgorithm.equalsIgnoreCase("EC"))
+            return "SHA256withECDSA";
+        else
+            return null;
+    }
 }
diff --git a/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java b/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java
new file mode 100644
--- /dev/null
+++ b/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java
@@ -0,0 +1,268 @@
+/*
+ * 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 sun.security.x509.AlgorithmId;
+import sun.security.tools.jarsigner.Builder;
+import java.io.OutputStream;
+import java.net.URI;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.cert.CertPath;
+import java.security.cert.Certificate;
+import java.util.List;
+import java.util.Objects;
+import java.util.zip.ZipFile;
+
+/**
+ * A utility class to sign a jar file.
+ * <p>
+ * A caller first creates a {@code JarSigner} object, then calls various
+ * mutators (methods except for {@link #sign}) to specify the options of
+ * signing, and finally calls {@link #sign} to sign a given file.
+ * <p>
+ * The {@link #sign} method does not change the internal state of a
+ * {@code JarSigner} object. Therefore it's safe to call multiple {@link #sign}
+ * methods on different jar files in parallel.
+ * <p>
+ * Unless otherwise stated, calling a method with a null argument will throw
+ * a {@link NullPointerException}.
+ * <p>
+ * Example:
+ * <pre>
+ * JarSigner signer = new JarSigner(key, certPath)
+ *         .digestAlg("SHA-1")
+ *         .sigAlg("SHA1withDSA");
+ * try (ZipFile in = new ZipFile(inputFile);
+ *         FileOutputStream out = new FileOutputStream(outFile)) {
+ *     signer.sign(in, out);
+ * }
+ * </pre>
+ *
+ * @since 1.9
+ */
+@jdk.Exported
+public final class JarSigner {
+
+    private final Builder builder;
+
+    /**
+     * Creates a {@code JarSigner} object with
+     * a {@link KeyStore.PrivateKeyEntry} object.
+     *
+     * @param entry the {@link KeyStore.PrivateKeyEntry} of the signer.
+     */
+    public JarSigner(KeyStore.PrivateKeyEntry entry) {
+        builder = new Builder(
+                entry.getPrivateKey(), entry.getCertificateChain());
+    }
+
+    /**
+     * Creates a {@code JarSigner} object with a private key and
+     * a certificate path.
+     *
+     * @param privateKey the private key of the signer.
+     * @param certPath the certificate 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 JarSigner(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 (1st in certPath)");
+        }
+        builder = new Builder(
+                privateKey, certs.toArray(new Certificate[certs.size()]));
+    }
+
+    /**
+     * Sets the digest algorithm. If no digest algorithm is specified,
+     * the default algorithm returned by {@link #getDefaultDigestAlg}
+     * will be used.
+     *
+     * @param algorithm the standard name of the algorithm set. See
+     *      the 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} itself.
+     * @throws NoSuchAlgorithmException if {@code algorithm} is not available.
+     */
+    public JarSigner digestAlg(String algorithm) throws NoSuchAlgorithmException {
+        builder.digestAlg(Objects.requireNonNull(algorithm), null);
+        return this;
+    }
+
+    /**
+     * Sets the digest algorithm from the specified provider.
+     * If no digest algorithm is specified, the default algorithm
+     * returned by {@link #getDefaultDigestAlg} will be used.
+     *
+     * @param algorithm the standard name of the algorithm set. See
+     *      the 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} itself.
+     * @throws NoSuchAlgorithmException if {@code algorithm} is not available
+     *      in the specified provider.
+     */
+    public JarSigner digestAlg(String algorithm, Provider provider)
+            throws NoSuchAlgorithmException {
+        builder.digestAlg(
+                Objects.requireNonNull(algorithm),
+                Objects.requireNonNull(provider));
+        return this;
+    }
+
+    /**
+     * Sets the signature algorithm. If no signature algorithm
+     * is specified, the default signature algorithm returned by
+     * {@link #getDefaultSigAlg} for the private key algorithm will be used.
+     *
+     * @param algorithm the standard name of the algorithm set. See
+     *      the 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} 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 JarSigner sigAlg(String algorithm) throws NoSuchAlgorithmException {
+        builder.sigAlg(Objects.requireNonNull(algorithm), null);
+        return this;
+    }
+
+    /**
+     * Sets the signature algorithm from the specified provider.
+     * If no signature algorithm is specified, the default signature algorithm
+     * returned by {@link #getDefaultSigAlg} for the private key algorithm
+     * will be used.
+     *
+     * @param algorithm the standard name of the algorithm set. See
+     *      the 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} 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 JarSigner sigAlg(String algorithm, Provider provider)
+            throws NoSuchAlgorithmException {
+        builder.sigAlg(
+                Objects.requireNonNull(algorithm),
+                Objects.requireNonNull(provider));
+        return this;
+    }
+
+    /**
+     * Sets the URI of the Time Stamping Authority (TSA).
+     *
+     * @param uri the URI.
+     * @return the {@code JarSigner} itself.
+     */
+    public JarSigner tsa(URI uri) {
+        builder.tsa(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} 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 JarSigner signerName(String name) {
+        builder.signerName(Objects.requireNonNull(name));
+        return this;
+    }
+
+    /**
+     * Gets the default digest algorithm.
+     *
+     * @return the default digest algorithm.
+     */
+    public static String getDefaultDigestAlg() {
+        return Builder.getDefaultDigestAlg();
+    }
+
+    /**
+     * Gets the default signature algorithm for a private key algorithm.
+     * For example, SHA256withRSA for RSA, and SHA256withDSA for DSA.
+     *
+     * @param keyAlg the key algorithm.
+     * @return the default signature algorithm. Returns null if a default
+     *      signature algorithm cannot be found. In this case, {@link #sigAlg}
+     *      must be called to specify a signature algorithm. Otherwise,
+     *      the {@link #sign} method will throw a {@link JarSignerException}.
+     * @throws IllegalArgumentException if {@code keyAlg} is empty.
+     */
+    public static String getDefaultSigAlg(String keyAlg) {
+        if (keyAlg.isEmpty()) {
+            throw new IllegalArgumentException("key algorithm cannot be empty");
+        }
+        return AlgorithmId.getDefaultSigAlgForKeyAlg(keyAlg);
+    }
+
+    /**
+     * 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) {
+        builder.build().sign(
+                Objects.requireNonNull(file),
+                Objects.requireNonNull(os));
+    }
+
+}
diff --git a/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSignerException.java b/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSignerException.java
new file mode 100644
--- /dev/null
+++ b/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSignerException.java
@@ -0,0 +1,48 @@
+/*
+ * 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 exception with a message and a cause.
+     *
+     * @param message the message.
+     * @param cause the cause.
+     */
+    public JarSignerException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
+
diff --git a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Action.java b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Action.java
new file mode 100644
--- /dev/null
+++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Action.java
@@ -0,0 +1,742 @@
+/*
+ * 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 sun.security.tools.jarsigner;
+
+import com.sun.jarsigner.ContentSigner;
+import com.sun.jarsigner.ContentSignerParameters;
+import sun.misc.IOUtils;
+import sun.security.util.ManifestDigester;
+import sun.security.util.SignatureFileVerifier;
+import sun.security.x509.AlgorithmId;
+
+import jdk.security.jarsigner.JarSigner;
+import jdk.security.jarsigner.JarSignerException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Vector;
+import java.util.jar.Attributes;
+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;
+
+/**
+ * The class that actually signs a jar.
+ *
+ * This class is separated from Builder to make sure the sign() method here
+ * does not modify any internal status of a JarSigner. All fields in Builder
+ * are duplicated here as final.
+ */
+public class Action {
+
+    private static final String META_INF = "META-INF/";
+
+    // Signer materials
+    private final PrivateKey privateKey;
+    private final X509Certificate[] certChain;
+
+    // Below are all option settings
+    // Common:
+    private final String[] digestalg;
+    private final String sigalg;
+    private final Provider digestProvider;
+    private final Provider sigProvider;
+    private final URI tsaUrl;
+    private final String signerName;
+    // Seldom:
+    private final String tSAPolicyID;
+    private final String tSADigestAlg;
+    private final X509Certificate tsaCert;
+    // Useless:
+    private final boolean signManifest; // "sign" the whole manifest
+    private final boolean externalSF; // leave the .SF out of the PKCS7 block
+    private final ContentSigner signingMechanism;
+
+    private final Builder.EventHandler handler;
+
+    public Action(Builder builder) {
+
+        this.privateKey = builder.privateKey;
+        this.certChain = builder.certChain;
+        this.digestalg = builder.digestalg;
+        this.digestProvider = builder.digestProvider;
+        this.sigalg = builder.sigalg;
+        this.sigProvider = builder.sigProvider;
+        this.tsaUrl = builder.tsaUrl;
+        this.signerName = builder.signerName;
+        this.tSAPolicyID = builder.tSAPolicyID;
+        this.tSADigestAlg = builder.tSADigestAlg;
+        this.tsaCert = builder.tsaCert;
+        this.signManifest = builder.signManifest;
+        this.externalSF = builder.externalSF;
+        this.signingMechanism = builder.signingMechanism;
+        this.handler = builder.handler;
+    }
+
+    public void sign(ZipFile zipFile, OutputStream os) {
+        try {
+            sign0(zipFile, os);
+        } catch (SocketTimeoutException | CertificateException e) {
+            throw new JarSignerException("Error applying timestamp", e);
+        } catch (IOException ioe) {
+            throw new JarSignerException("I/O error", ioe);
+        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+            throw new JarSignerException("Signer error", e);
+        } catch (SignatureException se) {
+            throw new JarSignerException("Signing error", se);
+        }
+    }
+
+    private void sign0(ZipFile zipFile, OutputStream os)
+            throws IOException, CertificateException, NoSuchAlgorithmException,
+                SignatureException, InvalidKeyException {
+        String name;
+        if (signerName == null) {
+            name = "SIGNER";
+        } else {
+            name = signerName;
+        }
+
+        MessageDigest[] digests;
+        try {
+            if (digestalg == null) {
+                digests = new MessageDigest[]{
+                        MessageDigest.getInstance(Builder.getDefaultDigestAlg())};
+            } else {
+                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 = IOUtils.readFully(
+                    zipFile.getInputStream(mfFile), -1, true);
+            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) == true) {
+                    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 (mfCreated) {
+            handler.adding(mfFile.getName());
+        } else if (mfModified) {
+            handler.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,
+                name, signManifest);
+
+        byte[] block = null;
+
+        Signature signer;
+        if (sigalg == null) {
+            String alg = JarSigner.getDefaultSigAlg(privateKey.getAlgorithm());
+            if (alg == null) {
+                throw new NoSuchAlgorithmException(
+                        "No signature alg for " + privateKey.getAlgorithm());
+            }
+            signer = Signature.getInstance(alg);
+        } else 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();
+        ContentSignerParameters params =
+                new JarSignerParameters(null, tsaUrl, tsaCert, 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 (zipFile.getEntry(sfFilename) != null) {
+            handler.updating(sfFilename);
+        } else {
+            handler.adding(sfFilename);
+        }
+
+        // signature block file
+        zos.putNextEntry(bkFile);
+        zos.write(block);
+
+        if (zipFile.getEntry(bkFilename) != null) {
+            handler.updating(bkFilename);
+        } else {
+            handler.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 (manifest.getAttributes(ze.getName()) != null) {
+                    handler.signing(ze.getName());
+                } else if (!ze.isDirectory()) {
+                    handler.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 (manifest.getAttributes(ze.getName()) != null) {
+                    handler.signing(ze.getName());
+                } else {
+                    handler.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 = ((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;
+    }
+
+    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;
+    }
+
+    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 (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);
+                }
+            }
+        }
+
+        // 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
+        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);
+        }
+    }
+
+    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;
+
+        JarSignerParameters(String[] args, URI tsa, X509Certificate tsaCertificate,
+                String tSAPolicyID, String tSADigestAlg,
+                byte[] signature, String signatureAlgorithm,
+                X509Certificate[] signerCertificateChain, byte[] content,
+                ZipFile source) {
+
+            Objects.requireNonNull(signature);
+            Objects.requireNonNull(signatureAlgorithm);
+            Objects.requireNonNull(signerCertificateChain);
+
+            if (tSADigestAlg == null) {
+                tSADigestAlg = Builder.getDefaultDigestAlg();
+            }
+
+            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;
+        }
+
+        public String[] getCommandLine() {
+            return args;
+        }
+
+        public URI getTimestampingAuthority() {
+            return tsa;
+        }
+
+        public X509Certificate getTimestampingAuthorityCertificate() {
+            return tsaCertificate;
+        }
+
+        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;
+        }
+    }
+}
diff --git a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Builder.java b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Builder.java
new file mode 100644
--- /dev/null
+++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Builder.java
@@ -0,0 +1,197 @@
+/*
+ * 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 sun.security.tools.jarsigner;
+
+import com.sun.jarsigner.ContentSigner;
+import sun.security.x509.AlgorithmId;
+
+import java.net.URI;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.Signature;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Locale;
+
+/**
+ * A utility to build an Action object that contains the actual sign method.
+ *
+ * This class exposes more public methods than JarSigner so that the jarsigner
+ * tool can be based on it. Null check, array cloning, and other checks that
+ * are usually not necessary when called internally are implemented in
+ * JarSigner. Read each method carefully.
+ */
+public class Builder {
+
+    // Signer materials
+    final PrivateKey privateKey;
+    final X509Certificate[] certChain;
+
+    // Below are all option settings
+    // Common:
+    String[] digestalg;
+    String sigalg;
+    Provider digestProvider;
+    Provider sigProvider;
+    URI tsaUrl;
+    String signerName;
+    // Seldom:
+    String tSAPolicyID;
+    String tSADigestAlg;
+    X509Certificate tsaCert;
+    // Useless:
+    boolean signManifest = true;
+    boolean externalSF = true;
+    ContentSigner signingMechanism;
+
+    EventHandler handler = new EventHandler() {};
+
+    interface EventHandler {
+        default public void updating(String s) {}
+        default public void adding(String s) {}
+        default public void signing(String s) {}
+    }
+
+    public Builder digestAlg(String alg, Provider provider)
+            throws NoSuchAlgorithmException {
+        // Check availability
+        if (provider == null) {
+            MessageDigest.getInstance(alg);
+        } else {
+            MessageDigest.getInstance(alg, provider);
+        }
+        digestalg = new String[]{alg};
+        digestProvider = provider;
+        return this;
+    }
+
+    public Builder sigAlg(String signatureAlgorithm, Provider provider)
+            throws NoSuchAlgorithmException, IllegalArgumentException {
+
+        // Check availability
+        if (provider == null) {
+            Signature.getInstance(signatureAlgorithm);
+        } else {
+            Signature.getInstance(signatureAlgorithm, provider);
+        }
+        AlgorithmId.checkKeyAndSigAlgMatch(
+                privateKey.getAlgorithm(), signatureAlgorithm);
+        this.sigalg = signatureAlgorithm;
+        this.sigProvider = provider;
+        return this;
+    }
+
+    public Builder tsa(URI uri) {
+        this.tsaUrl = uri;
+        return this;
+    }
+
+    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;
+    }
+
+    public static String getDefaultDigestAlg() {
+        return "SHA-256";
+    }
+
+    // Methods not exposed by JarSigner (yet)
+    public Builder tsaCert(X509Certificate tsaCert) {
+        this.tsaCert = tsaCert;
+        return this;
+    }
+
+    public Builder tsaPolicyId(String oid) {
+        this.tSAPolicyID = oid;
+        return this;
+    }
+
+    public Builder tsaDigestAlg(String alg) throws NoSuchAlgorithmException {
+        if (alg != null) {
+            MessageDigest.getInstance(alg);
+        }
+        this.tSADigestAlg = alg;
+        return this;
+    }
+
+    public Builder altsigner(com.sun.jarsigner.ContentSigner signer) {
+        signingMechanism = signer;
+        return this;
+    }
+
+    public Builder sectionsOnly(boolean v) {
+        signManifest = !v;
+        return this;
+    }
+
+    public Builder internalSF(boolean v) {
+        externalSF = !v;
+        return this;
+    }
+
+    public Builder setEventHandler(EventHandler a) {
+        this.handler = a == null ? new EventHandler() {}: a;
+        return this;
+    }
+
+    public String getDefaultTsaDigestAlg() {
+        return getDefaultDigestAlg();
+    }
+
+    // Constructor
+
+    public Builder(PrivateKey key, Certificate... certs) {
+        this.privateKey = key;
+        this.certChain = new X509Certificate[certs.length];
+        for (int i=0; i<certs.length; i++) {
+            certChain[i] = (X509Certificate)certs[i];
+        }
+    }
+
+    // Builder
+
+    public Action build() {
+        return new Action(this);
+    }
+}
diff --git a/test/sun/security/tools/jarsigner/API.java b/test/sun/security/tools/jarsigner/API.java
new file mode 100644
--- /dev/null
+++ b/test/sun/security/tools/jarsigner/API.java
@@ -0,0 +1,177 @@
+/*
+ * 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 New APIs for jar signing
+ */
+
+import jdk.security.jarsigner.JarSigner;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+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.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+public class API {
+    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();
+        }
+
+        KeyStore ks = KeyStore.getInstance("JKS");
+        ks.load(new FileInputStream(
+                new File(System.getProperty("test.src"), "JarSigning.keystore")),
+                "bbbbbb".toCharArray());
+        PrivateKey key = (PrivateKey)ks.getKey("c", "bbbbbb".toCharArray());
+        Certificate cert = ks.getCertificate("c");
+        JarSigner js = new JarSigner(key,
+                CertificateFactory.getInstance("X.509").generateCertPath(
+                        Collections.singletonList(cert)));
+
+        try {
+            js.digestAlg("FUNNY");
+            throw new Exception("Should have failed");
+        } catch (NoSuchAlgorithmException e) {
+            // Good
+        }
+
+        try {
+            js.sigAlg("SHA1withDSA");
+            throw new Exception("Should have failed");
+        } catch (IllegalArgumentException e) {
+            // Good
+        }
+
+        try {
+            js.sigAlg("FUNwithJOY");
+            throw new Exception("Should have failed");
+        } catch (NoSuchAlgorithmException e) {
+            // Good
+        }
+
+        js.digestAlg("SHA1");
+        js.sigAlg("SHA1withRSA");
+
+        try {
+            js.signerName("");
+            throw new Exception("Should have failed");
+        } catch (IllegalArgumentException e) {
+            // I knew that
+        }
+
+        try {
+            js.signerName("this_is_long");
+            throw new Exception("Should have failed");
+        } catch (IllegalArgumentException e) {
+            // I knew that
+        }
+
+        try {
+            js.signerName("illegal!");
+            throw new Exception("Should have failed");
+        } catch (IllegalArgumentException e) {
+            // I knew that
+        }
+
+        OutputStream blackHole = new OutputStream() {
+            @Override
+            public void write(int b) throws IOException {
+                return;
+            }
+        };
+
+        try (ZipFile src = new ZipFile("src.zip")) {
+            js.sign(src, blackHole);
+        }
+
+
+        Provider p = new MyProvider();
+        js.digestAlg("Five", p);
+        js.sigAlg("SHA1WithRSA", p);
+        try (ZipFile src = new ZipFile("src.zip")) {
+            js.sign(src, blackHole);
+        }
+    }
+
+    public static class MyProvider extends Provider {
+        MyProvider() {
+            super("MY", 1.0d, null);
+            put("MessageDigest.Five", Five.class.getName());
+            put("Signature.SHA1WithRSA", SHA1WithRSA.class.getName());
+        }
+    }
+
+    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() { }
+    }
+
+    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; }
+    }
+}