jdk/src/share/classes/sun/security/tools/jarsigner/Main.java
changeset 14182 3041082abb40
parent 12046 378aa3362868
child 14421 a64b2cc9d429
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/security/tools/jarsigner/Main.java	Sun Oct 14 22:58:59 2012 +0100
@@ -0,0 +1,2469 @@
+/*
+ * Copyright (c) 1997, 2012, 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 java.io.*;
+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;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.PKIXParameters;
+import java.security.cert.TrustAnchor;
+import java.util.Map.Entry;
+import sun.security.tools.KeyStoreUtil;
+import sun.security.tools.PathList;
+import sun.security.x509.*;
+import sun.security.util.*;
+import sun.misc.BASE64Encoder;
+
+
+/**
+ * <p>The jarsigner utility.
+ *
+ * The exit codes for the main method are:
+ *
+ * 0: success
+ * 1: any error that the jar cannot be signed or verified, including:
+ *      keystore loading error
+ *      TSP communication error
+ *      jarsigner command line error...
+ * otherwise: error codes from -strict
+ *
+ * @author Roland Schemers
+ * @author Jan Luehe
+ */
+
+public class Main {
+
+    // for i18n
+    private static final java.util.ResourceBundle rb =
+        java.util.ResourceBundle.getBundle
+        ("sun.security.tools.jarsigner.Resources");
+    private static final Collator collator = Collator.getInstance();
+    static {
+        // this is for case insensitive string comparisions
+        collator.setStrength(Collator.PRIMARY);
+    }
+
+    private static final String META_INF = "META-INF/";
+
+    // prefix for new signature-related files in META-INF directory
+    private static final String SIG_PREFIX = META_INF + "SIG-";
+
+    private static final Class[] PARAM_STRING = { String.class };
+
+    private static final String NONE = "NONE";
+    private static final String P11KEYSTORE = "PKCS11";
+
+    private static final long SIX_MONTHS = 180*24*60*60*1000L; //milliseconds
+
+    // Attention:
+    // This is the entry that get launched by the security tool jarsigner.
+    public static void main(String args[]) throws Exception {
+        Main js = new Main();
+        js.run(args);
+    }
+
+    static final String VERSION = "1.0";
+
+    static final int IN_KEYSTORE = 0x01;        // signer is in keystore
+    static final int IN_SCOPE = 0x02;
+    static final int NOT_ALIAS = 0x04;          // alias list is NOT empty and
+                                                // signer is not in alias list
+    static final int SIGNED_BY_ALIAS = 0x08;    // signer is in alias list
+
+    X509Certificate[] certChain;    // signer's cert chain (when composing)
+    PrivateKey privateKey;          // private key
+    KeyStore store;                 // the keystore specified by -keystore
+                                    // or the default keystore, never null
+
+    String keystore; // key store file
+    boolean nullStream = false; // null keystore input stream (NONE)
+    boolean token = false; // token-based keystore
+    String jarfile;  // jar files to sign or verify
+    String alias;    // alias to sign jar with
+    List<String> ckaliases = new ArrayList<>(); // aliases in -verify
+    char[] storepass; // keystore password
+    boolean protectedPath; // protected authentication path
+    String storetype; // keystore type
+    String providerName; // provider name
+    Vector<String> providers = null; // list of providers
+    // arguments for provider constructors
+    HashMap<String,String> providerArgs = new HashMap<>();
+    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 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
+    boolean verify = false; // verify the jar
+    String verbose = null; // verbose output when signing/verifying
+    boolean showcerts = false; // show certs when verifying
+    boolean debug = false; // debug
+    boolean signManifest = true; // "sign" the whole manifest
+    boolean externalSF = true; // leave the .SF out of the PKCS7 block
+    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;
+
+    private boolean hasExpiredCert = false;
+    private boolean hasExpiringCert = false;
+    private boolean notYetValidCert = false;
+    private boolean chainNotValidated = false;
+    private boolean notSignedByAlias = false;
+    private boolean aliasNotInStore = false;
+    private boolean hasUnsignedEntry = false;
+    private boolean badKeyUsage = false;
+    private boolean badExtendedKeyUsage = false;
+    private boolean badNetscapeCertType = false;
+
+    CertificateFactory certificateFactory;
+    CertPathValidator validator;
+    PKIXParameters pkixParameters;
+
+    public void run(String args[]) {
+        try {
+            parseArgs(args);
+
+            // Try to load and install the specified providers
+            if (providers != null) {
+                ClassLoader cl = ClassLoader.getSystemClassLoader();
+                Enumeration<String> e = providers.elements();
+                while (e.hasMoreElements()) {
+                    String provName = e.nextElement();
+                    Class<?> provClass;
+                    if (cl != null) {
+                        provClass = cl.loadClass(provName);
+                    } else {
+                        provClass = Class.forName(provName);
+                    }
+
+                    String provArg = providerArgs.get(provName);
+                    Object obj;
+                    if (provArg == null) {
+                        obj = provClass.newInstance();
+                    } else {
+                        Constructor<?> c =
+                                provClass.getConstructor(PARAM_STRING);
+                        obj = c.newInstance(provArg);
+                    }
+
+                    if (!(obj instanceof Provider)) {
+                        MessageFormat form = new MessageFormat(rb.getString
+                            ("provName.not.a.provider"));
+                        Object[] source = {provName};
+                        throw new Exception(form.format(source));
+                    }
+                    Security.addProvider((Provider)obj);
+                }
+            }
+
+            if (verify) {
+                try {
+                    loadKeyStore(keystore, false);
+                } catch (Exception e) {
+                    if ((keystore != null) || (storepass != null)) {
+                        System.out.println(rb.getString("jarsigner.error.") +
+                                        e.getMessage());
+                        System.exit(1);
+                    }
+                }
+                /*              if (debug) {
+                    SignatureFileVerifier.setDebug(true);
+                    ManifestEntryVerifier.setDebug(true);
+                }
+                */
+                verifyJar(jarfile);
+            } else {
+                loadKeyStore(keystore, true);
+                getAliasInfo(alias);
+
+                // load the alternative signing mechanism
+                if (altSignerClass != null) {
+                    signingMechanism = loadSigningMechanism(altSignerClass,
+                        altSignerClasspath);
+                }
+                signJar(jarfile, alias, args);
+            }
+        } catch (Exception e) {
+            System.out.println(rb.getString("jarsigner.error.") + e);
+            if (debug) {
+                e.printStackTrace();
+            }
+            System.exit(1);
+        } finally {
+            // zero-out private key password
+            if (keypass != null) {
+                Arrays.fill(keypass, ' ');
+                keypass = null;
+            }
+            // zero-out keystore password
+            if (storepass != null) {
+                Arrays.fill(storepass, ' ');
+                storepass = null;
+            }
+        }
+
+        if (strict) {
+            int exitCode = 0;
+            if (hasExpiringCert) {
+                exitCode |= 2;
+            }
+            if (chainNotValidated || hasExpiredCert || notYetValidCert) {
+                exitCode |= 4;
+            }
+            if (badKeyUsage || badExtendedKeyUsage || badNetscapeCertType) {
+                exitCode |= 8;
+            }
+            if (hasUnsignedEntry) {
+                exitCode |= 16;
+            }
+            if (notSignedByAlias || aliasNotInStore) {
+                exitCode |= 32;
+            }
+            if (exitCode != 0) {
+                System.exit(exitCode);
+            }
+        }
+    }
+
+    /*
+     * Parse command line arguments.
+     */
+    void parseArgs(String args[]) {
+        /* parse flags */
+        int n = 0;
+
+        if (args.length == 0) fullusage();
+        for (n=0; n < args.length; n++) {
+
+            String flags = args[n];
+            String modifier = null;
+            if (flags.charAt(0) == '-') {
+                int pos = flags.indexOf(':');
+                if (pos > 0) {
+                    modifier = flags.substring(pos+1);
+                    flags = flags.substring(0, pos);
+                }
+            }
+
+            if (collator.compare(flags, "-keystore") == 0) {
+                if (++n == args.length) usageNoArg();
+                keystore = args[n];
+            } else if (collator.compare(flags, "-storepass") ==0) {
+                if (++n == args.length) usageNoArg();
+                storepass = getPass(modifier, args[n]);
+            } else if (collator.compare(flags, "-storetype") ==0) {
+                if (++n == args.length) usageNoArg();
+                storetype = args[n];
+            } else if (collator.compare(flags, "-providerName") ==0) {
+                if (++n == args.length) usageNoArg();
+                providerName = args[n];
+            } else if ((collator.compare(flags, "-provider") == 0) ||
+                        (collator.compare(flags, "-providerClass") == 0)) {
+                if (++n == args.length) usageNoArg();
+                if (providers == null) {
+                    providers = new Vector<String>(3);
+                }
+                providers.add(args[n]);
+
+                if (args.length > (n+1)) {
+                    flags = args[n+1];
+                    if (collator.compare(flags, "-providerArg") == 0) {
+                        if (args.length == (n+2)) usageNoArg();
+                        providerArgs.put(args[n], args[n+2]);
+                        n += 2;
+                    }
+                }
+            } else if (collator.compare(flags, "-protected") ==0) {
+                protectedPath = true;
+            } else if (collator.compare(flags, "-certchain") ==0) {
+                if (++n == args.length) usageNoArg();
+                altCertChain = args[n];
+            } else if (collator.compare(flags, "-debug") ==0) {
+                debug = true;
+            } else if (collator.compare(flags, "-keypass") ==0) {
+                if (++n == args.length) usageNoArg();
+                keypass = getPass(modifier, args[n]);
+            } else if (collator.compare(flags, "-sigfile") ==0) {
+                if (++n == args.length) usageNoArg();
+                sigfile = args[n];
+            } else if (collator.compare(flags, "-signedjar") ==0) {
+                if (++n == args.length) usageNoArg();
+                signedjar = args[n];
+            } else if (collator.compare(flags, "-tsa") ==0) {
+                if (++n == args.length) usageNoArg();
+                tsaUrl = args[n];
+            } else if (collator.compare(flags, "-tsacert") ==0) {
+                if (++n == args.length) usageNoArg();
+                tsaAlias = args[n];
+            } else if (collator.compare(flags, "-altsigner") ==0) {
+                if (++n == args.length) usageNoArg();
+                altSignerClass = args[n];
+            } else if (collator.compare(flags, "-altsignerpath") ==0) {
+                if (++n == args.length) usageNoArg();
+                altSignerClasspath = args[n];
+            } else if (collator.compare(flags, "-sectionsonly") ==0) {
+                signManifest = false;
+            } else if (collator.compare(flags, "-internalsf") ==0) {
+                externalSF = false;
+            } else if (collator.compare(flags, "-verify") ==0) {
+                verify = true;
+            } else if (collator.compare(flags, "-verbose") ==0) {
+                verbose = (modifier != null) ? modifier : "all";
+            } else if (collator.compare(flags, "-sigalg") ==0) {
+                if (++n == args.length) usageNoArg();
+                sigalg = args[n];
+            } else if (collator.compare(flags, "-digestalg") ==0) {
+                if (++n == args.length) usageNoArg();
+                digestalg = args[n];
+            } else if (collator.compare(flags, "-certs") ==0) {
+                showcerts = true;
+            } else if (collator.compare(flags, "-strict") ==0) {
+                strict = true;
+            } else if (collator.compare(flags, "-h") == 0 ||
+                        collator.compare(flags, "-help") == 0) {
+                fullusage();
+            } else {
+                if (!flags.startsWith("-")) {
+                    if (jarfile == null) {
+                        jarfile = flags;
+                    } else {
+                        alias = flags;
+                        ckaliases.add(alias);
+                    }
+                } else {
+                    System.err.println(
+                            rb.getString("Illegal.option.") + flags);
+                    usage();
+                }
+            }
+        }
+
+        // -certs must always be specified with -verbose
+        if (verbose == null) showcerts = false;
+
+        if (jarfile == null) {
+            System.err.println(rb.getString("Please.specify.jarfile.name"));
+            usage();
+        }
+        if (!verify && alias == null) {
+            System.err.println(rb.getString("Please.specify.alias.name"));
+            usage();
+        }
+        if (!verify && ckaliases.size() > 1) {
+            System.err.println(rb.getString("Only.one.alias.can.be.specified"));
+            usage();
+        }
+
+        if (storetype == null) {
+            storetype = KeyStore.getDefaultType();
+        }
+        storetype = KeyStoreUtil.niceStoreTypeName(storetype);
+
+        try {
+            if (signedjar != null && new File(signedjar).getCanonicalPath().equals(
+                    new File(jarfile).getCanonicalPath())) {
+                signedjar = null;
+            }
+        } catch (IOException ioe) {
+            // File system error?
+            // Just ignore it.
+        }
+
+        if (P11KEYSTORE.equalsIgnoreCase(storetype) ||
+                KeyStoreUtil.isWindowsKeyStore(storetype)) {
+            token = true;
+            if (keystore == null) {
+                keystore = NONE;
+            }
+        }
+
+        if (NONE.equals(keystore)) {
+            nullStream = true;
+        }
+
+        if (token && !nullStream) {
+            System.err.println(MessageFormat.format(rb.getString
+                (".keystore.must.be.NONE.if.storetype.is.{0}"), storetype));
+            usage();
+        }
+
+        if (token && keypass != null) {
+            System.err.println(MessageFormat.format(rb.getString
+                (".keypass.can.not.be.specified.if.storetype.is.{0}"), storetype));
+            usage();
+        }
+
+        if (protectedPath) {
+            if (storepass != null || keypass != null) {
+                System.err.println(rb.getString
+                        ("If.protected.is.specified.then.storepass.and.keypass.must.not.be.specified"));
+                usage();
+            }
+        }
+        if (KeyStoreUtil.isWindowsKeyStore(storetype)) {
+            if (storepass != null || keypass != null) {
+                System.err.println(rb.getString
+                        ("If.keystore.is.not.password.protected.then.storepass.and.keypass.must.not.be.specified"));
+                usage();
+            }
+        }
+    }
+
+    static char[] getPass(String modifier, String arg) {
+        char[] output = KeyStoreUtil.getPassWithModifier(modifier, arg, rb);
+        if (output != null) return output;
+        usage();
+        return null;    // Useless, usage() already exit
+    }
+
+    static void usageNoArg() {
+        System.out.println(rb.getString("Option.lacks.argument"));
+        usage();
+    }
+
+    static void usage() {
+        System.out.println();
+        System.out.println(rb.getString("Please.type.jarsigner.help.for.usage"));
+        System.exit(1);
+    }
+
+    static void fullusage() {
+        System.out.println(rb.getString
+                ("Usage.jarsigner.options.jar.file.alias"));
+        System.out.println(rb.getString
+                (".jarsigner.verify.options.jar.file.alias."));
+        System.out.println();
+        System.out.println(rb.getString
+                (".keystore.url.keystore.location"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".storepass.password.password.for.keystore.integrity"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".storetype.type.keystore.type"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".keypass.password.password.for.private.key.if.different."));
+        System.out.println();
+        System.out.println(rb.getString
+                (".certchain.file.name.of.alternative.certchain.file"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".sigfile.file.name.of.SF.DSA.file"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".signedjar.file.name.of.signed.JAR.file"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".digestalg.algorithm.name.of.digest.algorithm"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".sigalg.algorithm.name.of.signature.algorithm"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".verify.verify.a.signed.JAR.file"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".verbose.suboptions.verbose.output.when.signing.verifying."));
+        System.out.println(rb.getString
+                (".suboptions.can.be.all.grouped.or.summary"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".certs.display.certificates.when.verbose.and.verifying"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".tsa.url.location.of.the.Timestamping.Authority"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".tsacert.alias.public.key.certificate.for.Timestamping.Authority"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".altsigner.class.class.name.of.an.alternative.signing.mechanism"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".altsignerpath.pathlist.location.of.an.alternative.signing.mechanism"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".internalsf.include.the.SF.file.inside.the.signature.block"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".sectionsonly.don.t.compute.hash.of.entire.manifest"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".protected.keystore.has.protected.authentication.path"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".providerName.name.provider.name"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".providerClass.class.name.of.cryptographic.service.provider.s"));
+        System.out.println(rb.getString
+                (".providerArg.arg.master.class.file.and.constructor.argument"));
+        System.out.println();
+        System.out.println(rb.getString
+                (".strict.treat.warnings.as.errors"));
+        System.out.println();
+
+        System.exit(0);
+    }
+
+    void verifyJar(String jarName)
+        throws Exception
+    {
+        boolean anySigned = false;  // if there exists entry inside jar signed
+        JarFile jf = null;
+
+        try {
+            jf = new JarFile(jarName, true);
+            Vector<JarEntry> entriesVec = new Vector<>();
+            byte[] buffer = new byte[8192];
+
+            Enumeration<JarEntry> entries = jf.entries();
+            while (entries.hasMoreElements()) {
+                JarEntry je = entries.nextElement();
+                entriesVec.addElement(je);
+                InputStream is = null;
+                try {
+                    is = jf.getInputStream(je);
+                    int n;
+                    while ((n = is.read(buffer, 0, buffer.length)) != -1) {
+                        // we just read. this will throw a SecurityException
+                        // if  a signature/digest check fails.
+                    }
+                } finally {
+                    if (is != null) {
+                        is.close();
+                    }
+                }
+            }
+
+            Manifest man = jf.getManifest();
+
+            // The map to record display info, only used when -verbose provided
+            //      key: signer info string
+            //      value: the list of files with common key
+            Map<String,List<String>> output = new LinkedHashMap<>();
+
+            if (man != null) {
+                if (verbose != null) System.out.println();
+                Enumeration<JarEntry> e = entriesVec.elements();
+
+                String tab = rb.getString("6SPACE");
+
+                while (e.hasMoreElements()) {
+                    JarEntry je = e.nextElement();
+                    String name = je.getName();
+                    CodeSigner[] signers = je.getCodeSigners();
+                    boolean isSigned = (signers != null);
+                    anySigned |= isSigned;
+                    hasUnsignedEntry |= !je.isDirectory() && !isSigned
+                                        && !signatureRelated(name);
+
+                    int inStoreOrScope = inKeyStore(signers);
+
+                    boolean inStore = (inStoreOrScope & IN_KEYSTORE) != 0;
+                    boolean inScope = (inStoreOrScope & IN_SCOPE) != 0;
+
+                    notSignedByAlias |= (inStoreOrScope & NOT_ALIAS) != 0;
+                    if (keystore != null) {
+                        aliasNotInStore |= isSigned && (!inStore && !inScope);
+                    }
+
+                    // Only used when -verbose provided
+                    StringBuffer sb = null;
+                    if (verbose != null) {
+                        sb = new StringBuffer();
+                        boolean inManifest =
+                            ((man.getAttributes(name) != null) ||
+                             (man.getAttributes("./"+name) != null) ||
+                             (man.getAttributes("/"+name) != null));
+                        sb.append(
+                          (isSigned ? rb.getString("s") : rb.getString("SPACE")) +
+                          (inManifest ? rb.getString("m") : rb.getString("SPACE")) +
+                          (inStore ? rb.getString("k") : rb.getString("SPACE")) +
+                          (inScope ? rb.getString("i") : rb.getString("SPACE")) +
+                          ((inStoreOrScope & NOT_ALIAS) != 0 ?"X":" ") +
+                          rb.getString("SPACE"));
+                        sb.append("|");
+                    }
+
+                    // When -certs provided, display info has extra empty
+                    // lines at the beginning and end.
+                    if (isSigned) {
+                        if (showcerts) sb.append('\n');
+                        for (CodeSigner signer: signers) {
+                            // signerInfo() must be called even if -verbose
+                            // not provided. The method updates various
+                            // warning flags.
+                            String si = signerInfo(signer, tab);
+                            if (showcerts) {
+                                sb.append(si);
+                                sb.append('\n');
+                            }
+                        }
+                    } else if (showcerts && !verbose.equals("all")) {
+                        // Print no info for unsigned entries when -verbose:all,
+                        // to be consistent with old behavior.
+                        if (signatureRelated(name)) {
+                            sb.append("\n" + tab + rb.getString(
+                                    ".Signature.related.entries.") + "\n\n");
+                        } else {
+                            sb.append("\n" + tab + rb.getString(
+                                    ".Unsigned.entries.") + "\n\n");
+                        }
+                    }
+
+                    if (verbose != null) {
+                        String label = sb.toString();
+                        if (signatureRelated(name)) {
+                            // Entries inside META-INF and other unsigned
+                            // entries are grouped separately.
+                            label = "-" + label;
+                        }
+
+                        // The label finally contains 2 parts separated by '|':
+                        // The legend displayed before the entry names, and
+                        // the cert info (if -certs specfied).
+
+                        if (!output.containsKey(label)) {
+                            output.put(label, new ArrayList<String>());
+                        }
+
+                        StringBuffer fb = new StringBuffer();
+                        String s = Long.toString(je.getSize());
+                        for (int i = 6 - s.length(); i > 0; --i) {
+                            fb.append(' ');
+                        }
+                        fb.append(s).append(' ').
+                                append(new Date(je.getTime()).toString());
+                        fb.append(' ').append(name);
+
+                        output.get(label).add(fb.toString());
+                    }
+                }
+            }
+            if (verbose != null) {
+                for (Entry<String,List<String>> s: output.entrySet()) {
+                    List<String> files = s.getValue();
+                    String key = s.getKey();
+                    if (key.charAt(0) == '-') { // the signature-related group
+                        key = key.substring(1);
+                    }
+                    int pipe = key.indexOf('|');
+                    if (verbose.equals("all")) {
+                        for (String f: files) {
+                            System.out.println(key.substring(0, pipe) + f);
+                            System.out.printf(key.substring(pipe+1));
+                        }
+                    } else {
+                        if (verbose.equals("grouped")) {
+                            for (String f: files) {
+                                System.out.println(key.substring(0, pipe) + f);
+                            }
+                        } else if (verbose.equals("summary")) {
+                            System.out.print(key.substring(0, pipe));
+                            if (files.size() > 1) {
+                                System.out.println(files.get(0) + " " +
+                                        String.format(rb.getString(
+                                        ".and.d.more."), files.size()-1));
+                            } else {
+                                System.out.println(files.get(0));
+                            }
+                        }
+                        System.out.printf(key.substring(pipe+1));
+                    }
+                }
+                System.out.println();
+                System.out.println(rb.getString(
+                    ".s.signature.was.verified."));
+                System.out.println(rb.getString(
+                    ".m.entry.is.listed.in.manifest"));
+                System.out.println(rb.getString(
+                    ".k.at.least.one.certificate.was.found.in.keystore"));
+                System.out.println(rb.getString(
+                    ".i.at.least.one.certificate.was.found.in.identity.scope"));
+                if (ckaliases.size() > 0) {
+                    System.out.println(rb.getString(
+                        ".X.not.signed.by.specified.alias.es."));
+                }
+                System.out.println();
+            }
+            if (man == null)
+                System.out.println(rb.getString("no.manifest."));
+
+            if (!anySigned) {
+                System.out.println(rb.getString(
+                      "jar.is.unsigned.signatures.missing.or.not.parsable."));
+            } else {
+                System.out.println(rb.getString("jar.verified."));
+                if (hasUnsignedEntry || hasExpiredCert || hasExpiringCert ||
+                    badKeyUsage || badExtendedKeyUsage || badNetscapeCertType ||
+                    notYetValidCert || chainNotValidated ||
+                    aliasNotInStore || notSignedByAlias) {
+
+                    System.out.println();
+                    System.out.println(rb.getString("Warning."));
+                    if (badKeyUsage) {
+                        System.out.println(
+                            rb.getString("This.jar.contains.entries.whose.signer.certificate.s.KeyUsage.extension.doesn.t.allow.code.signing."));
+                    }
+
+                    if (badExtendedKeyUsage) {
+                        System.out.println(
+                            rb.getString("This.jar.contains.entries.whose.signer.certificate.s.ExtendedKeyUsage.extension.doesn.t.allow.code.signing."));
+                    }
+
+                    if (badNetscapeCertType) {
+                        System.out.println(
+                            rb.getString("This.jar.contains.entries.whose.signer.certificate.s.NetscapeCertType.extension.doesn.t.allow.code.signing."));
+                    }
+
+                    if (hasUnsignedEntry) {
+                        System.out.println(rb.getString(
+                            "This.jar.contains.unsigned.entries.which.have.not.been.integrity.checked."));
+                    }
+                    if (hasExpiredCert) {
+                        System.out.println(rb.getString(
+                            "This.jar.contains.entries.whose.signer.certificate.has.expired."));
+                    }
+                    if (hasExpiringCert) {
+                        System.out.println(rb.getString(
+                            "This.jar.contains.entries.whose.signer.certificate.will.expire.within.six.months."));
+                    }
+                    if (notYetValidCert) {
+                        System.out.println(rb.getString(
+                            "This.jar.contains.entries.whose.signer.certificate.is.not.yet.valid."));
+                    }
+
+                    if (chainNotValidated) {
+                        System.out.println(
+                                rb.getString("This.jar.contains.entries.whose.certificate.chain.is.not.validated."));
+                    }
+
+                    if (notSignedByAlias) {
+                        System.out.println(
+                                rb.getString("This.jar.contains.signed.entries.which.is.not.signed.by.the.specified.alias.es."));
+                    }
+
+                    if (aliasNotInStore) {
+                        System.out.println(rb.getString("This.jar.contains.signed.entries.that.s.not.signed.by.alias.in.this.keystore."));
+                    }
+                    if (! (verbose != null && showcerts)) {
+                        System.out.println();
+                        System.out.println(rb.getString(
+                            "Re.run.with.the.verbose.and.certs.options.for.more.details."));
+                    }
+                }
+            }
+            return;
+        } catch (Exception e) {
+            System.out.println(rb.getString("jarsigner.") + e);
+            if (debug) {
+                e.printStackTrace();
+            }
+        } finally { // close the resource
+            if (jf != null) {
+                jf.close();
+            }
+        }
+
+        System.exit(1);
+    }
+
+    private static MessageFormat validityTimeForm = null;
+    private static MessageFormat notYetTimeForm = null;
+    private static MessageFormat expiredTimeForm = null;
+    private static MessageFormat expiringTimeForm = null;
+
+    /*
+     * Display some details about a certificate:
+     *
+     * [<tab>] <cert-type> [", " <subject-DN>] [" (" <keystore-entry-alias> ")"]
+     * [<validity-period> | <expiry-warning>]
+     *
+     * Note: no newline character at the end
+     */
+    String printCert(String tab, Certificate c, boolean checkValidityPeriod,
+        Date timestamp, boolean checkUsage) {
+
+        StringBuilder certStr = new StringBuilder();
+        String space = rb.getString("SPACE");
+        X509Certificate x509Cert = null;
+
+        if (c instanceof X509Certificate) {
+            x509Cert = (X509Certificate) c;
+            certStr.append(tab).append(x509Cert.getType())
+                .append(rb.getString("COMMA"))
+                .append(x509Cert.getSubjectDN().getName());
+        } else {
+            certStr.append(tab).append(c.getType());
+        }
+
+        String alias = storeHash.get(c);
+        if (alias != null) {
+            certStr.append(space).append(alias);
+        }
+
+        if (checkValidityPeriod && x509Cert != null) {
+
+            certStr.append("\n").append(tab).append("[");
+            Date notAfter = x509Cert.getNotAfter();
+            try {
+                boolean printValidity = true;
+                if (timestamp == null) {
+                    x509Cert.checkValidity();
+                    // test if cert will expire within six months
+                    if (notAfter.getTime() < System.currentTimeMillis() + SIX_MONTHS) {
+                        hasExpiringCert = true;
+                        if (expiringTimeForm == null) {
+                            expiringTimeForm = new MessageFormat(
+                                rb.getString("certificate.will.expire.on"));
+                        }
+                        Object[] source = { notAfter };
+                        certStr.append(expiringTimeForm.format(source));
+                        printValidity = false;
+                    }
+                } else {
+                    x509Cert.checkValidity(timestamp);
+                }
+                if (printValidity) {
+                    if (validityTimeForm == null) {
+                        validityTimeForm = new MessageFormat(
+                            rb.getString("certificate.is.valid.from"));
+                    }
+                    Object[] source = { x509Cert.getNotBefore(), notAfter };
+                    certStr.append(validityTimeForm.format(source));
+                }
+            } catch (CertificateExpiredException cee) {
+                hasExpiredCert = true;
+
+                if (expiredTimeForm == null) {
+                    expiredTimeForm = new MessageFormat(
+                        rb.getString("certificate.expired.on"));
+                }
+                Object[] source = { notAfter };
+                certStr.append(expiredTimeForm.format(source));
+
+            } catch (CertificateNotYetValidException cnyve) {
+                notYetValidCert = true;
+
+                if (notYetTimeForm == null) {
+                    notYetTimeForm = new MessageFormat(
+                        rb.getString("certificate.is.not.valid.until"));
+                }
+                Object[] source = { x509Cert.getNotBefore() };
+                certStr.append(notYetTimeForm.format(source));
+            }
+            certStr.append("]");
+
+            if (checkUsage) {
+                boolean[] bad = new boolean[3];
+                checkCertUsage(x509Cert, bad);
+                if (bad[0] || bad[1] || bad[2]) {
+                    String x = "";
+                    if (bad[0]) {
+                        x ="KeyUsage";
+                    }
+                    if (bad[1]) {
+                        if (x.length() > 0) x = x + ", ";
+                        x = x + "ExtendedKeyUsage";
+                    }
+                    if (bad[2]) {
+                        if (x.length() > 0) x = x + ", ";
+                        x = x + "NetscapeCertType";
+                    }
+                    certStr.append("\n").append(tab)
+                        .append(MessageFormat.format(rb.getString(
+                        ".{0}.extension.does.not.support.code.signing."), x));
+                }
+            }
+        }
+        return certStr.toString();
+    }
+
+    private static MessageFormat signTimeForm = null;
+
+    private String printTimestamp(String tab, Timestamp timestamp) {
+
+        if (signTimeForm == null) {
+            signTimeForm =
+                new MessageFormat(rb.getString("entry.was.signed.on"));
+        }
+        Object[] source = { timestamp.getTimestamp() };
+
+        return new StringBuilder().append(tab).append("[")
+            .append(signTimeForm.format(source)).append("]").toString();
+    }
+
+    private Map<CodeSigner,Integer> cacheForInKS = new IdentityHashMap<>();
+
+    private int inKeyStoreForOneSigner(CodeSigner signer) {
+        if (cacheForInKS.containsKey(signer)) {
+            return cacheForInKS.get(signer);
+        }
+
+        boolean found = false;
+        int result = 0;
+        List<? extends Certificate> certs = signer.getSignerCertPath().getCertificates();
+        for (Certificate c : certs) {
+            String alias = storeHash.get(c);
+            if (alias != null) {
+                if (alias.startsWith("(")) {
+                    result |= IN_KEYSTORE;
+                } else if (alias.startsWith("[")) {
+                    result |= IN_SCOPE;
+                }
+                if (ckaliases.contains(alias.substring(1, alias.length() - 1))) {
+                    result |= SIGNED_BY_ALIAS;
+                }
+            } else {
+                if (store != null) {
+                    try {
+                        alias = store.getCertificateAlias(c);
+                    } catch (KeyStoreException kse) {
+                        // never happens, because keystore has been loaded
+                    }
+                    if (alias != null) {
+                        storeHash.put(c, "(" + alias + ")");
+                        found = true;
+                        result |= IN_KEYSTORE;
+                    }
+                }
+                if (ckaliases.contains(alias)) {
+                    result |= SIGNED_BY_ALIAS;
+                }
+            }
+        }
+        cacheForInKS.put(signer, result);
+        return result;
+    }
+
+    Hashtable<Certificate, String> storeHash = new Hashtable<>();
+
+    int inKeyStore(CodeSigner[] signers) {
+
+        if (signers == null)
+            return 0;
+
+        int output = 0;
+
+        for (CodeSigner signer: signers) {
+            int result = inKeyStoreForOneSigner(signer);
+            output |= result;
+        }
+        if (ckaliases.size() > 0 && (output & SIGNED_BY_ALIAS) == 0) {
+            output |= NOT_ALIAS;
+        }
+        return output;
+    }
+
+    void signJar(String jarName, String alias, String[] args)
+        throws Exception {
+        boolean aliasUsed = false;
+        X509Certificate tsaCert = null;
+
+        if (sigfile == null) {
+            sigfile = alias;
+            aliasUsed = true;
+        }
+
+        if (sigfile.length() > 8) {
+            sigfile = sigfile.substring(0, 8).toUpperCase(Locale.ENGLISH);
+        } else {
+            sigfile = sigfile.toUpperCase(Locale.ENGLISH);
+        }
+
+        StringBuilder tmpSigFile = new StringBuilder(sigfile.length());
+        for (int j = 0; j < sigfile.length(); j++) {
+            char c = sigfile.charAt(j);
+            if (!
+                ((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."));
+                }
+            }
+            tmpSigFile.append(c);
+        }
+
+        sigfile = tmpSigFile.toString();
+
+        String tmpJarName;
+        if (signedjar == null) tmpJarName = jarName+".sig";
+        else tmpJarName = signedjar;
+
+        File jarFile = new File(jarName);
+        File signedJarFile = new File(tmpJarName);
+
+        // Open the jar (zip) file
+        try {
+            zipFile = new ZipFile(jarName);
+        } catch (IOException ioe) {
+            error(rb.getString("unable.to.open.jar.file.")+jarName, ioe);
+        }
+
+        FileOutputStream fos = null;
+        try {
+            fos = new FileOutputStream(signedJarFile);
+        } catch (IOException ioe) {
+            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) };
+
+            // 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;
+            }
+
+            /*
+             * 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!)
+             */
+            BASE64Encoder encoder = new JarBASE64Encoder();
+            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 (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, encoder,
+                                      manifest) == true) {
+                        mfModified = true;
+                    }
+                } else if (!ze.isDirectory()) {
+                    // Add entry to manifest
+                    Attributes attrs = getDigestAttributes(ze, zipFile,
+                                                           digests,
+                                                           encoder);
+                    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 (verbose != null) {
+                if (mfCreated) {
+                    System.out.println(rb.getString(".adding.") +
+                                        mfFile.getName());
+                } else if (mfModified) {
+                    System.out.println(rb.getString(".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,
+                                                 sigfile, signManifest);
+
+            if (tsaAlias != null) {
+                tsaCert = getTsaCert(tsaAlias);
+            }
+
+            SignatureFile.Block block = null;
+
+            try {
+                block =
+                    sf.generateBlock(privateKey, sigalg, certChain,
+                        externalSF, tsaUrl, tsaCert, signingMechanism, args,
+                        zipFile);
+            } catch (SocketTimeoutException e) {
+                // 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);
+                }
+            }
+
+            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) {
+                zipFile.close();
+                zipFile = null;
+            }
+
+            if (zos != null) {
+                zos.close();
+            }
+        }
+
+        // no IOException thrown in the follow try clause, so disable
+        // the try clause.
+        // try {
+            if (signedjar == null) {
+                // attempt an atomic rename. If that fails,
+                // rename the original jar file, then the signed
+                // one, then delete the original.
+                if (!signedJarFile.renameTo(jarFile)) {
+                    File origJar = new File(jarName+".orig");
+
+                    if (jarFile.renameTo(origJar)) {
+                        if (signedJarFile.renameTo(jarFile)) {
+                            origJar.delete();
+                        } else {
+                            MessageFormat form = new MessageFormat(rb.getString
+                        ("attempt.to.rename.signedJarFile.to.jarFile.failed"));
+                            Object[] source = {signedJarFile, jarFile};
+                            error(form.format(source));
+                        }
+                    } else {
+                        MessageFormat form = new MessageFormat(rb.getString
+                            ("attempt.to.rename.jarFile.to.origJar.failed"));
+                        Object[] source = {jarFile, origJar};
+                        error(form.format(source));
+                    }
+                }
+            }
+
+            if (hasExpiredCert || hasExpiringCert || notYetValidCert
+                    || badKeyUsage || badExtendedKeyUsage
+                    || badNetscapeCertType || chainNotValidated) {
+                System.out.println();
+
+                System.out.println(rb.getString("Warning."));
+                if (badKeyUsage) {
+                    System.out.println(
+                        rb.getString("The.signer.certificate.s.KeyUsage.extension.doesn.t.allow.code.signing."));
+                }
+
+                if (badExtendedKeyUsage) {
+                    System.out.println(
+                        rb.getString("The.signer.certificate.s.ExtendedKeyUsage.extension.doesn.t.allow.code.signing."));
+                }
+
+                if (badNetscapeCertType) {
+                    System.out.println(
+                        rb.getString("The.signer.certificate.s.NetscapeCertType.extension.doesn.t.allow.code.signing."));
+                }
+
+                if (hasExpiredCert) {
+                    System.out.println(
+                        rb.getString("The.signer.certificate.has.expired."));
+                } else if (hasExpiringCert) {
+                    System.out.println(
+                        rb.getString("The.signer.certificate.will.expire.within.six.months."));
+                } else if (notYetValidCert) {
+                    System.out.println(
+                        rb.getString("The.signer.certificate.is.not.yet.valid."));
+                }
+
+                if (chainNotValidated) {
+                    System.out.println(
+                            rb.getString("The.signer.s.certificate.chain.is.not.validated."));
+                }
+            }
+
+        // no IOException thrown in the above try clause, so disable
+        // the catch clause.
+        // } catch(IOException ioe) {
+        //     error(rb.getString("unable.to.sign.jar.")+ioe, ioe);
+        // }
+    }
+
+    /**
+     * 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-*
+     * . META-INF/*.SF
+     * . META-INF/*.DSA
+     * . META-INF/*.RSA
+     * . META-INF/*.EC
+     */
+    private boolean signatureRelated(String name) {
+        String ucName = name.toUpperCase(Locale.ENGLISH);
+        if (ucName.equals(JarFile.MANIFEST_NAME) ||
+            ucName.equals(META_INF) ||
+            (ucName.startsWith(SIG_PREFIX) &&
+                ucName.indexOf("/") == ucName.lastIndexOf("/"))) {
+            return true;
+        }
+
+        if (ucName.startsWith(META_INF) &&
+            SignatureFileVerifier.isBlockOrSF(ucName)) {
+            // .SF/.DSA/.RSA/.EC files in META-INF subdirs
+            // are not considered signature-related
+            return (ucName.indexOf("/") == ucName.lastIndexOf("/"));
+        }
+
+        return false;
+    }
+
+    Map<CodeSigner,String> cacheForSignerInfo = new IdentityHashMap<>();
+
+    /**
+     * Returns a string of singer info, with a newline at the end
+     */
+    private String signerInfo(CodeSigner signer, String tab) {
+        if (cacheForSignerInfo.containsKey(signer)) {
+            return cacheForSignerInfo.get(signer);
+        }
+        StringBuffer s = new StringBuffer();
+        List<? extends Certificate> certs = signer.getSignerCertPath().getCertificates();
+        // display the signature timestamp, if present
+        Date timestamp;
+        Timestamp ts = signer.getTimestamp();
+        if (ts != null) {
+            s.append(printTimestamp(tab, ts));
+            s.append('\n');
+            timestamp = ts.getTimestamp();
+        } else {
+            timestamp = null;
+        }
+        // display the certificate(s). The first one is end-entity cert and
+        // its KeyUsage should be checked.
+        boolean first = true;
+        for (Certificate c : certs) {
+            s.append(printCert(tab, c, true, timestamp, first));
+            s.append('\n');
+            first = false;
+        }
+        try {
+            CertPath cp = certificateFactory.generateCertPath(certs);
+            validator.validate(cp, pkixParameters);
+        } catch (Exception e) {
+            if (debug) {
+                e.printStackTrace();
+            }
+            if (e.getCause() != null &&
+                    (e.getCause() instanceof CertificateExpiredException ||
+                     e.getCause() instanceof CertificateNotYetValidException)) {
+                // No more warning, we alreay have hasExpiredCert or notYetValidCert
+            } else {
+                chainNotValidated = true;
+                s.append(tab + rb.getString(".CertPath.not.validated.") +
+                        e.getLocalizedMessage() + "]\n");   // TODO
+            }
+        }
+        String result = s.toString();
+        cacheForSignerInfo.put(signer, result);
+        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) {
+            keyStoreName = System.getProperty("user.home") + File.separator
+                + ".keystore";
+        }
+
+        try {
+
+            certificateFactory = CertificateFactory.getInstance("X.509");
+            validator = CertPathValidator.getInstance("PKIX");
+            Set<TrustAnchor> tas = new HashSet<>();
+            try {
+                KeyStore caks = KeyStoreUtil.getCacertsKeyStore();
+                if (caks != null) {
+                    Enumeration<String> aliases = caks.aliases();
+                    while (aliases.hasMoreElements()) {
+                        String a = aliases.nextElement();
+                        try {
+                            tas.add(new TrustAnchor((X509Certificate)caks.getCertificate(a), null));
+                        } catch (Exception e2) {
+                            // ignore, when a SecretkeyEntry does not include a cert
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                // Ignore, if cacerts cannot be loaded
+            }
+
+            if (providerName == null) {
+                store = KeyStore.getInstance(storetype);
+            } else {
+                store = KeyStore.getInstance(storetype, providerName);
+            }
+
+            // Get pass phrase
+            // XXX need to disable echo; on UNIX, call getpass(char *prompt)Z
+            // and on NT call ??
+            if (token && storepass == null && !protectedPath
+                    && !KeyStoreUtil.isWindowsKeyStore(storetype)) {
+                storepass = getPass
+                        (rb.getString("Enter.Passphrase.for.keystore."));
+            } else if (!token && storepass == null && prompt) {
+                storepass = getPass
+                        (rb.getString("Enter.Passphrase.for.keystore."));
+            }
+
+            try {
+                if (nullStream) {
+                    store.load(null, storepass);
+                } else {
+                    keyStoreName = keyStoreName.replace(File.separatorChar, '/');
+                    URL url = null;
+                    try {
+                        url = new URL(keyStoreName);
+                    } catch (java.net.MalformedURLException e) {
+                        // try as file
+                        url = new File(keyStoreName).toURI().toURL();
+                    }
+                    InputStream is = null;
+                    try {
+                        is = url.openStream();
+                        store.load(is, storepass);
+                    } finally {
+                        if (is != null) {
+                            is.close();
+                        }
+                    }
+                }
+                Enumeration<String> aliases = store.aliases();
+                while (aliases.hasMoreElements()) {
+                    String a = aliases.nextElement();
+                    try {
+                        X509Certificate c = (X509Certificate)store.getCertificate(a);
+                        // Only add TrustedCertificateEntry and self-signed
+                        // PrivateKeyEntry
+                        if (store.isCertificateEntry(a) ||
+                                c.getSubjectDN().equals(c.getIssuerDN())) {
+                            tas.add(new TrustAnchor(c, null));
+                        }
+                    } catch (Exception e2) {
+                        // ignore, when a SecretkeyEntry does not include a cert
+                    }
+                }
+            } finally {
+                try {
+                    pkixParameters = new PKIXParameters(tas);
+                    pkixParameters.setRevocationEnabled(false);
+                } catch (InvalidAlgorithmParameterException ex) {
+                    // Only if tas is empty
+                }
+            }
+        } catch (IOException ioe) {
+            throw new RuntimeException(rb.getString("keystore.load.") +
+                                        ioe.getMessage());
+        } catch (java.security.cert.CertificateException ce) {
+            throw new RuntimeException(rb.getString("certificate.exception.") +
+                                        ce.getMessage());
+        } catch (NoSuchProviderException pe) {
+            throw new RuntimeException(rb.getString("keystore.load.") +
+                                        pe.getMessage());
+        } catch (NoSuchAlgorithmException nsae) {
+            throw new RuntimeException(rb.getString("keystore.load.") +
+                                        nsae.getMessage());
+        } catch (KeyStoreException kse) {
+            throw new RuntimeException
+                (rb.getString("unable.to.instantiate.keystore.class.") +
+                kse.getMessage());
+        }
+    }
+
+    X509Certificate getTsaCert(String alias) {
+
+        java.security.cert.Certificate cs = null;
+
+        try {
+            cs = store.getCertificate(alias);
+        } catch (KeyStoreException kse) {
+            // this never happens, because keystore has been loaded
+        }
+        if (cs == null || (!(cs instanceof X509Certificate))) {
+            MessageFormat form = new MessageFormat(rb.getString
+                ("Certificate.not.found.for.alias.alias.must.reference.a.valid.KeyStore.entry.containing.an.X.509.public.key.certificate.for.the"));
+            Object[] source = {alias, alias};
+            error(form.format(source));
+        }
+        return (X509Certificate) cs;
+    }
+
+    /**
+     * Check if userCert is designed to be a code signer
+     * @param userCert the certificate to be examined
+     * @param bad 3 booleans to show if the KeyUsage, ExtendedKeyUsage,
+     *            NetscapeCertType has codeSigning flag turned on.
+     *            If null, the class field badKeyUsage, badExtendedKeyUsage,
+     *            badNetscapeCertType will be set.
+     */
+    void checkCertUsage(X509Certificate userCert, boolean[] bad) {
+
+        // Can act as a signer?
+        // 1. if KeyUsage, then [0:digitalSignature] or
+        //    [1:nonRepudiation] should be true
+        // 2. if ExtendedKeyUsage, then should contains ANY or CODE_SIGNING
+        // 3. if NetscapeCertType, then should contains OBJECT_SIGNING
+        // 1,2,3 must be true
+
+        if (bad != null) {
+            bad[0] = bad[1] = bad[2] = false;
+        }
+
+        boolean[] keyUsage = userCert.getKeyUsage();
+        if (keyUsage != null) {
+            keyUsage = Arrays.copyOf(keyUsage, 9);
+            if (!keyUsage[0] && !keyUsage[1]) {
+                if (bad != null) {
+                    bad[0] = true;
+                    badKeyUsage = true;
+                }
+            }
+        }
+
+        try {
+            List<String> xKeyUsage = userCert.getExtendedKeyUsage();
+            if (xKeyUsage != null) {
+                if (!xKeyUsage.contains("2.5.29.37.0") // anyExtendedKeyUsage
+                        && !xKeyUsage.contains("1.3.6.1.5.5.7.3.3")) {  // codeSigning
+                    if (bad != null) {
+                        bad[1] = true;
+                        badExtendedKeyUsage = true;
+                    }
+                }
+            }
+        } catch (java.security.cert.CertificateParsingException e) {
+            // shouldn't happen
+        }
+
+        try {
+            // OID_NETSCAPE_CERT_TYPE
+            byte[] netscapeEx = userCert.getExtensionValue
+                    ("2.16.840.1.113730.1.1");
+            if (netscapeEx != null) {
+                DerInputStream in = new DerInputStream(netscapeEx);
+                byte[] encoded = in.getOctetString();
+                encoded = new DerValue(encoded).getUnalignedBitString()
+                        .toByteArray();
+
+                NetscapeCertTypeExtension extn =
+                        new NetscapeCertTypeExtension(encoded);
+
+                Boolean val = extn.get(NetscapeCertTypeExtension.OBJECT_SIGNING);
+                if (!val) {
+                    if (bad != null) {
+                        bad[2] = true;
+                        badNetscapeCertType = true;
+                    }
+                }
+            }
+        } catch (IOException e) {
+            //
+        }
+    }
+
+    void getAliasInfo(String alias) {
+
+        Key key = null;
+
+        try {
+            java.security.cert.Certificate[] cs = null;
+            if (altCertChain != null) {
+                try {
+                    cs = CertificateFactory.getInstance("X.509").
+                            generateCertificates(new FileInputStream(altCertChain)).
+                            toArray(new Certificate[0]);
+                } catch (CertificateException ex) {
+                    error(rb.getString("Cannot.restore.certchain.from.file.specified"));
+                } catch (FileNotFoundException ex) {
+                    error(rb.getString("File.specified.by.certchain.does.not.exist"));
+                }
+            } else {
+                try {
+                    cs = store.getCertificateChain(alias);
+                } catch (KeyStoreException kse) {
+                    // this never happens, because keystore has been loaded
+                }
+            }
+            if (cs == null || cs.length == 0) {
+                if (altCertChain != null) {
+                    error(rb.getString
+                            ("Certificate.chain.not.found.in.the.file.specified."));
+                } else {
+                    MessageFormat form = new MessageFormat(rb.getString
+                        ("Certificate.chain.not.found.for.alias.alias.must.reference.a.valid.KeyStore.key.entry.containing.a.private.key.and"));
+                    Object[] source = {alias, alias};
+                    error(form.format(source));
+                }
+            }
+
+            certChain = new X509Certificate[cs.length];
+            for (int i=0; i<cs.length; i++) {
+                if (!(cs[i] instanceof X509Certificate)) {
+                    error(rb.getString
+                        ("found.non.X.509.certificate.in.signer.s.chain"));
+                }
+                certChain[i] = (X509Certificate)cs[i];
+            }
+
+            // We don't meant to print anything, the next call
+            // checks validity and keyUsage etc
+            printCert("", certChain[0], true, null, true);
+
+            try {
+                CertPath cp = certificateFactory.generateCertPath(Arrays.asList(certChain));
+                validator.validate(cp, pkixParameters);
+            } catch (Exception e) {
+                if (debug) {
+                    e.printStackTrace();
+                }
+                if (e.getCause() != null &&
+                        (e.getCause() instanceof CertificateExpiredException ||
+                        e.getCause() instanceof CertificateNotYetValidException)) {
+                    // No more warning, we alreay have hasExpiredCert or notYetValidCert
+                } else {
+                    chainNotValidated = true;
+                }
+            }
+
+            try {
+                if (!token && keypass == null)
+                    key = store.getKey(alias, storepass);
+                else
+                    key = store.getKey(alias, keypass);
+            } catch (UnrecoverableKeyException e) {
+                if (token) {
+                    throw e;
+                } else if (keypass == null) {
+                    // Did not work out, so prompt user for key password
+                    MessageFormat form = new MessageFormat(rb.getString
+                        ("Enter.key.password.for.alias."));
+                    Object[] source = {alias};
+                    keypass = getPass(form.format(source));
+                    key = store.getKey(alias, keypass);
+                }
+            }
+        } catch (NoSuchAlgorithmException e) {
+            error(e.getMessage());
+        } catch (UnrecoverableKeyException e) {
+            error(rb.getString("unable.to.recover.key.from.keystore"));
+        } catch (KeyStoreException kse) {
+            // this never happens, because keystore has been loaded
+        }
+
+        if (!(key instanceof PrivateKey)) {
+            MessageFormat form = new MessageFormat(rb.getString
+                ("key.associated.with.alias.not.a.private.key"));
+            Object[] source = {alias};
+            error(form.format(source));
+        } else {
+            privateKey = (PrivateKey)key;
+        }
+    }
+
+    void error(String message)
+    {
+        System.out.println(rb.getString("jarsigner.")+message);
+        System.exit(1);
+    }
+
+
+    void error(String message, Exception e)
+    {
+        System.out.println(rb.getString("jarsigner.")+message);
+        if (debug) {
+            e.printStackTrace();
+        }
+        System.exit(1);
+    }
+
+    char[] getPass(String prompt)
+    {
+        System.err.print(prompt);
+        System.err.flush();
+        try {
+            char[] pass = Password.readPassword(System.in);
+
+            if (pass == null) {
+                error(rb.getString("you.must.enter.key.password"));
+            } else {
+                return pass;
+            }
+        } catch (IOException ioe) {
+            error(rb.getString("unable.to.read.password.")+ioe.getMessage());
+        }
+        // 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,
+                                             BASE64Encoder encoder)
+        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] = encoder.encode(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,
+                                           BASE64Encoder encoder)
+        throws IOException {
+
+        String[] base64Digests = getDigests(ze, zf, digests, encoder);
+        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,
+                                  BASE64Encoder encoder,
+                                  Manifest mf) throws IOException {
+        boolean update = false;
+
+        Attributes attrs = mf.getAttributes(ze.getName());
+        String[] base64Digests = getDigests(ze, zf, digests, encoder);
+
+        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;
+    }
+}
+
+/**
+ * This is a BASE64Encoder that does not insert a default newline at the end of
+ * every output line. This is necessary because java.util.jar does its own
+ * line management (see Manifest.make72Safe()). Inserting additional new lines
+ * can cause line-wrapping problems (see CR 6219522).
+ */
+class JarBASE64Encoder extends BASE64Encoder {
+    /**
+     * Encode the suffix that ends every output line.
+     */
+    protected void encodeLineSuffix(OutputStream aStream) throws IOException { }
+}
+
+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();
+        BASE64Encoder encoder = new JarBASE64Encoder();
+
+        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",
+                               encoder.encode(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,
+                        encoder.encode(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",
+                                  encoder.encode(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.
+     */
+    public Block generateBlock(PrivateKey privateKey,
+                               String sigalg,
+                               X509Certificate[] certChain,
+                               boolean externalSF, String tsaUrl,
+                               X509Certificate tsaCert,
+                               ContentSigner signingMechanism,
+                               String[] args, ZipFile zipFile)
+        throws NoSuchAlgorithmException, InvalidKeyException, IOException,
+            SignatureException, CertificateException
+    {
+        return new Block(this, privateKey, sigalg, certChain, externalSF,
+                tsaUrl, tsaCert, signingMechanism, args, zipFile);
+    }
+
+
+    public static class Block {
+
+        private byte[] block;
+        private String blockFileName;
+
+        /*
+         * Construct a new signature block.
+         */
+        Block(SignatureFile sfg, PrivateKey privateKey, String sigalg,
+            X509Certificate[] certChain, boolean externalSF, String tsaUrl,
+            X509Certificate tsaCert, 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(CertificateIssuerName.NAME + "." +
+                                CertificateIssuerName.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 = "SHA1withDSA";
+                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, 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.
+ */
+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;
+
+    /**
+     * Create a new object.
+     */
+    JarSignerParameters(String[] args, URI tsa, X509Certificate tsaCertificate,
+        byte[] signature, String signatureAlgorithm,
+        X509Certificate[] signerCertificateChain, byte[] content,
+        ZipFile source) {
+
+        if (signature == null || signatureAlgorithm == null ||
+            signerCertificateChain == null) {
+            throw new NullPointerException();
+        }
+        this.args = args;
+        this.tsa = tsa;
+        this.tsaCertificate = tsaCertificate;
+        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;
+    }
+
+    /**
+     * 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;
+    }
+}