changeset 1668:66cdb266311e

Import from trunk. Detect CN mismatches in a site's https certificate and allow the user to bypass it if they choose to do so.
author Deepak Bhole <dbhole@redhat.com>
date Tue, 25 Aug 2009 10:55:01 -0400
parents da9eb62e065f
children 20cd55dd027f
files ChangeLog rt/net/sourceforge/jnlp/resources/Messages.properties rt/net/sourceforge/jnlp/security/HttpsCertVerifier.java rt/net/sourceforge/jnlp/security/VariableX509TrustManager.java
diffstat 4 files changed, 177 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Aug 25 10:53:05 2009 -0400
+++ b/ChangeLog	Tue Aug 25 10:55:01 2009 -0400
@@ -1,3 +1,26 @@
+2009-08-25  Deepak Bhole <dbhole@redhat.com>
+
+	* rt/net/sourceforge/jnlp/resources/Messages.properties: Add new
+	message key for CN name mismatches.
+	* rt/net/sourceforge/jnlp/security/HttpsCertVerifier.java
+	(HttpsCertVerifier): Accept new parameters that indicate certificate trust
+	status, CN mismatch status, and the hostname.
+	(getAlreadyTrustPublisher): Use provided isTrusted boolean to get around
+	checkServerTrusted() synchronization.
+	(getDetails): Include details about CN mismatch.
+	(getNamesForCert): New private method. Returns all acceptable names for
+	a given X509Certificate.
+	(R): Overloaded the method to return messages that have 2 variables.
+	* rt/net/sourceforge/jnlp/security/VariableX509TrustManager.java: Extend
+	X509ExtendedTrustManager rather than X509TrustManager.
+	(checkClientTrusted): Overloaded method with one that takes a hostname.
+	(checkServerTrusted): Same. The new overloaded method also checks for CN
+	mismatch if the certificate is not explicitly trusted.
+	(isExplicitlyTrusted): Returns if the given certificate chain is part of
+	the local user trusted DB.
+	(askUser): Change parameters to accept information about trust, host match
+	status, and hostname.
+
 2009-08-25  Deepak Bhole  <dbhole@redhat.com>
 
 	* rt/net/sourceforge/jnlp/JNLPFile.java: Add a new key variable that is
@@ -536,7 +559,7 @@
 	* plugin/icedteanp/IcedTeaNPPlugin.h: New file. Header for
 	IcedTeaNPPlugin.cc.
 	* plugin/icedteanp/IcedTeaPluginRequestProcessor.cc: New file. Processes 
-    plugin data requests from Java side.
+	plugin data requests from Java side.
 	* plugin/icedteanp/IcedTeaPluginRequestProcessor.h: new file. Header for
 	IcedTeaPluginRequestProcessor.cc.
 	* plugin/icedteanp/IcedTeaPluginUtils.cc: New file. Utility functions for
--- a/rt/net/sourceforge/jnlp/resources/Messages.properties	Tue Aug 25 10:53:05 2009 -0400
+++ b/rt/net/sourceforge/jnlp/resources/Messages.properties	Tue Aug 25 10:55:01 2009 -0400
@@ -168,6 +168,7 @@
 SNotYetValidCert=Resources contain entries whose signer certificate is not yet valid.
 SUntrustedCertificate=The digital signature was generated with an untrusted certificate.
 STrustedCertificate=The digital signature was generated with a trusted certificate.
+SCNMisMatch=The expected hostname for this certificate is: "{0}"<BR>The address being connected to is: "{1}"
 SRunWithoutRestrictions=This application will be run without the security restrictions normally provided by java.
 
 
--- a/rt/net/sourceforge/jnlp/security/HttpsCertVerifier.java	Tue Aug 25 10:53:05 2009 -0400
+++ b/rt/net/sourceforge/jnlp/security/HttpsCertVerifier.java	Tue Aug 25 10:55:01 2009 -0400
@@ -37,38 +37,49 @@
 
 package net.sourceforge.jnlp.security;
 
+import java.io.IOException;
 import java.security.cert.CertPath;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.CertificateNotYetValidException;
-import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 
 import net.sourceforge.jnlp.runtime.JNLPRuntime;
 import net.sourceforge.jnlp.tools.KeyTool;
+import sun.security.util.DerValue;
+import sun.security.util.HostnameChecker;
+import sun.security.x509.X500Name;
  
 public class HttpsCertVerifier implements CertVerifier {
 
     private VariableX509TrustManager tm;
     private X509Certificate[] chain;
     private String authType;
+    private String hostName;
+    private boolean isTrusted;
+    private boolean hostMatched;
     private ArrayList<String> details = new ArrayList<String>();
     
-    public HttpsCertVerifier(VariableX509TrustManager tm, X509Certificate[] chain, String authType) {
+    public HttpsCertVerifier(VariableX509TrustManager tm, 
+                             X509Certificate[] chain, String authType, 
+                             boolean isTrusted, boolean hostMatched,
+                             String hostName) {
         this.tm = tm;
         this.chain = chain;
         this.authType = authType;
+        this.hostName = hostName;
+        this.isTrusted = isTrusted;
+        this.hostMatched = hostMatched;
     }
 
     public boolean getAlreadyTrustPublisher() {
-        try {
-            tm.checkServerTrusted(chain, authType, true);
-            return true;
-        } catch (CertificateException ce) {
-            return false;
-        }
+        return isTrusted;
     }
 
     public ArrayList<CertPath> getCerts() {
@@ -91,10 +102,12 @@
     }
 
     public ArrayList<String> getDetails() {
+
 	boolean hasExpiredCert=false; 
 	boolean hasExpiringCert=false;
 	boolean notYetValidCert=false;
 	boolean isUntrusted=false; 
+	boolean CNMisMatch = !hostMatched;
 
 	if (! getAlreadyTrustPublisher())
               isUntrusted = true;
@@ -121,7 +134,9 @@
 	   }
 	}
 
-	if (isUntrusted || hasExpiredCert || hasExpiringCert || notYetValidCert) {
+	String altNames = getNamesForCert(chain[0]);
+
+	if (isUntrusted || hasExpiredCert || hasExpiringCert || notYetValidCert || CNMisMatch) {
 	      if (isUntrusted)
 	        addToDetails(R("SUntrustedCertificate"));
               if (hasExpiredCert)
@@ -130,10 +145,54 @@
                 addToDetails(R("SHasExpiringCert"));
               if (notYetValidCert)
                 addToDetails(R("SNotYetValidCert"));
+              if (CNMisMatch)
+                  addToDetails(R("SCNMisMatch", altNames, this.hostName));  
         }
+	
+	
 	return details;
     }
 
+    private String getNamesForCert(X509Certificate c) {
+        
+        String names = "";
+        
+        
+        // We use the specification from 
+        // http://java.sun.com/j2se/1.5.0/docs/api/java/security/cert/X509Certificate.html#getSubjectAlternativeNames()
+        // to determine the type of address
+        int ALTNAME_DNS = 2;
+        int ALTNAME_IP = 7;
+
+        try {
+            Collection<List<?>> subjAltNames = c.getSubjectAlternativeNames();
+            X500Name subjectName = HostnameChecker.getSubjectX500Name(c);
+            DerValue derValue = subjectName.findMostSpecificAttribute
+                                                        (X500Name.commonName_oid);
+            names += derValue.getAsString();
+
+            if (subjAltNames != null) {
+                for (List<?> next : subjAltNames) {
+                    if ( ((Integer)next.get(0)).intValue() == ALTNAME_IP || 
+                            ((Integer)next.get(0)).intValue() == ALTNAME_DNS
+                    ) {
+                        names += ", " + (String)next.get(1);
+                    }
+                }
+            }
+            
+            if (subjAltNames != null)
+                names = names.substring(2); // remove proceeding ", "
+
+        } catch (CertificateParsingException cpe) {
+            cpe.printStackTrace();
+        } catch (IOException ioe) {
+            ioe.printStackTrace();
+        }
+
+        return names;
+    }
+    
     private void addToDetails(String detail) {
       if (!details.contains(detail))
         details.add(detail);
@@ -142,6 +201,10 @@
     private static String R(String key) {
       return JNLPRuntime.getMessage(key);
     }
+    
+    private static String R(String key, String arg1, String arg2) {
+        return JNLPRuntime.getMessage(key, new Object[] { arg1, arg2 });
+    }
 
     public Certificate getPublisher() {
       if (chain.length > 0)
--- a/rt/net/sourceforge/jnlp/security/VariableX509TrustManager.java	Tue Aug 25 10:53:05 2009 -0400
+++ b/rt/net/sourceforge/jnlp/security/VariableX509TrustManager.java	Tue Aug 25 10:55:01 2009 -0400
@@ -47,15 +47,18 @@
 import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.X509TrustManager;
 
+import sun.security.util.HostnameChecker;
 import sun.security.validator.ValidatorException;
 
+import com.sun.net.ssl.internal.ssl.X509ExtendedTrustManager;
+
 /**
  * This class implements an X509 Trust Manager. The certificates it trusts are 
  * "variable", in the sense that it can dynamically, and temporarily support 
  * different certificates that are not in the keystore.  
  */
 
-public class VariableX509TrustManager implements X509TrustManager {
+public class VariableX509TrustManager extends X509ExtendedTrustManager {
 
     KeyStore userKeyStore = null;
     KeyStore caKeyStore = null;
@@ -112,11 +115,11 @@
     }
 
     /**
-     * Check if client is trusted (not support for custom here, only system/user)
+     * Check if client is trusted (no support for custom here, only system/user)
      */
-    public void checkClientTrusted(X509Certificate[] chain, String authType)
+    public void checkClientTrusted(X509Certificate[] chain, String authType, 
+                                   String hostName, String algorithm)
             throws CertificateException {
-
         // First try catrustmanager, then try usertrustmanager
         try {
             caTrustManager.checkClientTrusted(chain, authType);
@@ -131,9 +134,20 @@
         }
     }
 
+    public void checkClientTrusted(X509Certificate[] chain, String authType)
+            throws CertificateException {
+        checkClientTrusted(chain, authType, null, null);
+    }
+
+    public void checkServerTrusted(X509Certificate[] chain, String authType, 
+                                   String hostName, String algorithm)
+            throws CertificateException {
+        checkServerTrusted(chain, authType, hostName, false);
+    }
+    
     public void checkServerTrusted(X509Certificate[] chain, String authType)
             throws CertificateException {
-        checkServerTrusted(chain, authType, false);
+        checkServerTrusted(chain, authType, null, null);
     }
 
     /**
@@ -143,17 +157,44 @@
      * @param authType The auth type algorithm
      * @param checkOnly Whether to "check only" i.e. no user prompt, or to prompt for permission 
      */
-    public synchronized void checkServerTrusted(X509Certificate[] chain, String authType, boolean checkOnly) throws CertificateException {
+    public synchronized void checkServerTrusted(X509Certificate[] chain, 
+                             String authType, String hostName, 
+                             boolean checkOnly) throws CertificateException {
+        CertificateException ce = null;
+        boolean trusted = true;
+        boolean CNMatched = true;
+
         try {
             checkAllManagers(chain, authType);
-        } catch (CertificateException ce) {
-            
+        } catch (CertificateException e) {
+            trusted = false;
+            ce = e;
+        }
+
+        // If the certificate is not explicitly trusted, we 
+        // need to prompt the user
+        if (!isExplicitlyTrusted(chain, authType)) {
+
+            try {
+                HostnameChecker checker = HostnameChecker
+                        .getInstance(HostnameChecker.TYPE_TLS);
+
+                checker.match(hostName, chain[0]); // only need to match @ 0 for
+                                                   // CN
+
+            } catch (CertificateException e) {
+                CNMatched = false;
+                ce = e;
+            }
+        }
+
+        if (!trusted || !CNMatched) {
             if (checkOnly) {
                 throw ce;
             } else {
 
-                boolean b = askUser(chain,authType);
-            
+                boolean b = askUser(chain, authType, trusted, CNMatched, hostName);
+
                 if (b) {
                     temporarilyTrust(chain[0]);
                 }
@@ -162,7 +203,7 @@
             }
         }
     }
-
+    
     /**
      * Check system, user and custom trust manager  
      */
@@ -179,6 +220,26 @@
             }
         }        
     }
+    
+    /**
+     * Return if the user explicitly trusted this i.e. in userTrustManager or temporarilyTrusted
+     */
+    private boolean isExplicitlyTrusted(X509Certificate[] chain, String authType) {
+        boolean explicitlyTrusted = false;
+        
+        try {
+            userTrustManager.checkServerTrusted(chain, authType);
+            explicitlyTrusted = true;
+        } catch (ValidatorException uex) {
+            if (temporarilyTrusted.contains(chain[0]))
+                explicitlyTrusted = true;
+        } catch (CertificateException ce) {
+            // do nothing, this means that the cert is not explicitly trusted
+        }
+
+        return explicitlyTrusted;
+        
+    }
 
     public X509Certificate[] getAcceptedIssuers() {
         // delegate to default
@@ -201,8 +262,14 @@
      * @param authType The authentication algorithm
      * @return user's response
      */
-    private boolean askUser(X509Certificate[] chain, String authType) {
-    	return SecurityWarningDialog.showCertWarningDialog(SecurityWarningDialog.AccessType.UNVERIFIED, null, new HttpsCertVerifier(this, chain, authType)); 
+    private boolean askUser(X509Certificate[] chain, String authType, 
+                            boolean isTrusted, boolean hostMatched, 
+                            String hostName) {
+    	return SecurityWarningDialog.showCertWarningDialog(
+    	                SecurityWarningDialog.AccessType.UNVERIFIED, null, 
+    	                new HttpsCertVerifier(this, chain, authType, 
+    	                                      isTrusted, hostMatched,
+    	                                      hostName)); 
     }
 
     /**