diff options
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); + } +} |