diff options
author | Danesh Dadachanji <[email protected]> | 2012-10-22 11:02:38 -0400 |
---|---|---|
committer | Danesh Dadachanji <[email protected]> | 2012-10-22 11:02:38 -0400 |
commit | e150560769232e18fa516609933649dab002f358 (patch) | |
tree | 661e1b4d3c9d101447bc952e541e8f054c1d96be | |
parent | 229e52bca7c9298d3a0889fe1bc6f9107b32639a (diff) |
Major rework of JarCertVerifier certificate management.
This is a long-planned rework of JarCertVerifier, allowing it to handle
multiple certificates. The algorithms used to verify jars with multiple
certificates vary between JNLPs and Applets.
16 files changed, 1882 insertions, 410 deletions
@@ -28,6 +28,76 @@ * tests/reproducers/custom/AppletFolderInArchiveTag/srcs/Makefile: and * tests/reproducers/custom/UnsignedContentInMETAINF/srcs/Makefile: following above renaming +2012-10-19 Adam Domurad <[email protected]> + + * netx/net/sourceforge/jnlp/security/AppVerifier.java: Use interface + types for declared types where applicable. + * netx/net/sourceforge/jnlp/security/PluginAppVerifier.java: Same. + * netx/net/sourceforge/jnlp/tools/JarCertVerifier.java: Same. + +2012-10-19 Danesh Dadachanji <[email protected]> + + Rework JarCertVerifier certificate management to handle multiple + certificates and use different algorithms to verify JNLPs and Applets. + * netx/net/sourceforge/jnlp/resources/Messages.properties: + Removed SHasUnsignedEntry. + * netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java: + Set JCV instance to final but uninitialized. + (JNLPClassLoader): Initialized JCV with runtime dependent verifier. + (addNewJar), (initializeResources), (verifySignedJNLP): + Replaced use of local JarCertVerifier variable with the instance variable. + Added calls to isFullySigned wherever signer verification is done. + (activateJars): No longer verifies nested jars. These receive the same + security permissions as their parent jar, regardless of the nested + jar's signing. + (checkTrustWithUser): Removed JCV param, reimplemented to wrap around + JCV's checkTrustWithUser method. + (verifyJars): Removed. + * netx/net/sourceforge/jnlp/security/AppVerifier.java: + New strategy pattern interface that specifies verification methods + required regardless of the runtime. + * netx/net/sourceforge/jnlp/security/JNLPAppVerifier.java: + * netx/net/sourceforge/jnlp/security/PluginAppVerifier.java: + New strategy pattern classes used to determine which algorithms to use + depending on the runtime. + * netx/net/sourceforge/jnlp/security/CertVerifier.java: + Added CertPath param to all the methods. + (noSigningIssues): Removed. + * netx/net/sourceforge/jnlp/security/CertWarningPane.java: + * netx/net/sourceforge/jnlp/security/CertsInfoPane.java: + * netx/net/sourceforge/jnlp/security/MoreInfoPane.java: + Updated calls to the verifier's methods with the new CertPath param. All + are set to null so far. + * netx/net/sourceforge/jnlp/security/HttpsCertVerifier.java: + Added CertPath param to all the methods. It's mostly ignored though. + * netx/net/sourceforge/jnlp/tools/CertInformation.java: + New class to represent all the information about a signer with + with respect to all of the entries it has signed for the app. + * netx/net/sourceforge/jnlp/tools/JarCertVerifier.java: + Completely reworked to use CertInformation and AppVerifier functionality. + (getCertPath), (getCertInformation), (checkTrustWithUser), + (getJarSignableEntries), (getTotalJarEntries): New method. + (noSigningIssues), (anyJarsSigned): Removed. + (verifyResult): Renamed enum to VerifyResult + (JarCertVerifier): New constructor used to set AppVerifier instance. + (getAlreadyTrustPublisher), (getRootInCacerts): Now uses strategy pattern. + (hasSigningIssues), (getDetails), (checkTrustedCerts), (checkCertUsage): + Now uses cert info class. + (getCerts): Renamed to getCertsList. + (isFullySignedByASingleCert): renamed to isFullySigned and to use + the strategy pattern. + (add): New public method that resets some instance vars and + calls verifyJars. + (verifyJars): Modifier changed to private, above method should be used. + Also skips jars that have been verified before. + (verifyJar): Removed actual verification code, only reads jars into the JVM. + (verifyJarEntryCerts): New method. Does actual verification of jars. + (getPublisher), (getRoot): Use hacky currentlyUsed variable as the signer. + * tests/netx/unit/net/sourceforge/jnlp/tools/JarCertVerifierTest.java: + Unit test JCV's verifyJarEntryCerts method. + * tests/test-extensions/net/sourceforge/jnlp/tools/CodeSignerCreator.java: + Unit test helper that creates CodeSigner instances. + 2012-10-16 Adam Domurad <[email protected]> * tests/reproducers/simple/AppletTakesLastParam/srcs/AppletTakesLastParam.java: @@ -19,6 +19,7 @@ New in release 1.4 (2012-XX-XX): - PR955: regression: SweetHome3D fails to run - PR1145: IcedTea-Web can cause ClassCircularityError - PR1161: X509VariableTrustManager does not work correctly with OpenJDK7 + - PR822: Applets fail to load if jars have different signers New in release 1.3 (2012-XX-XX): * NetX diff --git a/netx/net/sourceforge/jnlp/resources/Messages.properties b/netx/net/sourceforge/jnlp/resources/Messages.properties index 1f3a1e5..4010837 100644 --- a/netx/net/sourceforge/jnlp/resources/Messages.properties +++ b/netx/net/sourceforge/jnlp/resources/Messages.properties @@ -82,7 +82,7 @@ LSignedAppJarUsingUnsignedJar=Signed application using unsigned jars. LSignedAppJarUsingUnsignedJarInfo=The main application jar is signed, but some of the jars it is using aren't.
LSignedJNLPFileDidNotMatch=The signed JNLP file did not match the launching JNLP file.
LNoSecInstance=Error: No security instance for {0}. The application may have trouble continuing
-LCertFoundIn={0} found in cacerts ({1}) +LCertFoundIn={0} found in cacerts ({1})
LSingleInstanceExists=Another instance of this applet already exists and only one may be run at the same time.
JNotApplet=File is not an applet.
@@ -227,7 +227,6 @@ SJNLPFileIsNotSigned=This application contains a digital signature in which the SBadKeyUsage=Resources contain entries whose signer certificate's KeyUsage extension doesn't allow code signing.
SBadExtendedKeyUsage=Resources contain entries whose signer certificate's ExtendedKeyUsage extension doesn't allow code signing.
SBadNetscapeCertType=Resources contain entries whose signer certificate's NetscapeCertType extension doesn't allow code signing.
-SHasUnsignedEntry=Resources contain unsigned entries which have not been integrity-checked.
SHasExpiredCert=The digital signature has expired.
SHasExpiringCert=Resources contain entries whose signer certificate will expire within six months.
SNotYetValidCert=Resources contain entries whose signer certificate is not yet valid.
diff --git a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java index 523d9bd..94d93a4 100644 --- a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java +++ b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java @@ -79,8 +79,10 @@ import net.sourceforge.jnlp.cache.CacheUtil; import net.sourceforge.jnlp.cache.IllegalResourceDescriptorException; import net.sourceforge.jnlp.cache.ResourceTracker; import net.sourceforge.jnlp.cache.UpdatePolicy; +import net.sourceforge.jnlp.security.AppVerifier; +import net.sourceforge.jnlp.security.JNLPAppVerifier; +import net.sourceforge.jnlp.security.PluginAppVerifier; import net.sourceforge.jnlp.security.SecurityDialogs; -import net.sourceforge.jnlp.security.SecurityDialogs.AccessType; import net.sourceforge.jnlp.tools.JarCertVerifier; import net.sourceforge.jnlp.util.FileUtils; import sun.misc.JarIndex; @@ -153,14 +155,8 @@ public class JNLPClassLoader extends URLClassLoader { /** all jars not yet part of classloader or active */ private List<JARDesc> available = new ArrayList<JARDesc>(); - /** 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 jar cert verifier tool to verify our jars */ - private JarCertVerifier jcv = null; + private final JarCertVerifier jcv; private boolean signing = false; @@ -223,6 +219,16 @@ public class JNLPClassLoader extends URLClassLoader { this.mainClass = mainName; + AppVerifier verifier; + + if (file instanceof PluginBridge && !((PluginBridge)file).useJNLPHref()) { + verifier = new PluginAppVerifier(); + } else { + verifier = new JNLPAppVerifier(); + } + + jcv = new JarCertVerifier(verifier); + // initialize extensions initializeExtensions(); @@ -604,10 +610,8 @@ public class JNLPClassLoader extends URLClassLoader { if (JNLPRuntime.isVerifying()) { - JarCertVerifier jcv; - try { - jcv = verifyJars(initialJars); + jcv.add(initialJars, tracker); } catch (Exception e) { //we caught an Exception from the JarCertVerifier class. //Note: one of these exceptions could be from not being able @@ -618,7 +622,7 @@ public class JNLPClassLoader extends URLClassLoader { } //Case when at least one jar has some signing - if (jcv.anyJarsSigned() && jcv.isFullySignedByASingleCert()) { + if (jcv.isFullySigned()) { signing = true; if (!jcv.allJarsSigned() && @@ -650,10 +654,10 @@ public class JNLPClassLoader extends URLClassLoader { // If main jar was found, but a signed JNLP file was not located if (!isSignedJNLP && foundMainJar) file.setSignedJNLPAsMissing(); - + //user does not trust this publisher - if (!jcv.getAlreadyTrustPublisher() && !jcv.isTriviallySigned()) { - checkTrustWithUser(jcv); + if (!jcv.isTriviallySigned()) { + checkTrustWithUser(); } else { /** * If the user trusts this publisher (i.e. the publisher's certificate @@ -864,7 +868,6 @@ public class JNLPClassLoader extends URLClassLoader { private void verifySignedJNLP(JARDesc jarDesc, JarFile jarFile) throws LaunchException { - JarCertVerifier signer = new JarCertVerifier(); List<JARDesc> desc = new ArrayList<JARDesc>(); desc.add(jarDesc); @@ -875,9 +878,9 @@ public class JNLPClassLoader extends URLClassLoader { InputStreamReader jnlpReader = null; try { - signer.verifyJars(desc, tracker); - - if (signer.allJarsSigned()) { // If the jar is signed + // NOTE: verification should have happened by now. In other words, + // calling jcv.verifyJars(desc, tracker) here should have no affect. + if (jcv.isFullySigned()) { Enumeration<JarEntry> entries = jarFile.entries(); JarEntry je; @@ -961,7 +964,7 @@ public class JNLPClassLoader extends URLClassLoader { /* * After this exception is caught, it is escaped. If an exception is * thrown while handling the jar file, (mainly for - * JarCertVerifier.verifyJars) it assumes the jar file is unsigned and + * JarCertVerifier.add) it assumes the jar file is unsigned and * skip the check for a signed JNLP file */ @@ -991,28 +994,18 @@ public class JNLPClassLoader extends URLClassLoader { e.printStackTrace(System.err); } } - - private void checkTrustWithUser(JarCertVerifier jcv) throws LaunchException { + + /** + * Prompt the user for trust on all the signers that require approval. + * @throws LaunchException if the user does not approve every dialog prompt. + */ + private void checkTrustWithUser() throws LaunchException { if (JNLPRuntime.isTrustAll()){ return; } - if (!jcv.getRootInCacerts()) { //root cert is not in cacerts - boolean b = SecurityDialogs.showCertWarningDialog( - AccessType.UNVERIFIED, file, jcv); - if (!b) - throw new LaunchException(null, null, R("LSFatal"), - R("LCLaunching"), R("LNotVerified"), ""); - } else if (jcv.getRootInCacerts()) { //root cert is in cacerts - boolean b = false; - if (jcv.noSigningIssues()) - b = SecurityDialogs.showCertWarningDialog( - AccessType.VERIFIED, file, jcv); - else if (!jcv.noSigningIssues()) - b = SecurityDialogs.showCertWarningDialog( - AccessType.SIGNING_ERROR, file, jcv); - if (!b) - throw new LaunchException(null, null, R("LSFatal"), - R("LCLaunching"), R("LCancelOnUserRequest"), ""); + + if (jcv.isFullySigned() && !jcv.getAlreadyTrustPublisher()) { + jcv.checkTrustWithUser(file); } } @@ -1226,15 +1219,25 @@ public class JNLPClassLoader extends URLClassLoader { continue; } - JarCertVerifier signer = new JarCertVerifier(); - List<JARDesc> jars = new ArrayList<JARDesc>(); - JARDesc jarDesc = new JARDesc(new File(extractedJarLocation).toURL(), null, null, false, false, false, false); - jars.add(jarDesc); tracker.addResource(new File(extractedJarLocation).toURL(), null, null, null); - signer.verifyJars(jars, tracker); - if (signer.anyJarsSigned() && !signer.getAlreadyTrustPublisher()) { - checkTrustWithUser(signer); + URL codebase = file.getCodeBase(); + if (codebase == null) { + //FIXME: codebase should be the codebase of the Main Jar not + //the location. Although, it still works in the current state. + codebase = file.getResources().getMainJAR().getLocation(); + } + + SecurityDesc jarSecurity = null; + if (jcv.isFullySigned()) { + // Already trust application, nested jar should be given + jarSecurity = new SecurityDesc(file, + SecurityDesc.ALL_PERMISSIONS, + codebase.getHost()); + } else { + jarSecurity = new SecurityDesc(file, + SecurityDesc.SANDBOX_PERMISSIONS, + codebase.getHost()); } try { @@ -1244,25 +1247,6 @@ public class JNLPClassLoader extends URLClassLoader { CachedJarFileCallback.getInstance().addMapping(fakeRemote, fileURL); addURL(fakeRemote); - SecurityDesc jarSecurity = file.getSecurity(); - - if (file instanceof PluginBridge) { - - URL codebase = null; - - if (file.getCodeBase() != null) { - codebase = file.getCodeBase(); - } else { - //Fixme: codebase should be the codebase of the Main Jar not - //the location. Although, it still works in the current state. - codebase = file.getResources().getMainJAR().getLocation(); - } - - jarSecurity = new SecurityDesc(file, - SecurityDesc.ALL_PERMISSIONS, - codebase.getHost()); - } - jarLocationSecurityMap.put(fakeRemote, jarSecurity); } catch (MalformedURLException mfue) { @@ -1475,18 +1459,6 @@ public class JNLPClassLoader extends URLClassLoader { } /** - * Verifies code signing of jars to be used. - * - * @param jars the jars to be verified. - */ - private JarCertVerifier verifyJars(List<JARDesc> jars) throws Exception { - - jcv = new JarCertVerifier(); - jcv.verifyJars(jars, tracker); - return jcv; - } - - /** * Find the loaded class in this loader or any of its extension loaders. */ protected Class findLoadedClassAll(String name) { @@ -1642,7 +1614,6 @@ public class JNLPClassLoader extends URLClassLoader { // Verify if needed - final JarCertVerifier signer = new JarCertVerifier(); final List<JARDesc> jars = new ArrayList<JARDesc>(); jars.add(desc); @@ -1654,14 +1625,12 @@ public class JNLPClassLoader extends URLClassLoader { AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { public Void run() throws Exception { - signer.verifyJars(jars, tracker); + jcv.add(jars, tracker); - if (signer.anyJarsSigned() && !signer.getAlreadyTrustPublisher()) { - checkTrustWithUser(signer); - } + checkTrustWithUser(); final SecurityDesc security; - if (signer.anyJarsSigned()) { + if (jcv.isFullySigned()) { security = new SecurityDesc(file, SecurityDesc.ALL_PERMISSIONS, file.getCodeBase().getHost()); diff --git a/netx/net/sourceforge/jnlp/security/AppVerifier.java b/netx/net/sourceforge/jnlp/security/AppVerifier.java new file mode 100644 index 0000000..2fc1ec0 --- /dev/null +++ b/netx/net/sourceforge/jnlp/security/AppVerifier.java @@ -0,0 +1,91 @@ +/* AppVerifier.java + Copyright (C) 2012 Red Hat, Inc. + +This file is part of IcedTea. + +IcedTea is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 2. + +IcedTea 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 for more details. + +You should have received a copy of the GNU General Public License +along with IcedTea; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. +*/ + +package net.sourceforge.jnlp.security; + +import java.security.cert.CertPath; +import java.util.HashMap; + +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.LaunchException; +import net.sourceforge.jnlp.tools.CertInformation; +import net.sourceforge.jnlp.tools.JarCertVerifier; + +/** + * An interface that provides various details about an app's signers. + */ +public interface AppVerifier { + + /** + * Checks if the app has already found trust in its publisher(s). + * @param certs The certs to search through and their cert information + * @param signedJars A map of all the jars of this app and the number of + * signed entries each one has. + * @return True if the app trusts its publishers. + */ + public boolean hasAlreadyTrustedPublisher( + HashMap<CertPath, CertInformation> certs, + HashMap<String, Integer> signedJars); + + /** + * Checks if the app has signer(s) whose certs along their chains are in CA certs. + * @param certs The certs to search through and their cert information + * @param signedJars A map of all the jars of this app and the number of + * signed entries each one has. + * @return True if the app has a root in the CA certs store. + */ + public boolean hasRootInCacerts(HashMap<CertPath, CertInformation> certs, + HashMap<String, Integer> signedJars); + + /** + * Checks if the app's jars are covered by the provided certificates, enough + * to consider the app fully signed. + * @param certs Any possible signer and their respective information regarding this app. + * @param signedJars A map of all the jars of this app and the number of + * signed entries each one has. + * @return + */ + public boolean isFullySigned(HashMap<CertPath, CertInformation> certs, + HashMap<String, Integer> signedJars); + + /** + * Prompt the user with requests for trusting the certificates used by this app + * @throws LaunchException + */ + public void checkTrustWithUser(JarCertVerifier jcv, JNLPFile file) + throws LaunchException; +} diff --git a/netx/net/sourceforge/jnlp/security/CertVerifier.java b/netx/net/sourceforge/jnlp/security/CertVerifier.java index 842a865..7769884 100644 --- a/netx/net/sourceforge/jnlp/security/CertVerifier.java +++ b/netx/net/sourceforge/jnlp/security/CertVerifier.java @@ -1,5 +1,5 @@ /* CertVerifier.java - Copyright (C) 2009 Red Hat, Inc. + Copyright (C) 2012 Red Hat, Inc. This file is part of IcedTea. @@ -39,10 +39,10 @@ package net.sourceforge.jnlp.security; import java.security.cert.CertPath; import java.security.cert.Certificate; -import java.util.ArrayList; +import java.util.List; /** - * An interface that provides various details about a certificate + * An interface that provides various details about certificates of an app. */ public interface CertVerifier { @@ -58,36 +58,30 @@ public interface CertVerifier { public boolean getRootInCacerts(); /** - * Return if there are signing issues with the certificate(s) being veried + * Return if there are signing issues with the certificate being verified */ - public boolean hasSigningIssues(); + public boolean hasSigningIssues(CertPath certPath); /** - * Return if there are no signing issues with this cert (!hasSigningIssues()) + * Get the details regarding issue with this certificate */ - public boolean noSigningIssues(); + public List<String> getDetails(CertPath certPath); /** - * Get the details regarding issue(s) with this certificate - */ - public ArrayList<String> getDetails(); - - /** - * Return a valid certificate path to this certificate(s) being verified + * Return a valid certificate path to this certificate being verified * @return The CertPath */ - public CertPath getCertPath(); + public CertPath getCertPath(CertPath certPath); /** * Returns the application's publisher's certificate. */ - public abstract Certificate getPublisher(); + public abstract Certificate getPublisher(CertPath certPath); /** * Returns the application's root's certificate. This - * may return the same certificate as getPublisher() in + * may return the same certificate as getPublisher(CertPath certPath) in * the event that the application is self signed. */ - public abstract Certificate getRoot(); - + public abstract Certificate getRoot(CertPath certPath); } diff --git a/netx/net/sourceforge/jnlp/security/CertWarningPane.java b/netx/net/sourceforge/jnlp/security/CertWarningPane.java index eedd86e..fff814c 100644 --- a/netx/net/sourceforge/jnlp/security/CertWarningPane.java +++ b/netx/net/sourceforge/jnlp/security/CertWarningPane.java @@ -96,7 +96,7 @@ public class CertWarningPane extends SecurityDialogPanel { private void addComponents() { AccessType type = parent.getAccessType(); JNLPFile file = parent.getFile(); - Certificate c = parent.getCertVerifier().getPublisher(); + Certificate c = parent.getCertVerifier().getPublisher(null); String name = ""; String publisher = ""; @@ -253,7 +253,7 @@ public class CertWarningPane extends SecurityDialogPanel { if (alwaysTrust != null && alwaysTrust.isSelected()) { try { KeyStore ks = KeyStores.getKeyStore(Level.USER, Type.CERTS); - X509Certificate c = (X509Certificate) parent.getCertVerifier().getPublisher(); + X509Certificate c = (X509Certificate) parent.getCertVerifier().getPublisher(null); CertificateUtils.addToKeyStore(c, ks); File keyStoreFile = new File(KeyStores.getKeyStoreLocation(Level.USER, Type.CERTS)); if (!keyStoreFile.isFile()) { diff --git a/netx/net/sourceforge/jnlp/security/CertsInfoPane.java b/netx/net/sourceforge/jnlp/security/CertsInfoPane.java index c4f7c67..d22a590 100644 --- a/netx/net/sourceforge/jnlp/security/CertsInfoPane.java +++ b/netx/net/sourceforge/jnlp/security/CertsInfoPane.java @@ -84,7 +84,7 @@ public class CertsInfoPane extends SecurityDialogPanel { * Builds the JTree out of CertPaths. */ void buildTree() { - certPath = parent.getCertVerifier().getCertPath(); + certPath = parent.getCertVerifier().getCertPath(null); X509Certificate firstCert = ((X509Certificate) certPath.getCertificates().get(0)); String subjectString = diff --git a/netx/net/sourceforge/jnlp/security/HttpsCertVerifier.java b/netx/net/sourceforge/jnlp/security/HttpsCertVerifier.java index 8490cf8..edd5899 100644 --- a/netx/net/sourceforge/jnlp/security/HttpsCertVerifier.java +++ b/netx/net/sourceforge/jnlp/security/HttpsCertVerifier.java @@ -80,7 +80,14 @@ public class HttpsCertVerifier implements CertVerifier { return isTrusted; } - public CertPath getCertPath() { + + /* XXX: Most of these methods have a CertPath param that should be passed + * from the UI dialogs. However, this is not implemented yet so most of + * the params are ignored. + */ + + @Override + public CertPath getCertPath(CertPath certPath) { // Parameter ignored. ArrayList<X509Certificate> list = new ArrayList<X509Certificate>(); for (int i = 0; i < chain.length; i++) @@ -99,7 +106,8 @@ public class HttpsCertVerifier implements CertVerifier { return certPaths.get(0); } - public ArrayList<String> getDetails() { + @Override + public List<String> getDetails(CertPath certPath) { // Parameter ignored. boolean hasExpiredCert = false; boolean hasExpiringCert = false; @@ -192,13 +200,15 @@ public class HttpsCertVerifier implements CertVerifier { details.add(detail); } - public Certificate getPublisher() { + @Override + public Certificate getPublisher(CertPath certPath) { // Paramater ignored. if (chain.length > 0) return (Certificate) chain[0]; return null; } - public Certificate getRoot() { + @Override + public Certificate getRoot(CertPath certPath) { // Parameter ignored. if (chain.length > 0) return (Certificate) chain[chain.length - 1]; return null; @@ -207,18 +217,14 @@ public class HttpsCertVerifier implements CertVerifier { public boolean getRootInCacerts() { try { KeyStore[] caCertsKeyStores = KeyStores.getCAKeyStores(); - return CertificateUtils.inKeyStores((X509Certificate) getRoot(), caCertsKeyStores); + return CertificateUtils.inKeyStores((X509Certificate) getRoot(null), caCertsKeyStores); } catch (Exception e) { } return false; } - public boolean hasSigningIssues() { - return false; - } - - public boolean noSigningIssues() { + @Override + public boolean hasSigningIssues(CertPath certPath) { return false; } - } diff --git a/netx/net/sourceforge/jnlp/security/JNLPAppVerifier.java b/netx/net/sourceforge/jnlp/security/JNLPAppVerifier.java new file mode 100644 index 0000000..9dcfaf5 --- /dev/null +++ b/netx/net/sourceforge/jnlp/security/JNLPAppVerifier.java @@ -0,0 +1,143 @@ +/* JNLPAppVerifier.java + Copyright (C) 2012 Red Hat, Inc. + +This file is part of IcedTea. + +IcedTea is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 2. + +IcedTea 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 for more details. + +You should have received a copy of the GNU General Public License +along with IcedTea; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. + */ + +package net.sourceforge.jnlp.security; + +import static net.sourceforge.jnlp.runtime.Translator.R; + +import java.security.cert.CertPath; +import java.util.HashMap; +import java.util.Map; + +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.LaunchException; +import net.sourceforge.jnlp.security.SecurityDialogs.AccessType; +import net.sourceforge.jnlp.tools.CertInformation; +import net.sourceforge.jnlp.tools.JarCertVerifier; + +public class JNLPAppVerifier implements AppVerifier { + + @Override + public boolean hasAlreadyTrustedPublisher( + HashMap<CertPath, CertInformation> certs, + HashMap<String, Integer> signedJars) { + int sumOfSignableEntries = JarCertVerifier.getTotalJarEntries(signedJars); + for (CertInformation certInfo : certs.values()) { + Map<String, Integer> certSignedJars = certInfo.getSignedJars(); + + if (JarCertVerifier.getTotalJarEntries(certSignedJars) == sumOfSignableEntries + && certInfo.isPublisherAlreadyTrusted()) { + return true; + } + } + return false; + } + + @Override + public boolean hasRootInCacerts(HashMap<CertPath, CertInformation> certs, + HashMap<String, Integer> signedJars) { + int sumOfSignableEntries = JarCertVerifier.getTotalJarEntries(signedJars); + for (CertInformation certInfo : certs.values()) { + Map<String, Integer> certSignedJars = certInfo.getSignedJars(); + + if (JarCertVerifier.getTotalJarEntries(certSignedJars) == sumOfSignableEntries + && certInfo.isRootInCacerts()) { + return true; + } + } + return false; + } + + @Override + public boolean isFullySigned(HashMap<CertPath, CertInformation> certs, + HashMap<String, Integer> signedJars) { + int sumOfSignableEntries = JarCertVerifier.getTotalJarEntries(signedJars); + for (CertPath cPath : certs.keySet()) { + // If this cert has signed everything, return true + if (hasCompletelySignedApp(certs.get(cPath), sumOfSignableEntries)) { + return true; + } + } + + // No cert found that signed all entries. Return false. + return false; + } + + @Override + public void checkTrustWithUser(JarCertVerifier jcv, JNLPFile file) + throws LaunchException { + + int sumOfSignableEntries = JarCertVerifier.getTotalJarEntries(jcv.getJarSignableEntries()); + for (CertPath cPath : jcv.getCertsList()) { + jcv.setCurrentlyUsedCertPath(cPath); + CertInformation info = jcv.getCertInformation(cPath); + if (hasCompletelySignedApp(info, sumOfSignableEntries)) { + if (info.isPublisherAlreadyTrusted()) { + return; + } + + AccessType dialogType; + if (info.isRootInCacerts() && !info.hasSigningIssues()) { + dialogType = AccessType.VERIFIED; + } else if (info.isRootInCacerts()) { + dialogType = AccessType.SIGNING_ERROR; + } else { + dialogType = AccessType.UNVERIFIED; + } + + boolean wasApproved = SecurityDialogs.showCertWarningDialog( + dialogType, file, jcv); + if (wasApproved) { + return; + } + } + } + + throw new LaunchException(null, null, R("LSFatal"), R("LCLaunching"), + R("LCancelOnUserRequest"), ""); + } + + /** + * Find out if the CertPath with the given info has fully signed the app. + * @param info The information regarding the CertPath in question + * @param sumOfSignableEntries The total number of signable entries in the app. + * @return True if the signer has fully signed this app. + */ + public boolean hasCompletelySignedApp(CertInformation info, int sumOfSignableEntries) { + return JarCertVerifier.getTotalJarEntries(info.getSignedJars()) == sumOfSignableEntries; + } +} diff --git a/netx/net/sourceforge/jnlp/security/MoreInfoPane.java b/netx/net/sourceforge/jnlp/security/MoreInfoPane.java index 3276ea2..1ccd302 100644 --- a/netx/net/sourceforge/jnlp/security/MoreInfoPane.java +++ b/netx/net/sourceforge/jnlp/security/MoreInfoPane.java @@ -44,7 +44,7 @@ import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.ArrayList; +import java.util.List; import javax.swing.BorderFactory; import javax.swing.ImageIcon; @@ -73,7 +73,7 @@ public class MoreInfoPane extends SecurityDialogPanel { * Constructs the GUI components of this panel */ private void addComponents() { - ArrayList<String> details = certVerifier.getDetails(); + List<String> details = certVerifier.getDetails(null); // Show signed JNLP warning if the signed main jar does not have a // signed JNLP file and the launching JNLP file contains special properties diff --git a/netx/net/sourceforge/jnlp/security/PluginAppVerifier.java b/netx/net/sourceforge/jnlp/security/PluginAppVerifier.java new file mode 100644 index 0000000..a8589d8 --- /dev/null +++ b/netx/net/sourceforge/jnlp/security/PluginAppVerifier.java @@ -0,0 +1,224 @@ +/* PluginAppVerifier.java + Copyright (C) 2012 Red Hat, Inc. + +This file is part of IcedTea. + +IcedTea is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 2. + +IcedTea 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 for more details. + +You should have received a copy of the GNU General Public License +along with IcedTea; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. + */ + +package net.sourceforge.jnlp.security; + +import static net.sourceforge.jnlp.runtime.Translator.R; + +import java.security.cert.CertPath; +import java.util.ArrayList; +import java.util.HashMap; + +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.LaunchException; +import net.sourceforge.jnlp.security.SecurityDialogs.AccessType; +import net.sourceforge.jnlp.tools.CertInformation; +import net.sourceforge.jnlp.tools.JarCertVerifier; + +public class PluginAppVerifier implements AppVerifier { + + @Override + public boolean hasAlreadyTrustedPublisher( + HashMap<CertPath, CertInformation> certs, + HashMap<String, Integer> signedJars) { + + boolean allPublishersTrusted = true; + + for(String jarName : signedJars.keySet()) { + int numbSignableEntries = signedJars.get(jarName); + boolean publisherTrusted = false; + + for (CertInformation certInfo : certs.values()) { + if(certInfo.isSignerOfJar(jarName) + && numbSignableEntries == certInfo.getNumJarEntriesSigned(jarName) + && certInfo.isPublisherAlreadyTrusted()) { + publisherTrusted = true; + break; + } + } + + allPublishersTrusted &= publisherTrusted; + } + return allPublishersTrusted; + } + + @Override + public boolean hasRootInCacerts(HashMap<CertPath, CertInformation> certs, + HashMap<String, Integer> signedJars) { + + boolean allRootCAsTrusted = true; + + for(String jarName : signedJars.keySet()) { + int numbSignableEntries = signedJars.get(jarName); + boolean rootCATrusted = false; + + for (CertInformation certInfo : certs.values()) { + if(certInfo.isSignerOfJar(jarName) + && numbSignableEntries == certInfo.getNumJarEntriesSigned(jarName) + && certInfo.isRootInCacerts()) { + rootCATrusted = true; + break; + } + } + + allRootCAsTrusted &= rootCATrusted; + } + return allRootCAsTrusted; + } + + @Override + public boolean isFullySigned(HashMap<CertPath, CertInformation> certs, + HashMap<String, Integer> signedJars) { + + boolean isFullySigned = true; + + for(String jarName : signedJars.keySet()) { + int numbSignableEntries = signedJars.get(jarName); + boolean isSigned = false; + + for (CertInformation certInfo : certs.values()) { + if(certInfo.isSignerOfJar(jarName) + && numbSignableEntries == certInfo.getNumJarEntriesSigned(jarName)) { + isSigned = true; + break; + } + } + + isFullySigned &= isSigned; + } + + return isFullySigned; + } + + @Override + public void checkTrustWithUser(JarCertVerifier jcv, JNLPFile file) + throws LaunchException { + ArrayList<CertPath> certPaths = buildCertPathsList(jcv); + ArrayList<CertPath> alreadyApprovedByUser = new ArrayList<CertPath>(); + for (String jarName : jcv.getJarSignableEntries().keySet()) { + boolean trustFoundOrApproved = false; + for (CertPath cPathApproved : alreadyApprovedByUser) { + jcv.setCurrentlyUsedCertPath(cPathApproved); + CertInformation info = jcv.getCertInformation(cPathApproved); + if (info.isSignerOfJar(jarName) + && alreadyApprovedByUser.contains(cPathApproved)) { + trustFoundOrApproved = true; + break; + } + } + + if (trustFoundOrApproved) { + continue; + } + + for (CertPath cPath : certPaths) { + jcv.setCurrentlyUsedCertPath(cPath); + CertInformation info = jcv.getCertInformation(cPath); + if (info.isSignerOfJar(jarName)) { + if (info.isPublisherAlreadyTrusted()) { + trustFoundOrApproved = true; + alreadyApprovedByUser.add(cPath); + break; + } + + AccessType dialogType; + if (info.isRootInCacerts() && !info.hasSigningIssues()) { + dialogType = AccessType.VERIFIED; + } else if (info.isRootInCacerts()) { + dialogType = AccessType.SIGNING_ERROR; + } else { + dialogType = AccessType.UNVERIFIED; + } + + boolean wasApproved = SecurityDialogs.showCertWarningDialog( + dialogType, file, jcv); + if (wasApproved) { + alreadyApprovedByUser.add(cPath); + trustFoundOrApproved = true; + break; + } + } + } + if (!trustFoundOrApproved) { + throw new LaunchException(null, null, R("LSFatal"), + R("LCLaunching"), R("LCancelOnUserRequest"), ""); + } + } + } + + /** + * Build a list of all the CertPaths that were detected in the provided + * JCV, placing them in the most trusted possible order. + * @param jcv The verifier containing the CertPaths to examine. + * @return A list of CertPaths sorted in the following order: Signers with + * 1. Already trusted publishers + * 2. Roots in the CA store and have no signing issues + * 3. Roots in the CA store but have signing issues + * 4. Everything else + */ + public ArrayList<CertPath> buildCertPathsList(JarCertVerifier jcv) { + ArrayList<CertPath> certPathsList = jcv.getCertsList(); + ArrayList<CertPath> returnList = new ArrayList<CertPath>(); + + for (CertPath cPath : certPathsList) { + if (!returnList.contains(cPath) + && jcv.getCertInformation(cPath).isPublisherAlreadyTrusted()) + returnList.add(cPath); + } + + for (CertPath cPath : certPathsList) { + if (!returnList.contains(cPath) + && jcv.getCertInformation(cPath).isRootInCacerts() + && !jcv.getCertInformation(cPath).hasSigningIssues()) + returnList.add(cPath); + } + + for (CertPath cPath : certPathsList) { + if (!returnList.contains(cPath) + && jcv.getCertInformation(cPath).isRootInCacerts() + && jcv.getCertInformation(cPath).hasSigningIssues()) + returnList.add(cPath); + } + + for (CertPath cPath : certPathsList) { + if (!returnList.contains(cPath)) + returnList.add(cPath); + } + + return returnList; + } +} diff --git a/netx/net/sourceforge/jnlp/tools/CertInformation.java b/netx/net/sourceforge/jnlp/tools/CertInformation.java new file mode 100644 index 0000000..6d6d27e --- /dev/null +++ b/netx/net/sourceforge/jnlp/tools/CertInformation.java @@ -0,0 +1,292 @@ +/* CertInformation.java + Copyright (C) 2012 Red Hat, Inc. + +This file is part of IcedTea. + +IcedTea is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 2. + +IcedTea 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 for more details. + +You should have received a copy of the GNU General Public License +along with IcedTea; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. + */ + +package net.sourceforge.jnlp.tools; + +import static net.sourceforge.jnlp.runtime.Translator.R; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.sourceforge.jnlp.runtime.JNLPRuntime; + +/** + * Maintains information about a CertPath that has signed at least one of the + * entries provided by a jar of the app. + */ +public class CertInformation { + + private boolean hasExpiredCert = false; + private boolean hasExpiringCert = false; + + private boolean isNotYetValidCert = false; + + /* Code signer properties of the certificate. */ + private boolean hasBadKeyUsage = false; + private boolean hasBadExtendedKeyUsage = false; + private boolean hasBadNetscapeCertType = false; + + private boolean alreadyTrustPublisher = false; + private boolean rootInCacerts = false; + + static enum Detail { + TRUSTED (R("STrustedCertificate")), + UNTRUSTED (R("SUntrustedCertificate")), + RUN_WITHOUT_RESTRICTIONS(R("SRunWithoutRestrictions")), + EXPIRED (R("SHasExpiredCert")), + EXPIRING (R("SHasExpiringCert")), + NOT_YET_VALID (R("SNotYetValidCert")), + BAD_KEY_USAGE (R("SBadKeyUsage")), + BAT_EXTENDED_KEY_USAGE (R("SBadExtendedKeyUsage")), + BAD_NETSCAPE_CERT_TYPE (R("SBadNetscapeCertType")); + + private final String message; + Detail(String issue) { + message = issue; + } + + public String message() { + return message; + } + } + + private EnumSet<Detail> details = EnumSet.noneOf(Detail.class); + + /** The jars and their number of entries this cert has signed. */ + private HashMap<String, Integer> signedJars = new HashMap<String, Integer>(); + + /** + * Return if there are signing issues with this certificate. + * @return true if there are any issues with expiry, validity or bad key usage. + */ + public boolean hasSigningIssues() { + return hasExpiredCert || isNotYetValidCert || hasBadKeyUsage + || hasBadExtendedKeyUsage || hasBadNetscapeCertType; + } + + /** + * Return whether or not the publisher is already trusted. + * + * @return True if the publisher is trusted already. + */ + public boolean isPublisherAlreadyTrusted() { + return alreadyTrustPublisher; + } + + /** + * Set whether or not the publisher is already trusted. + * + */ + public void setAlreadyTrustPublisher() { + alreadyTrustPublisher = true; + } + + /** + * Return whether or not the root is in the list of trusted CA certificates. + * + * @return True if the root is in the list of CA certificates. + */ + public boolean isRootInCacerts() { + return rootInCacerts; + } + + /** + * Set that this cert's root CA is to be trusted. + */ + public void setRootInCacerts() { + rootInCacerts = true; + details.add(Detail.TRUSTED); + } + + /** + * Resets any trust of the root and publisher. Also removes unnecessary + * details from the list of issues. + */ + public void resetForReverification() { + alreadyTrustPublisher = false; + rootInCacerts = false; + removeFromDetails(Detail.UNTRUSTED); + removeFromDetails(Detail.TRUSTED); + } + /** + * Check if this cert is the signer of a jar. + * @param jarName The absolute path of the jar this certificate has signed. + * @return true if this cert has signed the jar found at jarName. + */ + public boolean isSignerOfJar(String jarName) { + return signedJars.containsKey(jarName); + } + + /** + * Add a jar to the list of jars this certificate has signed along with the + * number of entries it has signed in the jar. + * + * @param jarName The absolute path of the jar this certificate has signed. + * @param signedEntriesCount The number of entries this cert has signed in jarName. + */ + public void setNumJarEntriesSigned(String jarName, int signedEntriesCount) { + if (signedJars.containsKey(jarName)) { + if (JNLPRuntime.isDebug()) + System.err.println("WARNING: A jar that has already been " + + "verified is being yet again verified: " + jarName); + } else { + signedJars.put(jarName, signedEntriesCount); + } + } + + /** + * Find the number of entries this cert has signed in the specified jar. + * @param jarName The absolute path of the jar this certificate has signed. + * @return The number of entries this cert has signed in jarName. + */ + public int getNumJarEntriesSigned(String jarName) { + return signedJars.get(jarName); + } + + /** + * Get all the jars this cert has signed along with the number of entries + * in each jar. + * @return + */ + public Map<String, Integer> getSignedJars() { + return signedJars; + } + + /** + * Get the details regarding issue(s) with this certificate. + * + * @return A list of all the details/issues with this app. + */ + public List<String> getDetailsAsStrings() { + List<String> detailsToStr = new ArrayList<String>(); + for (Detail issue : details) { + detailsToStr.add(issue.message()); + } + return detailsToStr; + } + + /** + * Remove an issue from the list of details of issues with this certificate. + * List is unchanged if detail was not present. + * + * @param detail The issue to be removed regarding this certificate. + */ + private void removeFromDetails(Detail detail) { + details.remove(detail); + } + + /** + * Set that this cert is expired and add this issue to the list of details. + */ + public void setHasExpiredCert() { + hasExpiredCert = true; + details.add(Detail.RUN_WITHOUT_RESTRICTIONS); + details.add(Detail.EXPIRED); + } + + /** + * Set that this cert is expiring within 6 months and add this issue to + * the list of details. + */ + public void setHasExpiringCert() { + hasExpiringCert = true; + details.add(Detail.RUN_WITHOUT_RESTRICTIONS); + details.add(Detail.EXPIRING); + } + + /** + * Get whether or not this cert will expire within 6 months. + * @return true if the cert will be expired after 6 months. + */ + public boolean hasExpiringCert() { + return hasExpiringCert; + } + + /** + * Set that this cert is not yet valid + * and add this issue to the list of details. + */ + public void setNotYetValidCert() { + isNotYetValidCert = true; + details.add(Detail.RUN_WITHOUT_RESTRICTIONS); + details.add(Detail.NOT_YET_VALID); + } + + + /** + * Set that this cert has bad key usage + * and add this issue to the list of details. + */ + public void setBadKeyUsage() { + hasBadKeyUsage = true; + details.add(Detail.RUN_WITHOUT_RESTRICTIONS); + details.add(Detail.BAD_KEY_USAGE); + } + + + /** + * Set that this cert has bad extended key usage + * and add this issue to the list of details. + */ + public void setBadExtendedKeyUsage() { + hasBadExtendedKeyUsage = true; + details.add(Detail.RUN_WITHOUT_RESTRICTIONS); + details.add(Detail.BAT_EXTENDED_KEY_USAGE); + } + + + /** + * Set that this cert has a bad netscape cert type + * and add this issue to the list of details. + */ + public void setBadNetscapeCertType() { + hasBadNetscapeCertType = true; + details.add(Detail.RUN_WITHOUT_RESTRICTIONS); + details.add(Detail.BAD_NETSCAPE_CERT_TYPE); + } + + /** + * Set that this cert and all of its CAs are untrusted so far. + */ + public void setUntrusted() { + details.add(Detail.UNTRUSTED); + + } +} diff --git a/netx/net/sourceforge/jnlp/tools/JarCertVerifier.java b/netx/net/sourceforge/jnlp/tools/JarCertVerifier.java index 520880a..01426f3 100644 --- a/netx/net/sourceforge/jnlp/tools/JarCertVerifier.java +++ b/netx/net/sourceforge/jnlp/tools/JarCertVerifier.java @@ -25,25 +25,41 @@ package net.sourceforge.jnlp.tools; -import static net.sourceforge.jnlp.runtime.Translator.R; - -import java.io.*; -import java.util.*; -import java.util.jar.*; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.security.CodeSigner; +import java.security.KeyStore; +import java.security.cert.CertPath; 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.security.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Vector; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import net.sourceforge.jnlp.JARDesc; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.LaunchException; +import net.sourceforge.jnlp.cache.ResourceTracker; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.security.AppVerifier; +import net.sourceforge.jnlp.security.CertVerifier; +import net.sourceforge.jnlp.security.CertificateUtils; +import net.sourceforge.jnlp.security.KeyStores; +import sun.security.util.DerInputStream; +import sun.security.util.DerValue; +import sun.security.x509.NetscapeCertTypeExtension; /** - * <p>The jar certificate verifier utility. - * + * <p> + * The jar certificate verifier utility. + * * @author Roland Schemers * @author Jan Luehe */ @@ -55,53 +71,39 @@ public class JarCertVerifier implements CertVerifier { // 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 + private static final long SIX_MONTHS = 180 * 24 * 60 * 60 * 1000L; // milliseconds - static enum verifyResult { + static enum VerifyResult { UNSIGNED, SIGNED_OK, SIGNED_NOT_OK } - // signer's certificate chain (when composing) - X509Certificate[] certChain; + /** All of the jar files that were verified for signing */ + private ArrayList<String> verifiedJars = new ArrayList<String>(); + + /** All of the jar files that were not verified */ + private ArrayList<String> unverifiedJars = new ArrayList<String>(); - boolean verbose = false; // verbose output when signing/verifying - boolean showcerts = false; // show certs when verifying + /** The certificates used for jar verification linked to their respective information */ + private HashMap<CertPath, CertInformation> certs = new HashMap<CertPath, CertInformation>(); - private boolean hasExpiredCert = false; - private boolean hasExpiringCert = false; - private boolean notYetValidCert = false; + /** Temporary cert path hack to be used to keep track of which one a UI dialog is using */ + private CertPath currentlyUsed; - private boolean badKeyUsage = false; - private boolean badExtendedKeyUsage = false; - private boolean badNetscapeCertType = false; + /** Absolute location to jars and the number of entries which are possibly signable */ + private HashMap<String, Integer> jarSignableEntries = new HashMap<String, Integer>(); - private boolean alreadyTrustPublisher = false; - private boolean rootInCacerts = false; + /** The application verifier to use by this instance */ + private AppVerifier appVerifier; /** - * 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. + * Create a new jar certificate verifier utility that uses the provided verifier for its strategy pattern. + * + * @param verifier + * The application verifier to be used by the new instance. */ - 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 HashMap<CertPath, Integer> certs = new HashMap<CertPath, Integer>(); - - /** details of this signing */ - private ArrayList<String> details = new ArrayList<String>(); - - private int totalSignableEntries = 0; + public JarCertVerifier(AppVerifier verifier) { + appVerifier = verifier; + } /** Whether a signable entry was found within jars (jars with content more than just META-INF/*) */ private boolean triviallySigned = false; @@ -113,88 +115,107 @@ public class JarCertVerifier implements CertVerifier { return triviallySigned; } - /* (non-Javadoc) - * @see net.sourceforge.jnlp.tools.CertVerifier2#getAlreadyTrustPublisher() - */ public boolean getAlreadyTrustPublisher() { - return alreadyTrustPublisher; + boolean allPublishersTrusted = appVerifier.hasAlreadyTrustedPublisher( + certs, jarSignableEntries); + if (JNLPRuntime.isDebug()) { + System.out.println("App already has trusted publisher: " + + allPublishersTrusted); + } + return allPublishersTrusted; } - /* (non-Javadoc) - * @see net.sourceforge.jnlp.tools.CertVerifier2#getRootInCacerts() - */ public boolean getRootInCacerts() { - return rootInCacerts; - } - - public CertPath getCertPath() { - return certPath; + boolean allRootCAsTrusted = appVerifier.hasRootInCacerts(certs, + jarSignableEntries); + if (JNLPRuntime.isDebug()) { + System.out.println("App has trusted root CA: " + allRootCAsTrusted); + } + return allRootCAsTrusted; } - /* (non-Javadoc) - * @see net.sourceforge.jnlp.tools.CertVerifier2#hasSigningIssues() - */ - public boolean hasSigningIssues() { - return hasExpiredCert || notYetValidCert || badKeyUsage - || badExtendedKeyUsage || badNetscapeCertType; + public CertPath getCertPath(CertPath cPath) { // Parameter ignored. + return currentlyUsed; } - /* (non-Javadoc) - * @see net.sourceforge.jnlp.tools.CertVerifier2#noSigningIssues() - */ - public boolean noSigningIssues() { - return noSigningIssues; + public boolean hasSigningIssues(CertPath certPath) { + return certs.get(certPath).hasSigningIssues(); } - public boolean anyJarsSigned() { - return anyJarsSigned; + public List<String> getDetails(CertPath certPath) { + if (certPath != null) { + currentlyUsed = certPath; + } + return certs.get(currentlyUsed).getDetailsAsStrings(); } - /* (non-Javadoc) - * @see net.sourceforge.jnlp.tools.CertVerifier2#getDetails() + /** + * Get a list of the cert paths of all signers across the app. + * + * @return ArrayList of CertPath vars representing each of the signers present on any jar. */ - public ArrayList<String> getDetails() { - return details; + public ArrayList<CertPath> getCertsList() { + return new ArrayList<CertPath>(certs.keySet()); } - /* (non-Javadoc) - * @see net.sourceforge.jnlp.tools.CertVerifier2#getCerts() + /** + * Find the information the specified cert path has with respect to this application. + * + * @return All the information the path has with this app. */ - public ArrayList<CertPath> getCerts() { - return new ArrayList<CertPath>(certs.keySet()); + public CertInformation getCertInformation(CertPath cPath) { + return certs.get(cPath); } /** - * Returns whether or not all entries have a common signer. - * - * It is possible to create jars where only some entries are signed. In - * such cases, we should not prompt the user to accept anything, as the whole - * application must be treated as unsigned. This method should be called by a - * caller before it is about to ask the user to accept a cert and determine - * whether the application is trusted or not. - * - * @return Whether or not all entries have a common signer + * Returns whether or not the app is considered completely signed. + * + * An app using a JNLP is considered signed if all of the entries of its jars are signed by at least one common signer. + * + * An applet on the other hand only needs to have each individual jar be fully signed by a signer. The signers can differ between jars. + * + * @return Whether or not the app is considered signed. */ - public boolean isFullySignedByASingleCert() { - + // FIXME: Change javadoc once applets do not need entire jars signed. + public boolean isFullySigned() { if (triviallySigned) return true; - - for (CertPath cPath : certs.keySet()) { - // If this cert has signed everything, return true - if (certs.get(cPath) == totalSignableEntries) - return true; + boolean fullySigned = appVerifier.isFullySigned(certs, + jarSignableEntries); + if (JNLPRuntime.isDebug()) { + System.out.println("App already has trusted publisher: " + + fullySigned); } - - // No cert found that signed all entries. Return false. - return false; + return fullySigned; } - public void verifyJars(List<JARDesc> jars, ResourceTracker tracker) + /** + * Update the verifier to consider new jars when verifying. + * + * @param jars + * List of new jars to be verified. + * @param tracker + * Resource tracker used to obtain the the jars from cache + * @throws Exception + * Caused by issues with obtaining the jars' entries or interacting with the tracker. + */ + public void add(List<JARDesc> jars, ResourceTracker tracker) throws Exception { + verifyJars(jars, tracker); + } - verifiedJars = new ArrayList<String>(); - unverifiedJars = new ArrayList<String>(); + /** + * Verify the jars provided and update the state of this instance to match the new information. + * + * @param jars + * List of new jars to be verified. + * @param tracker + * Resource tracker used to obtain the the jars from cache + * @throws Exception + * Caused by issues with obtaining the jars' entries or interacting with the tracker. + */ + private void verifyJars(List<JARDesc> jars, ResourceTracker tracker) + throws Exception { for (JARDesc jar : jars) { @@ -209,17 +230,22 @@ public class JarCertVerifier implements CertVerifier { } String localFile = jarFile.getAbsolutePath(); - verifyResult result = verifyJar(localFile); + if (verifiedJars.contains(localFile) + || unverifiedJars.contains(localFile)) { + continue; + } + + VerifyResult result = verifyJar(localFile); triviallySigned = false; - if (result == verifyResult.UNSIGNED) { + if (result == VerifyResult.UNSIGNED) { unverifiedJars.add(localFile); - } else if (result == verifyResult.SIGNED_NOT_OK) { - noSigningIssues = false; + } else if (result == VerifyResult.SIGNED_NOT_OK) { verifiedJars.add(localFile); - } else if (result == verifyResult.SIGNED_OK) { + } else if (result == VerifyResult.SIGNED_OK) { verifiedJars.add(localFile); - triviallySigned = totalSignableEntries <= 0 && certs.size() <= 0; + triviallySigned = getTotalJarEntries(jarSignableEntries) <= 0 + && certs.size() <= 0; } } catch (Exception e) { // We may catch exceptions from using verifyJar() @@ -228,26 +254,20 @@ public class JarCertVerifier implements CertVerifier { } } - //we really only want the first certPath - for (CertPath cPath : certs.keySet()) { - - if (certs.get(cPath) != totalSignableEntries) - continue; - else - certPath = cPath; - - // check if the certs added above are in the trusted path - checkTrustedCerts(); - - if (alreadyTrustPublisher || rootInCacerts) - break; - } - + for (CertPath certPath : certs.keySet()) + checkTrustedCerts(certPath); } - private verifyResult verifyJar(String jarName) throws Exception { - boolean anySigned = false; - boolean hasUnsignedEntry = false; + /** + * Checks through all the jar entries of jarName for signers, storing all the common ones in the certs hash map. + * + * @param jarName + * The absolute path to the jar file. + * @return The return of {@link JarCertVerifier#verifyJarEntryCerts} using the entries found in the jar located at jarName. + * @throws Exception + * Will be thrown if there are any problems with the jar. + */ + private VerifyResult verifyJar(String jarName) throws Exception { JarFile jarFile = null; try { @@ -262,10 +282,9 @@ public class JarCertVerifier implements CertVerifier { InputStream is = jarFile.getInputStream(je); try { - int n; - while ((n = is.read(buffer, 0, buffer.length)) != -1) { + while (is.read(buffer, 0, buffer.length) != -1) { // we just read. this will throw a SecurityException - // if a signature/digest check fails. + // if a signature/digest check fails. } } finally { if (is != null) { @@ -273,95 +292,9 @@ public class JarCertVerifier implements CertVerifier { } } } + return verifyJarEntryCerts(jarName, jarFile.getManifest() != null, + entriesVec); - if (jarFile.getManifest() != null) { - if (verbose) - System.out.println(); - - long now = System.currentTimeMillis(); - - for (JarEntry je : entriesVec) { - String name = je.getName(); - CodeSigner[] signers = je.getCodeSigners(); - boolean isSigned = (signers != null); - anySigned |= isSigned; - - boolean shouldHaveSignature = !je.isDirectory() - && !isMetaInfFile(name); - - hasUnsignedEntry |= shouldHaveSignature && !isSigned; - - if (shouldHaveSignature) - totalSignableEntries++; - - if (shouldHaveSignature && isSigned) { - for (int i = 0; i < signers.length; i++) { - CertPath certPath = signers[i].getSignerCertPath(); - if (!certs.containsKey(certPath)) - certs.put(certPath, 1); - else - certs.put(certPath, certs.get(certPath) + 1); - - Certificate cert = signers[i].getSignerCertPath() - .getCertificates().get(0); - if (cert instanceof X509Certificate) { - checkCertUsage((X509Certificate) cert, null); - if (!showcerts) { - long notBefore = ((X509Certificate) cert) - .getNotBefore().getTime(); - long notAfter = ((X509Certificate) cert) - .getNotAfter().getTime(); - - if (now < notBefore) { - notYetValidCert = true; - } - - if (notAfter < now) { - hasExpiredCert = true; - } else if (notAfter < now + SIX_MONTHS) { - hasExpiringCert = true; - } - } - } - } - } - } //while e has more elements - } else { //if man not null - - // Else increment totalEntries by 1 so that unsigned jars with - // no manifests can't sneak in - totalSignableEntries++; - } - - //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; @@ -370,51 +303,175 @@ public class JarCertVerifier implements CertVerifier { jarFile.close(); } } - - //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. + * Checks through all the jar entries for signers, storing all the common ones in the certs hash map. + * + * @param jarName + * The absolute path to the jar file. + * @param jarHasManifest + * Whether or not the associated jar has a manifest. + * @param entries + * The list of entries in the associated jar. + * @return If there is at least one signable entry that is not signed by a common signer, return UNSIGNED. Otherwise every signable entry is signed by at least one common signer. If the signer has no issues, return SIGNED_OK. If there are any signing issues, return SIGNED_NOT_OK. + * @throws Exception + * Will be thrown if there are issues with entries. */ - private void checkTrustedCerts() throws Exception { - if (certPath != null) { - try { - X509Certificate publisher = (X509Certificate) getPublisher(); - KeyStore[] certKeyStores = KeyStores.getCertKeyStores(); - alreadyTrustPublisher = CertificateUtils.inKeyStores(publisher, certKeyStores); - X509Certificate root = (X509Certificate) getRoot(); - KeyStore[] caKeyStores = KeyStores.getCAKeyStores(); - // Check entire cert path for a trusted CA - for (Certificate c : certPath.getCertificates()) { - if ((rootInCacerts = CertificateUtils.inKeyStores( - (X509Certificate) c, caKeyStores))) { - break; + VerifyResult verifyJarEntryCerts(String jarName, boolean jarHasManifest, + Vector<JarEntry> entries) throws Exception { + // Contains number of entries the cert with this CertPath has signed. + HashMap<CertPath, Integer> jarSignCount = new HashMap<CertPath, Integer>(); + int numSignableEntriesInJar = 0; + + // Record current time just before checking the jar begins. + long now = System.currentTimeMillis(); + if (jarHasManifest) { + + for (JarEntry je : entries) { + String name = je.getName(); + CodeSigner[] signers = je.getCodeSigners(); + boolean isSigned = (signers != null); + + boolean shouldHaveSignature = !je.isDirectory() + && !isMetaInfFile(name); + + if (shouldHaveSignature) { + numSignableEntriesInJar++; + } + + if (shouldHaveSignature && isSigned) { + for (int i = 0; i < signers.length; i++) { + CertPath certPath = signers[i].getSignerCertPath(); + + if (!jarSignCount.containsKey(certPath)) + jarSignCount.put(certPath, 1); + else + jarSignCount.put(certPath, + jarSignCount.get(certPath) + 1); } } - } catch (Exception e) { - // TODO: Warn user about not being able to - // look through their cacerts/trusted.certs - // file depending on exception. - throw e; + } // while e has more elements + } else { // if manifest is null + + // Else increment total entries by 1 so that unsigned jars with + // no manifests can't sneak in + numSignableEntriesInJar++; + } + + jarSignableEntries.put(jarName, numSignableEntriesInJar); + + // Find all signers that have signed every signable entry in this jar. + boolean allEntriesSignedBySingleCert = false; + for (CertPath certPath : jarSignCount.keySet()) { + if (jarSignCount.get(certPath) == numSignableEntriesInJar) { + allEntriesSignedBySingleCert = true; + + boolean wasPreviouslyVerified = certs.containsKey(certPath); + if (!wasPreviouslyVerified) + certs.put(certPath, new CertInformation()); + + CertInformation certInfo = certs.get(certPath); + if (wasPreviouslyVerified) + certInfo.resetForReverification(); + + certInfo.setNumJarEntriesSigned(jarName, + numSignableEntriesInJar); + + Certificate cert = certPath.getCertificates().get(0); + if (cert instanceof X509Certificate) { + checkCertUsage(certPath, (X509Certificate) cert, null); + long notBefore = ((X509Certificate) cert).getNotBefore().getTime(); + long notAfter = ((X509Certificate) cert).getNotAfter().getTime(); + if (now < notBefore) { + certInfo.setNotYetValidCert(); + } + + if (notAfter < now) { + certInfo.setHasExpiredCert(); + } else if (notAfter < now + SIX_MONTHS) { + certInfo.setHasExpiringCert(); + } + } + } + } + + // Every signable entry of this jar needs to be signed by at least + // one signer for the jar to be considered successfully signed. + VerifyResult result = null; + if (allEntriesSignedBySingleCert) { + + // We need to find at least one signer without any issues. + for (CertPath entryCertPath : jarSignCount.keySet()) { + if (certs.containsKey(entryCertPath) + && !hasSigningIssues(entryCertPath)) { + result = VerifyResult.SIGNED_OK; + break; + } } + if (result == null) { + // All signers had issues + result = VerifyResult.SIGNED_NOT_OK; + } + } else { + result = VerifyResult.UNSIGNED; + } - if (!rootInCacerts) - addToDetails(R("SUntrustedCertificate")); - else - addToDetails(R("STrustedCertificate")); + if (JNLPRuntime.isDebug()) { + System.out.println("Jar found at " + jarName + + "has been verified as " + result); } + return result; } - /* (non-Javadoc) - * @see net.sourceforge.jnlp.tools.CertVerifier2#getPublisher() + /** + * Checks the user's trusted.certs file and the cacerts file to see if a + * publisher's and/or CA's certificate exists there. + * + * @param certPath + * The cert path of the signer being checked for trust. */ - public Certificate getPublisher() { - if (certPath != null) { - List<? extends Certificate> certList = certPath.getCertificates(); + private void checkTrustedCerts(CertPath certPath) throws Exception { + CertInformation info = certs.get(certPath); + try { + X509Certificate publisher = (X509Certificate) getPublisher(certPath); + KeyStore[] certKeyStores = KeyStores.getCertKeyStores(); + if (CertificateUtils.inKeyStores(publisher, certKeyStores)) + info.setAlreadyTrustPublisher(); + KeyStore[] caKeyStores = KeyStores.getCAKeyStores(); + // Check entire cert path for a trusted CA + for (Certificate c : certPath.getCertificates()) { + if (CertificateUtils.inKeyStores((X509Certificate) c, + caKeyStores)) { + info.setRootInCacerts(); + return; + } + } + } catch (Exception e) { + // TODO: Warn user about not being able to + // look through their cacerts/trusted.certs + // file depending on exception. + if (JNLPRuntime.isDebug()) { + System.out.println("WARNING: Unable to read through cert store files."); + } + throw e; + } + + // Otherwise a parent cert was not found to be trusted. + info.setUntrusted(); + } + + public void setCurrentlyUsedCertPath(CertPath cPath) { + currentlyUsed = cPath; + } + + public Certificate getPublisher(CertPath cPath) { + if (cPath != null) { + currentlyUsed = cPath; + } + if (currentlyUsed != null) { + List<? extends Certificate> certList = currentlyUsed + .getCertificates(); if (certList.size() > 0) { return certList.get(0); } else { @@ -425,12 +482,13 @@ public class JarCertVerifier implements CertVerifier { } } - /* (non-Javadoc) - * @see net.sourceforge.jnlp.tools.CertVerifier2#getRoot() - */ - public Certificate getRoot() { - if (certPath != null) { - List<? extends Certificate> certList = certPath.getCertificates(); + public Certificate getRoot(CertPath cPath) { + if (cPath != null) { + currentlyUsed = cPath; + } + if (currentlyUsed != null) { + List<? extends Certificate> certList = currentlyUsed + .getCertificates(); if (certList.size() > 0) { return certList.get(certList.size() - 1); } else { @@ -441,20 +499,10 @@ public class JarCertVerifier implements CertVerifier { } } - private void addToDetails(String detail) { - if (!details.contains(detail)) - details.add(detail); - } - /** * Returns whether a file is in META-INF, and thus does not require signing. - * - * Signature-related files under META-INF include: - * . META-INF/MANIFEST.MF - * . META-INF/SIG-* - * . META-INF/*.SF - * . META-INF/*.DSA - * . META-INF/*.RSA + * + * Signature-related files under META-INF include: . META-INF/MANIFEST.MF . META-INF/SIG-* . META-INF/*.SF . META-INF/*.DSA . META-INF/*.RSA */ static boolean isMetaInfFile(String name) { String ucName = name.toUpperCase(); @@ -463,15 +511,19 @@ public class JarCertVerifier implements CertVerifier { /** * 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, + * + * @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() + * + * Required for verifyJar() */ - void checkCertUsage(X509Certificate userCert, boolean[] bad) { + void checkCertUsage(CertPath certPath, X509Certificate userCert, + boolean[] bad) { // Can act as a signer? // 1. if KeyUsage, then [0] should be true @@ -489,7 +541,7 @@ public class JarCertVerifier implements CertVerifier { if (bad != null) { bad[0] = true; } else { - badKeyUsage = true; + certs.get(certPath).setBadKeyUsage(); } } } @@ -502,7 +554,7 @@ public class JarCertVerifier implements CertVerifier { if (bad != null) { bad[1] = true; } else { - badExtendedKeyUsage = true; + certs.get(certPath).setBadExtendedKeyUsage(); } } } @@ -512,24 +564,24 @@ public class JarCertVerifier implements CertVerifier { try { // OID_NETSCAPE_CERT_TYPE - byte[] netscapeEx = userCert.getExtensionValue - ("2.16.840.1.113730.1.1"); + 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); + NetscapeCertTypeExtension extn = new NetscapeCertTypeExtension( + encoded); - Boolean val = (Boolean) extn.get( - NetscapeCertTypeExtension.OBJECT_SIGNING); + Boolean val = (Boolean) extn + .get(NetscapeCertTypeExtension.OBJECT_SIGNING); if (!val) { if (bad != null) { bad[2] = true; } else { - badNetscapeCertType = true; + certs.get(certPath).setBadNetscapeCertType(); } } } @@ -540,11 +592,31 @@ public class JarCertVerifier implements CertVerifier { /** * 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; } + public void checkTrustWithUser(JNLPFile file) throws LaunchException { + appVerifier.checkTrustWithUser(this, file); + } + + public Map<String, Integer> getJarSignableEntries() { + return Collections.unmodifiableMap(jarSignableEntries); + } + + /** + * Get the total number of entries in the provided map. + * + * @return The number of entries. + */ + public static int getTotalJarEntries(Map<String, Integer> map) { + int sum = 0; + for (int value : map.values()) { + sum += value; + } + return sum; + } } diff --git a/tests/netx/unit/net/sourceforge/jnlp/tools/JarCertVerifierTest.java b/tests/netx/unit/net/sourceforge/jnlp/tools/JarCertVerifierTest.java index 300b85b..88054ab 100644 --- a/tests/netx/unit/net/sourceforge/jnlp/tools/JarCertVerifierTest.java +++ b/tests/netx/unit/net/sourceforge/jnlp/tools/JarCertVerifierTest.java @@ -37,18 +37,484 @@ exception statement from your version. package net.sourceforge.jnlp.tools; -import static org.junit.Assert.*; +import static net.sourceforge.jnlp.runtime.Translator.R; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.security.CodeSigner; +import java.util.Date; +import java.util.List; +import java.util.Vector; +import java.util.jar.JarEntry; + +import net.sourceforge.jnlp.JARDesc; +import net.sourceforge.jnlp.tools.JarCertVerifier.VerifyResult; + +import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; public class JarCertVerifierTest { @Test public void testIsMetaInfFile() { - final String METAINF ="META-INF"; + final String METAINF = "META-INF"; assertFalse(JarCertVerifier.isMetaInfFile("some_dir/" + METAINF + "/filename")); assertFalse(JarCertVerifier.isMetaInfFile(METAINF + "filename")); assertTrue(JarCertVerifier.isMetaInfFile(METAINF + "/filename")); } + class JarCertVerifierEntry extends JarEntry { + CodeSigner[] signers; + + public JarCertVerifierEntry(String name, CodeSigner[] codesigners) { + super(name); + signers = codesigners; + } + + public JarCertVerifierEntry(String name) { + this(name, null); + } + + public CodeSigner[] getCodeSigners() { + return signers == null ? null : signers.clone(); + } + } + + // Empty list to be used with JarCertVerifier constructor. + private static final List<JARDesc> emptyJARDescList = new Vector<JARDesc>(); + + private static final String DNPARTIAL = ", OU=JarCertVerifier Unit Test, O=IcedTea, L=Toronto, ST=Ontario, C=CA"; + private static CodeSigner alphaSigner, betaSigner, charlieSigner, + expiredSigner, expiringSigner, notYetValidSigner, expiringAndNotYetValidSigner; + + @BeforeClass + public static void setUp() throws Exception { + Date currentDate = new Date(); + Date pastDate = new Date(currentDate.getTime() - (1000L * 24L * 60L * 60L) - 1000L); // 1 day and 1 second in the past + Date futureDate = new Date(currentDate.getTime() + (1000L * 24L * 60L * 60L)); // 1 day in the future + alphaSigner = CodeSignerCreator.getOneCodeSigner("CN=Alpha Signer" + DNPARTIAL, currentDate, 365); + betaSigner = CodeSignerCreator.getOneCodeSigner("CN=Beta Signer" + DNPARTIAL, currentDate, 365); + charlieSigner = CodeSignerCreator.getOneCodeSigner("CN=Charlie Signer" + DNPARTIAL, currentDate, 365); + expiredSigner = CodeSignerCreator.getOneCodeSigner("CN=Expired Signer" + DNPARTIAL, pastDate, 1); + expiringSigner = CodeSignerCreator.getOneCodeSigner("CN=Expiring Signer" + DNPARTIAL, currentDate, 1); + notYetValidSigner = CodeSignerCreator.getOneCodeSigner("CN=Not Yet Valid Signer" + DNPARTIAL, futureDate, 365); + expiringAndNotYetValidSigner = CodeSignerCreator.getOneCodeSigner("CN=Expiring and Not Yet Valid Signer" + DNPARTIAL, futureDate, 3); + } + + @Test + public void testNoManifest() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + VerifyResult result = jcv.verifyJarEntryCerts("", false, null); + + Assert.assertEquals("No manifest should be considered unsigned.", + VerifyResult.UNSIGNED, result); + Assert.assertEquals("No manifest means no signers in the verifier.", + 0, jcv.getCertsList().size()); + } + + @Test + public void testNoSignableEntries() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("OneDirEntry/")); + entries.add(new JarCertVerifierEntry("META-INF/MANIFEST.MF")); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("No signable entry (only dirs/manifests) should be considered unsigned.", + VerifyResult.UNSIGNED, result); + Assert.assertEquals("No signable entry (only dirs/manifests) means no signers in the verifier.", + 0, jcv.getCertsList().size()); + } + + @Test + public void testSingleEntryNoSigners() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstEntryWithoutSigner")); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("One unsigned entry should be considered unsigned.", + VerifyResult.UNSIGNED, result); + Assert.assertEquals("One unsigned entry means no signers in the verifier.", + 0, jcv.getCertsList().size()); + } + + @Test + public void testManyEntriesNoSigners() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstEntryWithoutSigner")); + entries.add(new JarCertVerifierEntry("secondEntryWithoutSigner")); + entries.add(new JarCertVerifierEntry("thirdEntryWithoutSigner")); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("Many unsigned entries should be considered unsigned.", + VerifyResult.UNSIGNED, result); + Assert.assertEquals("Many unsigned entries means no signers in the verifier.", 0, + jcv.getCertsList().size()); + } + + @Test + public void testSingleEntrySingleValidSigner() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] signers = { alphaSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByOne", signers)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("One signed entry should be considered signed and okay.", + VerifyResult.SIGNED_OK, result); + Assert.assertEquals("One signed entry means one signer in the verifier.", + 1, jcv.getCertsList().size()); + Assert.assertTrue("One signed entry means one signer in the verifier.", + jcv.getCertsList().contains(alphaSigner.getSignerCertPath())); + } + + @Test + public void testManyEntriesSingleValidSigner() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] signers = { alphaSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByOne", signers)); + entries.add(new JarCertVerifierEntry("secondSignedByOne", signers)); + entries.add(new JarCertVerifierEntry("thirdSignedByOne", signers)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("Three entries signed by one signer should be considered signed and okay.", + VerifyResult.SIGNED_OK, result); + Assert.assertEquals("Three entries signed by one signer means one signer in the verifier.", + 1, jcv.getCertsList().size()); + Assert.assertTrue("Three entries signed by one signer means one signer in the verifier.", + jcv.getCertsList().contains(alphaSigner.getSignerCertPath())); + } + + @Test + public void testSingleEntryMultipleValidSigners() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] signers = { alphaSigner, betaSigner, charlieSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByThree", signers)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("One entry signed by three signers should be considered signed and okay.", + VerifyResult.SIGNED_OK, result); + Assert.assertEquals("One entry signed by three means three signers in the verifier.", + 3, jcv.getCertsList().size()); + Assert.assertTrue("One entry signed by three means three signers in the verifier.", + jcv.getCertsList().contains(alphaSigner.getSignerCertPath()) + && jcv.getCertsList().contains(betaSigner.getSignerCertPath()) + && jcv.getCertsList().contains(charlieSigner.getSignerCertPath())); + } + + @Test + public void testManyEntriesMultipleValidSigners() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] signers = { alphaSigner, betaSigner, charlieSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByThree", signers)); + entries.add(new JarCertVerifierEntry("secondSignedByThree", signers)); + entries.add(new JarCertVerifierEntry("thirdSignedByThree", signers)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("Three entries signed by three signers should be considered signed and okay.", + VerifyResult.SIGNED_OK, result); + Assert.assertEquals("Three entries signed by three means three signers in the verifier.", + 3, jcv.getCertsList().size()); + Assert.assertTrue("Three entries signed by three means three signers in the verifier.", + jcv.getCertsList().contains(alphaSigner.getSignerCertPath()) + && jcv.getCertsList().contains(betaSigner.getSignerCertPath()) + && jcv.getCertsList().contains(charlieSigner.getSignerCertPath())); + } + + @Test + public void testOneCommonSigner() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] alphaSigners = { alphaSigner }; + CodeSigner[] betaSigners = { alphaSigner, betaSigner }; + CodeSigner[] charlieSigners = { alphaSigner, charlieSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByOne", alphaSigners)); + entries.add(new JarCertVerifierEntry("secondSignedByTwo", betaSigners)); + entries.add(new JarCertVerifierEntry("thirdSignedByTwo", charlieSigners)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("Three entries signed by at least one common signer should be considered signed and okay.", + VerifyResult.SIGNED_OK, result); + Assert.assertEquals("Three entries signed completely by only one signer means one signer in the verifier.", + 1, jcv.getCertsList().size()); + Assert.assertTrue("Three entries signed completely by only one signer means one signer in the verifier.", + jcv.getCertsList().contains(alphaSigner.getSignerCertPath())); + } + + @Test + public void testNoCommonSigner() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] alphaSigners = { alphaSigner }; + CodeSigner[] betaSigners = { betaSigner }; + CodeSigner[] charlieSigners = { charlieSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByAlpha", alphaSigners)); + entries.add(new JarCertVerifierEntry("secondSignedByBeta", betaSigners)); + entries.add(new JarCertVerifierEntry("thirdSignedByCharlie", charlieSigners)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("Three entries signed by no common signers should be considered unsigned.", + VerifyResult.UNSIGNED, result); + Assert.assertEquals("Three entries signed by no common signers means no signers in the verifier.", + 0, jcv.getCertsList().size()); + } + + @Test + public void testFewButNotAllCommonSigners() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] alphaSigners = { alphaSigner }; + CodeSigner[] betaSigners = { betaSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByAlpha", alphaSigners)); + entries.add(new JarCertVerifierEntry("secondSignedByAlpha", alphaSigners)); + entries.add(new JarCertVerifierEntry("thirdSignedByBeta", betaSigners)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("First two entries signed by alpha signer, third entry signed by beta signer should be considered unisgned.", + VerifyResult.UNSIGNED, result); + Assert.assertEquals("Three entries signed by some common signers but not all means no signers in the verifier.", + 0, jcv.getCertsList().size()); + } + + @Test + public void testNotAllEntriesSigned() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] alphaSigners = { alphaSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByAlpha", alphaSigners)); + entries.add(new JarCertVerifierEntry("secondSignedByAlpha", alphaSigners)); + entries.add(new JarCertVerifierEntry("thirdUnsigned")); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("First two entries signed by alpha signer, third entry not signed, should be considered unisgned.", + VerifyResult.UNSIGNED, result); + Assert.assertEquals("First two entries signed by alpha signer, third entry not signed, means no signers in the verifier.", + 0, jcv.getCertsList().size()); + } + + @Test + public void testSingleEntryExpiredSigner() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] expiredSigners = { expiredSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByExpired", expiredSigners)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("One entry signed by expired cert, should be considered signed but not okay.", + VerifyResult.SIGNED_NOT_OK, result); + Assert.assertEquals("One entry signed by expired cert means one signer in the verifier.", + 1, jcv.getCertsList().size()); + Assert.assertTrue("One entry signed by expired cert means one signer in the verifier.", + jcv.getCertsList().contains(expiredSigner.getSignerCertPath())); + } + + @Test + public void testManyEntriesExpiredSigner() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] expiredSigners = { expiredSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByExpired", expiredSigners)); + entries.add(new JarCertVerifierEntry("secondSignedBExpired", expiredSigners)); + entries.add(new JarCertVerifierEntry("thirdSignedByExpired", expiredSigners)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("Three entries signed by expired cert, should be considered signed but not okay.", + VerifyResult.SIGNED_NOT_OK, result); + Assert.assertEquals("Three entries signed by expired cert means one signer in the verifier.", + 1, jcv.getCertsList().size()); + Assert.assertTrue("Three entries signed by expired cert means one signer in the verifier.", + jcv.getCertsList().contains(expiredSigner.getSignerCertPath())); + } + + @Test + public void testSingleEntryExpiringSigner() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] expiringSigners = { expiringSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByExpiring", expiringSigners)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("One entry signed by expiring cert, should be considered signed and okay.", + VerifyResult.SIGNED_OK, result); + Assert.assertEquals("One entry signed by expiring cert means one signer in the verifier.", + 1, jcv.getCertsList().size()); + Assert.assertTrue("One entry signed by expiring cert means one signer in the verifier.", + jcv.getCertsList().contains(expiringSigner.getSignerCertPath())); + } + + @Test + public void testManyEntriesExpiringSigner() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] expiringSigners = { expiringSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByExpiring", expiringSigners)); + entries.add(new JarCertVerifierEntry("secondSignedBExpiring", expiringSigners)); + entries.add(new JarCertVerifierEntry("thirdSignedByExpiring", expiringSigners)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("Three entries signed by expiring cert, should be considered signed and okay.", + VerifyResult.SIGNED_OK, result); + Assert.assertEquals("Three entries signed by expiring cert means one signer in the verifier.", + 1, jcv.getCertsList().size()); + Assert.assertTrue("Three entries signed by expiring cert means one signer in the verifier.", + jcv.getCertsList().contains(expiringSigner.getSignerCertPath())); + } + + @Test + public void testSingleEntryNotYetValidSigner() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] notYetValidSigners = { notYetValidSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByNotYetValid", notYetValidSigners)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("One entry signed by cert that is not yet valid, should be considered signed but not okay.", + VerifyResult.SIGNED_NOT_OK, result); + Assert.assertEquals("One entry signed by cert that is not yet valid means one signer in the verifier.", + 1, jcv.getCertsList().size()); + Assert.assertTrue("One entry signed by cert that is not yet valid means one signer in the verifier.", + jcv.getCertsList().contains(notYetValidSigner.getSignerCertPath())); + } + + @Test + public void testManyEntriesNotYetValidSigner() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] notYetValidSigners = { notYetValidSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByNotYetValid", notYetValidSigners)); + entries.add(new JarCertVerifierEntry("secondSignedByNotYetValid", notYetValidSigners)); + entries.add(new JarCertVerifierEntry("thirdSignedByNotYetValid", notYetValidSigners)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("Three entries signed by cert that is not yet valid, should be considered signed but not okay.", + VerifyResult.SIGNED_NOT_OK, result); + Assert.assertEquals("Three entries signed by cert that is not yet valid means one signer in the verifier.", + 1, jcv.getCertsList().size()); + Assert.assertTrue("Three entries signed by cert that is not yet valid means one signer in the verifier.", + jcv.getCertsList().contains(notYetValidSigner.getSignerCertPath())); + } + + @Test + public void testSingleEntryExpiringAndNotYetValidSigner() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] expiringAndNotYetValidSigners = { expiringAndNotYetValidSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByExpiringNotYetValid", expiringAndNotYetValidSigners)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("One entry signed by cert that is not yet valid but also expiring, should be considered signed but not okay.", + VerifyResult.SIGNED_NOT_OK, result); + Assert.assertEquals("One entry signed by cert that is not yet valid but also expiring means one signer in the verifier.", + 1, jcv.getCertsList().size()); + Assert.assertTrue("One entry signed by cert that is not yet valid but also expiring means one signer in the verifier.", + jcv.getCertsList().contains(expiringAndNotYetValidSigner.getSignerCertPath())); + } + + @Test + public void testManyEntryExpiringAndNotYetValidSigner() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + + CodeSigner[] expiringAndNotYetValidSigners = { expiringAndNotYetValidSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByExpiringNotYetValid", expiringAndNotYetValidSigners)); + entries.add(new JarCertVerifierEntry("secondSignedByExpiringNotYetValid", expiringAndNotYetValidSigners)); + entries.add(new JarCertVerifierEntry("thirdSignedByExpiringNotYetValid", expiringAndNotYetValidSigners)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("Three entries signed by cert that is not yet valid but also expiring, should be considered signed but not okay.", + VerifyResult.SIGNED_NOT_OK, result); + Assert.assertEquals("Three entries signed by cert that is not yet valid but also expiring means one signer in the verifier.", + 1, jcv.getCertsList().size()); + Assert.assertTrue("Three entries signed by cert that is not yet valid but also expiring means one signer in the verifier.", + jcv.getCertsList().contains(expiringAndNotYetValidSigner.getSignerCertPath())); + Assert.assertTrue("Three entries signed by cert that is not yet valid but also expiring means expiring issue should be in details list.", + jcv.getDetails(expiringAndNotYetValidSigner.getSignerCertPath()).contains(R("SHasExpiringCert"))); + } + + @Test + public void testSingleEntryOneExpiredOneValidSigner() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] oneExpiredOneValidSigner = { expiredSigner, alphaSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByTwo", oneExpiredOneValidSigner)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("One entry signed by one expired cert and another valid cert, should be considered signed and okay.", + VerifyResult.SIGNED_OK, result); + Assert.assertEquals("One entry signed by one expired cert and another valid cert means two signers in the verifier.", + 2, jcv.getCertsList().size()); + Assert.assertTrue("One entry signed by one expired cert and another valid cert means two signers in the verifier.", + jcv.getCertsList().contains(expiredSigner.getSignerCertPath()) + && jcv.getCertsList().contains(alphaSigner.getSignerCertPath())); + } + + @Test + public void testManyEntriesOneExpiredOneValidSigner() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] oneExpiredOneValidSigner = { expiredSigner, alphaSigner }; + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByTwo", oneExpiredOneValidSigner)); + entries.add(new JarCertVerifierEntry("secondSignedByTwo", oneExpiredOneValidSigner)); + entries.add(new JarCertVerifierEntry("thirdSignedByTwo", oneExpiredOneValidSigner)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("Three entries signed by one expired cert and another valid cert, should be considered signed and okay.", + VerifyResult.SIGNED_OK, result); + Assert.assertEquals("Three entries signed by one expired cert and another valid cert means two signers in the verifier.", + 2, jcv.getCertsList().size()); + Assert.assertTrue("Three entries signed by one expired cert and another valid cert means two signers in the verifier.", + jcv.getCertsList().contains(expiredSigner.getSignerCertPath()) + && jcv.getCertsList().contains(alphaSigner.getSignerCertPath())); + } + + @Test + public void testSomeExpiredEntries() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] oneExpiredOneValidSigners = { expiredSigner, alphaSigner }; + CodeSigner[] expiredSigners = { expiredSigner }; + + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("firstSignedByTwo", oneExpiredOneValidSigners)); + entries.add(new JarCertVerifierEntry("secondSignedByTwo", oneExpiredOneValidSigners)); + entries.add(new JarCertVerifierEntry("thirdSignedByExpired", expiredSigners)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("Two entries signed by one expired and one valid cert, third signed by just expired cert, should be considered signed but not okay.", + VerifyResult.SIGNED_NOT_OK, result); + Assert.assertEquals("Two entries signed by one expired and one valid cert, third signed by just expired cert means one signer in the verifier.", + 1, jcv.getCertsList().size()); + Assert.assertTrue("Two entries signed by one expired and one valid cert, third signed by just expired cert means one signer in the verifier.", + jcv.getCertsList().contains(expiredSigner.getSignerCertPath())); + } + + @Test + public void testManyInvalidOneValidStillSignedOkay() throws Exception { + JarCertVerifier jcv = new JarCertVerifier(null); + CodeSigner[] oneExpiredOneValidSigners = { alphaSigner, expiredSigner }; + CodeSigner[] oneNotYetValidOneValidSigners = { alphaSigner, notYetValidSigner }; + CodeSigner[] oneExpiringSigners = { alphaSigner, expiringSigner }; + + Vector<JarEntry> entries = new Vector<JarEntry>(); + entries.add(new JarCertVerifierEntry("META-INF/MANIFEST.MF")); + entries.add(new JarCertVerifierEntry("firstSigned", oneExpiredOneValidSigners)); + entries.add(new JarCertVerifierEntry("secondSigned", oneNotYetValidOneValidSigners)); + entries.add(new JarCertVerifierEntry("thirdSigned", oneExpiringSigners)); + entries.add(new JarCertVerifierEntry("oneDir/")); + entries.add(new JarCertVerifierEntry("oneDir/fourthSigned", oneExpiredOneValidSigners)); + VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + + Assert.assertEquals("Three entries sharing valid cert and others with issues, should be considered signed and okay.", + VerifyResult.SIGNED_OK, result); + Assert.assertEquals("Three entries sharing valid cert and others with issues means one signer in the verifier.", + 1, jcv.getCertsList().size()); + Assert.assertTrue("Three entries sharing valid cert and others with issues means one signer in the verifier.", + jcv.getCertsList().contains(alphaSigner.getSignerCertPath())); + } + } diff --git a/tests/test-extensions/net/sourceforge/jnlp/tools/CodeSignerCreator.java b/tests/test-extensions/net/sourceforge/jnlp/tools/CodeSignerCreator.java new file mode 100644 index 0000000..dcf1d39 --- /dev/null +++ b/tests/test-extensions/net/sourceforge/jnlp/tools/CodeSignerCreator.java @@ -0,0 +1,145 @@ +/* + * 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 net.sourceforge.jnlp.tools; + +import java.security.CodeSigner; +import java.security.PrivateKey; +import java.security.Timestamp; +import java.security.cert.CertPath; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Date; + +import sun.security.x509.AlgorithmId; +import sun.security.x509.CertAndKeyGen; +import sun.security.x509.CertificateAlgorithmId; +import sun.security.x509.CertificateIssuerName; +import sun.security.x509.CertificateSerialNumber; +import sun.security.x509.CertificateSubjectName; +import sun.security.x509.CertificateValidity; +import sun.security.x509.CertificateVersion; +import sun.security.x509.X500Name; +import sun.security.x509.X509CertImpl; +import sun.security.x509.X509CertInfo; + +public class CodeSignerCreator { + + /** + * Create an X509 Certificate signed using SHA1withRSA with a 2048 bit key. + * @param dname Domain Name to represent the certificate + * @param notBefore The date by which the certificate starts being valid. Cannot be null. + * @param validity The number of days the certificate is valid after notBefore. + * @return An X509 certificate setup with properties using the specified parameters. + * @throws Exception + */ + public static X509Certificate createCert(String dname, Date notBefore, int validity) + throws Exception { + int keysize = 2048; + String keyAlgName = "RSA"; + String sigAlgName = "SHA1withRSA"; + + if (dname == null) + throw new Exception("Required DN is null. Please specify cert Domain Name via dname"); + if (notBefore == null) + throw new Exception("Required start date is null. Please specify the date at which the cert is valid via notBefore"); + if (validity < 0) + throw new Exception("Required validity is negative. Please specify the number of days for which the cert is valid after the start date."); + + // KeyTool#doGenKeyPair + X500Name x500Name = new X500Name(dname); + + CertAndKeyGen keypair = new CertAndKeyGen(keyAlgName, sigAlgName); + + keypair.generate(keysize); + PrivateKey privKey = keypair.getPrivateKey(); + + X509Certificate oldCert = keypair.getSelfCertificate(x500Name, + notBefore, validity * 24L * 60L * 60L); + + // KeyTool#doSelfCert + byte[] encoded = oldCert.getEncoded(); + X509CertImpl certImpl = new X509CertImpl(encoded); + X509CertInfo certInfo = (X509CertInfo) certImpl.get(X509CertImpl.NAME + + "." + X509CertImpl.INFO); + + Date notAfter = new Date(notBefore.getTime() + validity*1000L*24L*60L*60L); + + CertificateValidity interval = new CertificateValidity(notBefore, + notAfter); + + certInfo.set(X509CertInfo.VALIDITY, interval); + certInfo.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber( + new java.util.Random().nextInt() & 0x7fffffff)); + certInfo.set(X509CertInfo.SUBJECT + "." + CertificateSubjectName.DN_NAME, x500Name); + certInfo.set(X509CertInfo.ISSUER + "." + CertificateIssuerName.DN_NAME, x500Name); + + // The inner and outer signature algorithms have to match. + // The way we achieve that is really ugly, but there seems to be no + // other solution: We first sign the cert, then retrieve the + // outer sigalg and use it to set the inner sigalg + X509CertImpl newCert = new X509CertImpl(certInfo); + newCert.sign(privKey, sigAlgName); + AlgorithmId sigAlgid = (AlgorithmId)newCert.get(X509CertImpl.SIG_ALG); + certInfo.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, sigAlgid); + + certInfo.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)); + + // FIXME Figure out extensions +// CertificateExtensions ext = createV3Extensions( +// null, +// (CertificateExtensions)certInfo.get(X509CertInfo.EXTENSIONS), +// v3ext, +// oldCert.getPublicKey(), +// null); +// certInfo.set(X509CertInfo.EXTENSIONS, ext); + + newCert = new X509CertImpl(certInfo); + newCert.sign(privKey, sigAlgName); + + return newCert; + } + + /** + * Create a new code signer with the specified information. + * @param domainName Domain Name to represent the certificate + * @param notBefore The date by which the certificate starts being valid. Cannot be null. + * @param validity The number of days the certificate is valid after notBefore. + * @return A code signer with the properties passed through its parameters. + */ + public static CodeSigner getOneCodeSigner(String domainName, Date notBefore, int validity) + throws Exception { + X509Certificate jarEntryCert = createCert(domainName, notBefore, validity); + + ArrayList<X509Certificate> certs = new ArrayList<X509Certificate>(1); + certs.add(jarEntryCert); + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + CertPath certPath = cf.generateCertPath(certs); + Timestamp certTimestamp = new Timestamp(jarEntryCert.getNotBefore(), certPath); + return new CodeSigner(certPath, certTimestamp); + } +} |