< prev index next >

src/share/classes/sun/security/tools/JarSigner.java

Print this page
rev 1453 : 6561126: keytool should use larger default keysize for keypairs
Reviewed-by: mullan
rev 1455 : 6870812: enhance security tools to use ECC algorithms
Reviewed-by: vinnie, mullan
rev 1456 : 6802846: jarsigner needs enhanced cert validation(options)
Reviewed-by: xuelei
rev 1461 : 6987827: security/util/Resources.java needs improvement
Reviewed-by: valeriep
rev 1465 : 7028490: better suggestion for jarsigner when TSA is not accessible
Reviewed-by: mullan
rev 1466 : 7019937: Translatability bug - Remove Unused String - String ID , read end of file
7019938: Translatability bug - Remove Unused String - String ID can not specify Principal with a
7019940: Translatability bug - Remove unused string - String ID: provided null name
7019942: Translatability bug - String ID: trustedCertEntry,
7019945: Translatability bug - Translatability issue - String ID: * has NOT been verified! In order to veri
7019947: Translatability bug - Translatability issue - String ID: * The integrity of the information stored i
7019949: Translatability bug - Translatability issue - String ID: * you must provide your keystore password.
Reviewed-by: weijun, wetmore
rev 1513 : 7194449: String resources for Key Tool and Policy Tool should be in their respective packages
Reviewed-by: alanb, weijun, mullan
rev 1592 : 8171954: Add stubs for sun.security.tools.KeyTool and sun.security.tools.JarSigner
Summary: Allow sun.security.tools.{keytool,jarsigner}.Main.main to be invoked using their old classes
Reviewed-by: omajid

*** 1,7 **** /* ! * Copyright (c) 1997, 2010, 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 --- 1,7 ---- /* ! * Copyright (c) 2016 Red Hat, Inc. * 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
*** 23,2178 **** * questions. */ package sun.security.tools; ! 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.net.URL; ! import java.net.URLClassLoader; ! import java.net.SocketTimeoutException; ! 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.cert.CertificateExpiredException; ! import java.security.cert.CertificateNotYetValidException; ! import java.security.*; ! import java.lang.reflect.Constructor; ! ! import com.sun.jarsigner.ContentSigner; ! import com.sun.jarsigner.ContentSignerParameters; ! import sun.security.x509.*; ! import sun.security.util.*; ! import sun.misc.BASE64Encoder; /** ! * <p>The jarsigner utility. ! * ! * @author Roland Schemers ! * @author Jan Luehe ! */ ! ! public class JarSigner { ! ! // for i18n ! private static final java.util.ResourceBundle rb = ! java.util.ResourceBundle.getBundle ! ("sun.security.tools.JarSignerResources"); ! 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/"; ! ! 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. ! // It's marked as exported private per AppServer Team's request. ! // See http://ccc.sfbay/6428446 ! public static void main(String args[]) throws Exception { ! JarSigner js = new JarSigner(); ! js.run(args); ! } ! ! static final String VERSION = "1.0"; ! ! static final int IN_KEYSTORE = 0x01; ! static final int IN_SCOPE = 0x02; ! ! // signer's certificate chain (when composing) ! X509Certificate[] certChain; ! ! /* ! * private key ! */ ! PrivateKey privateKey; ! KeyStore store; ! ! IdentityScope scope; ! ! String keystore; // key store file ! boolean nullStream = false; // null keystore input stream (NONE) ! boolean token = false; // token-based keystore ! String jarfile; // jar file to sign ! String alias; // alias to sign jar with ! char[] storepass; // keystore password ! boolean protectedPath; // protected authentication path ! String storetype; // keystore type ! String providerName; // provider name ! Vector<String> providers = null; // list of providers ! HashMap<String,String> providerArgs = new HashMap<String, String>(); // arguments for provider constructors ! char[] keypass; // private key password ! String sigfile; // name of .SF file ! String sigalg; // name of signature algorithm ! String digestalg = "SHA1"; // name of digest algorithm ! String signedjar; // output filename ! String tsaUrl; // location of the Timestamping Authority ! String tsaAlias; // alias for the Timestamping Authority's certificate ! boolean verify = false; // verify the jar ! boolean verbose = false; // 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 ! ! // 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 badKeyUsage = false; ! private boolean badExtendedKeyUsage = false; ! private boolean badNetscapeCertType = false; ! ! 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); ! } ! } ! ! hasExpiredCert = false; ! hasExpiringCert = false; ! notYetValidCert = false; ! ! badKeyUsage = false; ! badExtendedKeyUsage = false; ! badNetscapeCertType = false; ! ! if (verify) { ! try { ! loadKeyStore(keystore, false); ! scope = IdentityScope.getSystemScope(); ! } 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; ! } ! } ! } ! ! /* ! * Parse command line arguments. ! */ ! void parseArgs(String args[]) { ! /* parse flags */ ! int n = 0; ! ! for (n=0; (n < args.length) && args[n].startsWith("-"); n++) { ! ! String flags = args[n]; ! ! if (collator.compare(flags, "-keystore") == 0) { ! if (++n == args.length) usage(); ! keystore = args[n]; ! } else if (collator.compare(flags, "-storepass") ==0) { ! if (++n == args.length) usage(); ! storepass = args[n].toCharArray(); ! } else if (collator.compare(flags, "-storetype") ==0) { ! if (++n == args.length) usage(); ! storetype = args[n]; ! } else if (collator.compare(flags, "-providerName") ==0) { ! if (++n == args.length) usage(); ! providerName = args[n]; ! } else if ((collator.compare(flags, "-provider") == 0) || ! (collator.compare(flags, "-providerClass") == 0)) { ! if (++n == args.length) usage(); ! 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)) usage(); ! providerArgs.put(args[n], args[n+2]); ! n += 2; ! } ! } ! } else if (collator.compare(flags, "-protected") ==0) { ! protectedPath = true; ! } else if (collator.compare(flags, "-debug") ==0) { ! debug = true; ! } else if (collator.compare(flags, "-keypass") ==0) { ! if (++n == args.length) usage(); ! keypass = args[n].toCharArray(); ! } else if (collator.compare(flags, "-sigfile") ==0) { ! if (++n == args.length) usage(); ! sigfile = args[n]; ! } else if (collator.compare(flags, "-signedjar") ==0) { ! if (++n == args.length) usage(); ! signedjar = args[n]; ! } else if (collator.compare(flags, "-tsa") ==0) { ! if (++n == args.length) usage(); ! tsaUrl = args[n]; ! } else if (collator.compare(flags, "-tsacert") ==0) { ! if (++n == args.length) usage(); ! tsaAlias = args[n]; ! } else if (collator.compare(flags, "-altsigner") ==0) { ! if (++n == args.length) usage(); ! altSignerClass = args[n]; ! } else if (collator.compare(flags, "-altsignerpath") ==0) { ! if (++n == args.length) usage(); ! 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 = true; ! } else if (collator.compare(flags, "-sigalg") ==0) { ! if (++n == args.length) usage(); ! sigalg = args[n]; ! } else if (collator.compare(flags, "-digestalg") ==0) { ! if (++n == args.length) usage(); ! digestalg = args[n]; ! } else if (collator.compare(flags, "-certs") ==0) { ! showcerts = true; ! } else if (collator.compare(flags, "-h") == 0 || ! collator.compare(flags, "-help") == 0) { ! usage(); ! } else { ! System.err.println(rb.getString("Illegal option: ") + flags); ! usage(); ! } ! } ! ! if (n == args.length) usage(); ! jarfile = args[n++]; ! ! if (!verify) { ! if (n == args.length) usage(); ! alias = args[n++]; ! } ! ! if (storetype == null) { ! storetype = KeyStore.getDefaultType(); ! } ! storetype = KeyStoreUtil.niceStoreTypeName(storetype); ! ! 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)); ! System.err.println(); ! usage(); ! } ! ! if (token && keypass != null) { ! System.err.println(MessageFormat.format(rb.getString ! ("-keypass can not be specified " + ! "if -storetype is {0}"), storetype)); ! System.err.println(); ! 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")); ! System.err.println(); ! 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")); ! System.err.println(); ! usage(); ! } ! } ! } ! ! void usage() { ! System.out.println(rb.getString ! ("Usage: jarsigner [options] jar-file alias")); ! System.out.println(rb.getString ! (" jarsigner -verify [options] jar-file")); ! 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 ! ("[-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] verbose output when signing/verifying")); ! 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.exit(1); ! } ! ! void verifyJar(String jarName) ! throws Exception ! { ! boolean anySigned = false; ! boolean hasUnsignedEntry = false; ! JarFile jf = null; ! ! try { ! jf = new JarFile(jarName, true); ! Vector<JarEntry> entriesVec = new Vector<JarEntry>(); ! 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(); ! ! if (man != null) { ! if (verbose) System.out.println(); ! Enumeration<JarEntry> e = entriesVec.elements(); ! ! long now = System.currentTimeMillis(); ! ! 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); ! ! if (verbose) { ! int inStoreOrScope = inKeyStore(signers); ! boolean inStore = (inStoreOrScope & IN_KEYSTORE) != 0; ! boolean inScope = (inStoreOrScope & IN_SCOPE) != 0; ! boolean inManifest = ! ((man.getAttributes(name) != null) || ! (man.getAttributes("./"+name) != null) || ! (man.getAttributes("/"+name) != null)); ! System.out.print( ! (isSigned ? rb.getString("s") : rb.getString(" ")) + ! (inManifest ? rb.getString("m") : rb.getString(" ")) + ! (inStore ? rb.getString("k") : rb.getString(" ")) + ! (inScope ? rb.getString("i") : rb.getString(" ")) + ! rb.getString(" ")); ! StringBuffer sb = new StringBuffer(); ! String s = Long.toString(je.getSize()); ! for (int i = 6 - s.length(); i > 0; --i) { ! sb.append(' '); ! } ! sb.append(s).append(' '). ! append(new Date(je.getTime()).toString()); ! sb.append(' ').append(je.getName()); ! System.out.println(sb.toString()); ! ! if (signers != null && showcerts) { ! String tab = rb.getString(" "); ! for (int i = 0; i < signers.length; i++) { ! System.out.println(); ! List<? extends Certificate> certs = ! signers[i].getSignerCertPath() ! .getCertificates(); ! // display the signature timestamp, if present ! Timestamp timestamp = signers[i].getTimestamp(); ! if (timestamp != null) { ! System.out.println( ! printTimestamp(tab, timestamp)); ! } ! // display the certificate(s) ! for (Certificate c : certs) { ! System.out.println( ! printCert(tab, c, true, now)); ! } ! } ! System.out.println(); ! } ! ! } ! if (isSigned) { ! for (int i = 0; i < signers.length; i++) { ! Certificate cert = ! signers[i].getSignerCertPath() ! .getCertificates().get(0); ! if (cert instanceof X509Certificate) { ! checkCertUsage((X509Certificate)cert, null); ! if (!showcerts) { ! long notAfter = ((X509Certificate)cert) ! .getNotAfter().getTime(); ! ! if (notAfter < now) { ! hasExpiredCert = true; ! } else if (notAfter < now + SIX_MONTHS) { ! hasExpiringCert = true; ! } ! } ! } ! } ! } ! ! } ! } ! if (verbose) { ! 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")); ! 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) { ! ! 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 (! (verbose && showcerts)) { ! System.out.println(); ! System.out.println(rb.getString( ! "Re-run with the -verbose and -certs options for more details.")); ! } ! } ! } ! System.exit(0); ! } 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); ! } ! ! /* ! * Display some details about a certificate: ! * ! * <cert-type> [", " <subject-DN>] [" (" <keystore-entry-alias> ")"] ! */ ! String printCert(Certificate c) { ! return printCert("", c, false, 0); ! } ! ! 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>] ! */ ! String printCert(String tab, Certificate c, boolean checkValidityPeriod, ! long now) { ! ! StringBuilder certStr = new StringBuilder(); ! String space = rb.getString(" "); ! X509Certificate x509Cert = null; ! ! if (c instanceof X509Certificate) { ! x509Cert = (X509Certificate) c; ! certStr.append(tab).append(x509Cert.getType()) ! .append(rb.getString(", ")) ! .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 { ! x509Cert.checkValidity(); ! // test if cert will expire within six months ! if (now == 0) { ! now = System.currentTimeMillis(); ! } ! if (notAfter.getTime() < now + SIX_MONTHS) { ! hasExpiringCert = true; ! ! if (expiringTimeForm == null) { ! expiringTimeForm = new MessageFormat( ! rb.getString("certificate will expire on")); ! } ! Object[] source = { notAfter }; ! certStr.append(expiringTimeForm.format(source)); ! ! } else { ! 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("]"); ! ! 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(); ! } ! ! Hashtable<Certificate, String> storeHash = ! new Hashtable<Certificate, String>(); ! ! int inKeyStore(CodeSigner[] signers) { ! int result = 0; ! ! if (signers == null) ! return 0; ! ! boolean found = false; ! ! for (int i = 0; i < signers.length; i++) { ! found = false; ! List<? extends Certificate> certs = ! signers[i].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; ! } 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 (!found && (scope != null)) { ! Identity id = scope.getIdentity(c.getPublicKey()); ! if (id != null) { ! result |= IN_SCOPE; ! storeHash.put(c, "["+id.getName()+"]"); ! } ! } ! } ! } ! } ! return result; ! } ! ! 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(); ! } else { ! sigfile = sigfile.toUpperCase(); ! } ! ! 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(); ! String bkFilename = (META_INF + sigfile + ".DSA").toUpperCase(); ! ! 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<ZipEntry>(); ! ! 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) { ! 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. ") + ! rb.getString("When connecting from behind a firewall then an HTTP proxy may need to be specified. ") + ! rb.getString("Supply the following options to jarsigner: ") + ! "\n -J-Dhttp.proxyHost=<hostname> " + ! "\n -J-Dhttp.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) { ! if (zipFile.getEntry(sfFilename) != null) { ! System.out.println(rb.getString(" updating: ") + ! sfFilename); ! } else { ! System.out.println(rb.getString(" adding: ") + ! sfFilename); ! } ! } ! ! if (verbose) { ! 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) { ! String certUrl = ! TimestampedSigner.getTimestampingUrl(tsaCert); ! if (certUrl != null) { ! System.out.println(rb.getString("TSA location: ") + ! certUrl); ! } ! System.out.println( ! rb.getString("TSA certificate: ") + printCert(tsaCert)); ! } ! if (signingMechanism != null) { ! System.out.println( ! rb.getString("using an alternative signing mechanism")); ! } ! } ! ! // signature block file ! zos.putNextEntry(bkFile); ! block.write(zos); ! if (verbose) { ! 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) { ! 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) { ! 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.")); ! } ! } ! ! // 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 ! */ ! private boolean signatureRelated(String name) { ! return SignatureFileVerifier.isSigningRelated(name); ! } ! ! 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 { ! 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: ")); ! } ! ! 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(); ! } ! } ! } ! } 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 Timestamping Authority.")); ! 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] 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) { ! if (keyUsage.length < 1 || !keyUsage[0]) { ! if (bad != null) { ! bad[0] = true; ! } else { ! 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; ! } else { ! 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; ! } else { ! badNetscapeCertType = true; ! } ! } ! } ! } catch (IOException e) { ! // ! } ! } ! ! void getAliasInfo(String alias) { ! ! Key key = null; ! ! try { ! ! java.security.cert.Certificate[] cs = null; ! ! try { ! cs = store.getCertificateChain(alias); ! } catch (KeyStoreException kse) { ! // this never happens, because keystore has been loaded ! } ! if (cs == null) { ! 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 corresponding public key certificate chain.")); ! 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]; ! } ! ! // order the cert chain if necessary (put user cert first, ! // root-cert last in the chain) ! X509Certificate userCert ! = (X509Certificate)store.getCertificate(alias); ! ! // check validity of signer certificate ! try { ! userCert.checkValidity(); ! ! if (userCert.getNotAfter().getTime() < ! System.currentTimeMillis() + SIX_MONTHS) { ! ! hasExpiringCert = true; ! } ! } catch (CertificateExpiredException cee) { ! hasExpiredCert = true; ! ! } catch (CertificateNotYetValidException cnyve) { ! notYetValidCert = true; ! } ! ! checkCertUsage(userCert, null); ! ! if (!userCert.equals(certChain[0])) { ! // need to order ... ! X509Certificate[] certChainTmp ! = new X509Certificate[certChain.length]; ! certChainTmp[0] = userCert; ! Principal issuer = userCert.getIssuerDN(); ! for (int i=1; i<certChain.length; i++) { ! int j; ! // look for the cert whose subject corresponds to the ! // given issuer ! for (j=0; j<certChainTmp.length; j++) { ! if (certChainTmp[j] == null) ! continue; ! Principal subject = certChainTmp[j].getSubjectDN(); ! if (issuer.equals(subject)) { ! certChain[i] = certChainTmp[j]; ! issuer = certChainTmp[j].getIssuerDN(); ! certChainTmp[j] = null; ! break; ! } ! } ! if (j == certChainTmp.length) { ! error(rb.getString("incomplete certificate chain")); ! } ! ! } ! certChain = certChainTmp; // ordered ! } ! ! 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++) { ! String name = digests[i].getAlgorithm()+"-Digest"; ! String mfDigest = attrs.getValue(name); ! if (mfDigest == null ! && digests[i].getAlgorithm().equalsIgnoreCase("SHA")) { ! // treat "SHA" and "SHA1" the same ! mfDigest = attrs.getValue("SHA-Digest"); ! } ! if (mfDigest == null) { ! // compute digest and add it to list of attributes ! attrs.putValue(name, base64Digests[i]); ! update=true; ! } else { ! // compare digests, and replace the one in the manifest ! // if they are different ! 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 digestAlgorithm; ! 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")) ! digestAlgorithm = "SHA1"; ! else if (keyAlgorithm.equalsIgnoreCase("RSA")) ! digestAlgorithm = "SHA1"; ! else { ! throw new RuntimeException("private key is not a DSA or " ! + "RSA key"); ! } ! signatureAlgorithm = digestAlgorithm + "with" + keyAlgorithm; ! } else { ! signatureAlgorithm = sigalg; ! } ! ! // check common invalid key/signature algorithm combinations ! String sigAlgUpperCase = signatureAlgorithm.toUpperCase(); ! if ((sigAlgUpperCase.endsWith("WITHRSA") && ! !keyAlgorithm.equalsIgnoreCase("RSA")) || ! (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; } } --- 23,41 ---- * questions. */ package sun.security.tools; ! import sun.security.tools.jarsigner.Main; /** ! * This is a stub for compatibility reasons. ! * Please use sun.security.tools.jarsigner.Main in new code. */ ! public final class JarSigner { ! private JarSigner() { } ! public static void main(String[] args) throws Exception { ! Main.main(args); } }
< prev index next >