views:

708

answers:

3

Consider the following sample code which uses a TrustManager to log whether an outgoing connection used a valid certificate (but accept the connection in all cases):

import java.security.*;
import java.security.cert.*;
import javax.net.ssl.*;

public class CertChecker implements X509TrustManager {

    private final X509TrustManager defaultTM;

    public CertChecker() throws GeneralSecurityException {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init((KeyStore)null);
        defaultTM = (X509TrustManager) tmf.getTrustManagers()[0];
    }

    public void checkServerTrusted(X509Certificate[] certs, String authType) {
        if (defaultTM != null) {
            try {
                defaultTM.checkServerTrusted(certs, authType);
                System.out.println("Certificate valid");
            } catch (CertificateException ex) {
                System.out.println("Certificate invalid: " + ex.getMessage());
            }
        }
    }

    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
    public X509Certificate[] getAcceptedIssuers() { return null;}

    public static void main(String[] args) throws Exception {
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, new TrustManager[] {new CertChecker()}, new SecureRandom());
        SSLSocketFactory ssf = (SSLSocketFactory) sc.getSocketFactory();
        ((SSLSocket)ssf.createSocket(args[0], 443)).startHandshake();
    }
}

What do I have to do inside the checkClientTrusted method to check if that certificate is an extended validation certificate (green address bar in modern browsers) or a normal one (yellow address bar)?

edit:

I'm trying to get a CertPathValidator working, but somehow I only get exceptions about certificate is not a CA certificate... Any ideas?

edit2: Using PKIXParameters instead of PKIXBuilderParameters

private boolean isEVCertificate(X509Certificate[] certs, String authType) {
    try {
     CertPath cp = new X509CertPath(Arrays.asList(certs));
     KeyStore ks = KeyStore.getInstance("JKS");
     ks.load(new FileInputStream(new File(System.getProperty("java.home"), "lib/security/cacerts")), null);
     PKIXParameters cpp = new PKIXParameters(ks);
     cpp.setRevocationEnabled(false);
     CertPathValidator cpv = CertPathValidator.getInstance("PKIX");      
     PKIXCertPathValidatorResult res = (PKIXCertPathValidatorResult) cpv.validate(cp, cpp);
     System.out.println(res.getTrustAnchor().getCAName());
     System.out.println(res.getPolicyTree().getValidPolicy());
     System.out.println(cp);
     return false;
    } catch (Exception ex) {
     ex.printStackTrace();
     return false;
    }
}

I am testing against real-world EV certificates. The code now works with www.paypal.com (in the sense that it does not throw an exception), but does not work with banking.dkb.de. :-(

But even with Paypal.com the trust anchor getCAName returns null, so how can I know against which CA it was validated so that I can look up the right EV policy?

+2  A: 
erickson
There's no library for this!?
jrockway
Not that I know of. It's not a very common requirement; EV certificates are most useful in browsers, and there aren't many of those.
erickson
I don't get that `CertPathValidator` to work. My code is above. Please help me how to initialize it so that it works and does not throw exceptions...
mihi
Although I did not get it to work; I'm just accepting your answer since it was the most useful one :)
mihi
Your example works fine for me for www.paypal.com, but not for banking.dkb.de :( I guess something is wrong in my JDK/security settings/cacert file... I just give up now, thank you very much for your help :-)
mihi
Hey, I got it to work! After a lot of googling I found how to pass a certificate array to my own CertPathBuilder and validating the built cert path works now! See my own answer for code. Thank you very much for your help. :-) :-) :-)
mihi
+1  A: 

EDIT: Posted addtional code.

If you use Sun's X509 implementation, you can do something like this,

  CertificatePoliciesExtension ext = ((X509CertImpl)cert).getCertificatePoliciesExtension();
  List<PolicyInformation> policies = (List<PolicyInformation>)ext.get(CertificatePoliciesExtension.POLICIES);
  boolean evCert = false;
  for (PolicyInformation info : policies) {
      CertificatePolicyId id = info.getPolicyIdentifier();
      if (isEVPolicy(id)) {
         evCert = true;
         break;              
      }             
  }

  ......

  public static ObjectIdentifier[] EV_POLICIES;

  static {
      try {
          EV_POLICIES = new ObjectIdentifier[] {
       new ObjectIdentifier("2.16.840.1.113733.1.7.23.6"), // Verisign
       new ObjectIdentifier("1.3.6.1.4.1.14370.1.6"), // Geo-Trust of Verisign
       new ObjectIdentifier("2.16.840.1.113733.1.7.48.1") // Thawte
          };
      } catch (IOException e) {
  throw new IllegalStateException("Invalid OIDs");
      }
  }

  private boolean isEVPolicy(CertificatePolicyId id) {
 for (ObjectIdentifier oid : EV_POLICIES) {
  if (oid.equals((Object)id.getIdentifier())) 
   return true;
 }
 return false;
 }

We only allow EV cert from 3 CAs. You can add more EV OIDs in that array. You can get a full list of the OIDs from

http://hg.mozilla.org/mozilla-central/file/05ab1cbc361f/security/manager/ssl/src/nsIdentityChecking.cpp

ZZ Coder
No you can't. This will give you all policy extensions and not just the ones indicating that the certificate fulfills the "extended validation" requirements.
jarnbjo
This is just part of the code that checks for EV. However, this is enough if you just want to know if the cert has EV. Cert Policies Extension is added for EV cert and no CA uses for other purposes so far. Show me a real cert with other policies.
ZZ Coder
My bank is e.g. using a regular class 3 Verisign certificate with policy 2.16.840.1.113733.1.7.23.3 (non-EV).
jarnbjo
Can you tell me the name of the bank so I can check it out? I was told this extension is exclusively used for EV. In any case, I posted more code which includes the part to check for policies.
ZZ Coder
Policies are commonly used for other purposes besides EV. For example, many CAs use policies to indicate whether the private key resides on a hardware token (higher security) or is a lower-grade software key pair.
erickson
Also, your code is helpful, but its still not complete. For example, you need to make sure the policy is valid, given the policies higher up the cert path. Also, at a minimum, make sure that the policy ID matches the issuer.
erickson
The process to validate EV cert is indeed complex. Unfortunately, I can't post the whole class (800 lines long). We copied our logic from Firefox. Please refer to the C++ code mentioned in my answer for complete logic.
ZZ Coder
A: 

I finally got it to work... A running minimal example that shows all the logic and checks is below. And yes, it works for banking.dkb.de :-)

Thanks to all of you who helped me. Any comments about blatant security holes or anything else (except the code style or missing error handling; I tried hard to condense my code to the absolute minimum of runnable code) are welcome, so feel free to comment :)

import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;

import javax.net.ssl.*;
import javax.security.auth.x500.X500Principal;

public class CertChecker implements X509TrustManager {

    private final X509TrustManager defaultTM;

    public CertChecker() throws GeneralSecurityException {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init((KeyStore)null);
        defaultTM = (X509TrustManager) tmf.getTrustManagers()[0];
    }

    public void checkServerTrusted(X509Certificate[] certs, String authType) {
        if (defaultTM != null) {
            try {
                defaultTM.checkServerTrusted(certs, authType);
                if (isEVCertificate(certs))
                    System.out.println("EV Certificate: "+ certs[0].getSubjectX500Principal().getName() + " issued by " + certs[0].getIssuerX500Principal().getName());                    
                System.out.println("Certificate valid");
            } catch (CertificateException ex) {
                System.out.println("Certificate invalid: " + ex.getMessage());
            }
        }
    }

    private boolean isEVCertificate(X509Certificate[] certs) {
        try {
            // load keystore with trusted CA certificates
            KeyStore cacerts = KeyStore.getInstance("JKS");
            cacerts.load(new FileInputStream(new File(System.getProperty("java.home"), "lib/security/cacerts")), null);

            // build a cert selector that selects the first certificate of the certificate chain
            // TODO we should verify this against the hostname...
            X509CertSelector targetConstraints = new X509CertSelector();
            targetConstraints.setSubject(certs[0].getSubjectX500Principal());

            // build a cert path from our selected cert to a CA cert
            PKIXBuilderParameters params = new PKIXBuilderParameters(cacerts, targetConstraints);        
            params.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(Arrays.asList(certs))));
            params.setRevocationEnabled(false);
            CertPath cp = CertPathBuilder.getInstance("PKIX").build(params).getCertPath();

            // validate the cert path
            PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) CertPathValidator.getInstance("PKIX").validate(cp, params);
            return isEV(result);
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }

    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
    public X509Certificate[] getAcceptedIssuers() { return null;}

    public static void main(String[] args) throws Exception {
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, new TrustManager[] {new CertChecker()}, new SecureRandom());
        SSLSocketFactory ssf = (SSLSocketFactory) sc.getSocketFactory();
        ((SSLSocket)ssf.createSocket(args[0], 443)).startHandshake();
    }

    private static final Map<X500Principal, String> policies = new HashMap<X500Principal, String>();

    static {
        // It would make sense to populate this map from Properties loaded through 
        // Class.getResourceAsStream().
        policies.put(
                new X500Principal("OU=Class 3 Public Primary Certification Authority,O=VeriSign\\, Inc.,C=US"), 
                "2.16.840.1.113733.1.7.23.6"
        );
        // TODO add more certificates here
    }

    // based on http://stackoverflow.com/questions/1694466/1694720#1694720
    static boolean isEV(PKIXCertPathValidatorResult result)
    {
        // Determine the policy to look for.
        X500Principal root = result.getTrustAnchor().getTrustedCert().getSubjectX500Principal();
        System.out.println("[Debug] Found root DN: "+root.getName());
        String policy = policies.get(root);
        if (policy != null)
            System.out.println("[Debug] EV Policy should be: "+policy);

        // Traverse the tree, looking at its "leaves" to see if the end-entity 
        // certificate was issued under the corresponding EV policy.
        PolicyNode tree = result.getPolicyTree();
        if (tree == null)
            return false;
        Deque<PolicyNode> stack = new ArrayDeque<PolicyNode>();
        stack.push(tree);
        while (!stack.isEmpty()) {
            PolicyNode current = stack.pop();
            Iterator<? extends PolicyNode> children = current.getChildren();
            int leaf = stack.size();
            while (children.hasNext())
                stack.push(children.next());
            if (stack.size() == leaf) {
                System.out.println("[Debug] Found policy: " + current.getValidPolicy());
                // If the stack didn't grow, there were no "children". I.e., the 
                // current node is a "leaf" node of the policy tree.
                if (current.getValidPolicy().equals(policy))
                    return true;
            }
        }
        // The certificate wasn't issued under the authority's EV policy.
        return false;
    }
}
mihi