--- /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;
+ }
+}