diff options
Diffstat (limited to 'netx/net/sourceforge/jnlp/tools/JarSigner.java')
-rw-r--r-- | netx/net/sourceforge/jnlp/tools/JarSigner.java | 553 |
1 files changed, 553 insertions, 0 deletions
diff --git a/netx/net/sourceforge/jnlp/tools/JarSigner.java b/netx/net/sourceforge/jnlp/tools/JarSigner.java new file mode 100644 index 0000000..05ba28f --- /dev/null +++ b/netx/net/sourceforge/jnlp/tools/JarSigner.java @@ -0,0 +1,553 @@ +/* + * Copyright 1997-2007 Sun Microsystems, Inc. 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. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package net.sourceforge.jnlp.tools; + +import java.io.*; +import java.util.*; +import java.util.zip.*; +import java.util.jar.*; +import java.text.Collator; +import java.text.MessageFormat; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.security.cert.CertPath; +import java.security.*; +import sun.security.x509.*; +import sun.security.util.*; + +import net.sourceforge.jnlp.*; +import net.sourceforge.jnlp.cache.*; +import net.sourceforge.jnlp.runtime.*; +import net.sourceforge.jnlp.security.*; + +/** + * <p>The jarsigner utility. + * + * @author Roland Schemers + * @author Jan Luehe + */ + +public class JarSigner implements CertVerifier { + + private static String R(String key) { + return JNLPRuntime.getMessage(key); + } + + 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 long SIX_MONTHS = 180*24*60*60*1000L; //milliseconds + + static final String VERSION = "1.0"; + + static final int IN_KEYSTORE = 0x01; + static final int IN_SCOPE = 0x02; + + static enum verifyResult {UNSIGNED, SIGNED_OK, SIGNED_NOT_OK} + + // 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 + + private boolean hasExpiredCert = false; + private boolean hasExpiringCert = false; + private boolean notYetValidCert = false; + + private boolean badKeyUsage = false; + private boolean badExtendedKeyUsage = false; + private boolean badNetscapeCertType = false; + + private boolean alreadyTrustPublisher = false; + private boolean rootInCacerts = false; + + /** + * The single certPath used in this JarSiging. We're only keeping + * track of one here, since in practice there's only one signer + * for a JNLP Application. + */ + private CertPath certPath = null; + + private boolean noSigningIssues = true; + + private boolean anyJarsSigned = false; + + /** all of the jar files that were verified */ + private ArrayList<String> verifiedJars = null; + + /** all of the jar files that were not verified */ + private ArrayList<String> unverifiedJars = null; + + /** the certificates used for jar verification */ + private ArrayList<CertPath> certs = null; + + /** details of this signing */ + private ArrayList<String> details = new ArrayList<String>(); + + /* (non-Javadoc) + * @see net.sourceforge.jnlp.tools.CertVerifier2#getAlreadyTrustPublisher() + */ + public boolean getAlreadyTrustPublisher() { + return alreadyTrustPublisher; + } + + /* (non-Javadoc) + * @see net.sourceforge.jnlp.tools.CertVerifier2#getRootInCacerts() + */ + public boolean getRootInCacerts() { + return rootInCacerts; + } + + public CertPath getCertPath() { + return certPath; + } + + /* (non-Javadoc) + * @see net.sourceforge.jnlp.tools.CertVerifier2#hasSigningIssues() + */ + public boolean hasSigningIssues() { + return hasExpiredCert || notYetValidCert || badKeyUsage + || badExtendedKeyUsage || badNetscapeCertType; + } + + /* (non-Javadoc) + * @see net.sourceforge.jnlp.tools.CertVerifier2#noSigningIssues() + */ + public boolean noSigningIssues() { + return noSigningIssues; + } + + public boolean anyJarsSigned() { + return anyJarsSigned; + } + + /* (non-Javadoc) + * @see net.sourceforge.jnlp.tools.CertVerifier2#getDetails() + */ + public ArrayList<String> getDetails() { + return details; + } + + /* (non-Javadoc) + * @see net.sourceforge.jnlp.tools.CertVerifier2#getCerts() + */ + public ArrayList<CertPath> getCerts() { + return certs; + } + + public void verifyJars(List<JARDesc> jars, ResourceTracker tracker) + throws Exception { + + certs = new ArrayList<CertPath>(); + for (int i = 0; i < jars.size(); i++) { + + JARDesc jar = (JARDesc) jars.get(i); + verifiedJars = new ArrayList<String>(); + unverifiedJars = new ArrayList<String>(); + + try { + + File jarFile = tracker.getCacheFile(jar.getLocation()); + + // some sort of resource download/cache error. Nothing to add + // in that case ... but don't fail here + if (jarFile == null) { + return; + } + + String localFile = jarFile.getAbsolutePath(); + verifyResult result = verifyJar(localFile); + + if (result == verifyResult.UNSIGNED) { + unverifiedJars.add(localFile); + } else if (result == verifyResult.SIGNED_NOT_OK) { + noSigningIssues = false; + verifiedJars.add(localFile); + } else if (result == verifyResult.SIGNED_OK) { + verifiedJars.add(localFile); + } + } catch (Exception e){ + // We may catch exceptions from using verifyJar() + // or from checkTrustedCerts + throw e; + } + } + } + + public verifyResult verifyJar(String jarName) throws Exception { + boolean anySigned = false; + boolean hasUnsignedEntry = false; + JarFile jarFile = null; + + // certs could be uninitialized if one calls this method directly + if (certs == null) + certs = new ArrayList<CertPath>(); + + try { + jarFile = new JarFile(jarName, true); + Vector<JarEntry> entriesVec = new Vector<JarEntry>(); + byte[] buffer = new byte[8192]; + + JarEntry je; + Enumeration<JarEntry> entries = jarFile.entries(); + while (entries.hasMoreElements()) { + je = entries.nextElement(); + entriesVec.addElement(je); + + InputStream is = jarFile.getInputStream(je); + try { + 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(); + } + } + } + + if (jarFile.getManifest() != null) { + if (verbose) System.out.println(); + Enumeration<JarEntry> e = entriesVec.elements(); + + long now = System.currentTimeMillis(); + + while (e.hasMoreElements()) { + je = e.nextElement(); + String name = je.getName(); + CodeSigner[] signers = je.getCodeSigners(); + boolean isSigned = (signers != null); + anySigned |= isSigned; + hasUnsignedEntry |= !je.isDirectory() && !isSigned + && !signatureRelated(name); + if (isSigned) { + // TODO: Perhaps we should check here that + // signers.length is only of size 1, and throw an + // exception if it's not? + for (int i = 0; i < signers.length; i++) { + CertPath certPath = signers[i].getSignerCertPath(); + if (!certs.contains(certPath)) + certs.add(certPath); + + //we really only want the first certPath + if (!certPath.equals(this.certPath)){ + this.certPath = certPath; + } + + 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; + } + } + } + } + } + } //while e has more elements + } //if man not null + + //Alert the user if any of the following are true. + if (!anySigned) { + return verifyResult.UNSIGNED; + } else { + anyJarsSigned = true; + + //warnings + if (hasUnsignedEntry || hasExpiredCert || hasExpiringCert || + badKeyUsage || badExtendedKeyUsage || badNetscapeCertType || + notYetValidCert) { + + addToDetails(R("SRunWithoutRestrictions")); + + if (badKeyUsage) + addToDetails(R("SBadKeyUsage")); + if (badExtendedKeyUsage) + addToDetails(R("SBadExtendedKeyUsage")); + if (badNetscapeCertType) + addToDetails(R("SBadNetscapeCertType")); + if (hasUnsignedEntry) + addToDetails(R("SHasUnsignedEntry")); + if (hasExpiredCert) + addToDetails(R("SHasExpiredCert")); + if (hasExpiringCert) + addToDetails(R("SHasExpiringCert")); + if (notYetValidCert) + addToDetails(R("SNotYetValidCert")); + } + } + + } catch (Exception e) { + e.printStackTrace(); + throw e; + } finally { // close the resource + if (jarFile != null) { + jarFile.close(); + } + } + + // check if the certs added above are in the trusted path + checkTrustedCerts(); + + //anySigned does not guarantee that all files were signed. + return (anySigned && !(hasUnsignedEntry || hasExpiredCert + || badKeyUsage || badExtendedKeyUsage || badNetscapeCertType + || notYetValidCert)) ? verifyResult.SIGNED_OK : verifyResult.SIGNED_NOT_OK; + } + + /** + * Checks the user's trusted.certs file and the cacerts file to see + * if a publisher's and/or CA's certificate exists there. + */ + private void checkTrustedCerts() throws Exception { + if (certPath != null) { + try { + KeyTool kt = new KeyTool(); + alreadyTrustPublisher = kt.isTrusted(getPublisher()); + rootInCacerts = kt.checkCacertsForCertificate(getRoot()); + } catch (Exception e) { + // TODO: Warn user about not being able to + // look through their cacerts/trusted.certs + // file depending on exception. + throw e; + } + + if (!rootInCacerts) + addToDetails(R("SUntrustedCertificate")); + else + addToDetails(R("STrustedCertificate")); + } + } + + /* (non-Javadoc) + * @see net.sourceforge.jnlp.tools.CertVerifier2#getPublisher() + */ + public Certificate getPublisher() { + if (certPath != null) { + List<? extends Certificate> certList + = certPath.getCertificates(); + if (certList.size() > 0) { + return (Certificate)certList.get(0); + } else { + return null; + } + } else { + return null; + } + } + + /* (non-Javadoc) + * @see net.sourceforge.jnlp.tools.CertVerifier2#getRoot() + */ + public Certificate getRoot() { + if (certPath != null) { + List<? extends Certificate> certList + = certPath.getCertificates(); + if (certList.size() > 0) { + return (Certificate)certList.get( + certList.size() - 1); + } else { + return null; + } + } else { + return null; + } + } + + private void addToDetails(String detail) { + if (!details.contains(detail)) + details.add(detail); + } + + Hashtable<Certificate, String> storeHash = + new Hashtable<Certificate, String>(); + + /** + * signature-related files include: + * . META-INF/MANIFEST.MF + * . META-INF/SIG-* + * . META-INF/*.SF + * . META-INF/*.DSA + * . META-INF/*.RSA + * + * Required for verifyJar() + */ + private boolean signatureRelated(String name) { + String ucName = name.toUpperCase(); + 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 files in META-INF subdirs + // are not considered signature-related + return (ucName.indexOf("/") == ucName.lastIndexOf("/")); + } + + return false; + } + + /** + * 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. + * + * Required for verifyJar() + */ + 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 = (Boolean)extn.get( + NetscapeCertTypeExtension.OBJECT_SIGNING); + if (!val) { + if (bad != null) { + bad[2] = true; + } else { + badNetscapeCertType = true; + } + } + } + } catch (IOException e) { + // + } + } + + + /** + * Returns if all jars are signed. + * + * @return True if all jars are signed, false if there are one or more unsigned jars + */ + public boolean allJarsSigned() { + return this.unverifiedJars.size() == 0; + } + +} |