Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I verify that a certificate is an EV certificate with Java?

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?

like image 493
mihi Avatar asked Mar 01 '23 02:03

mihi


1 Answers

First, you'll need a table of issuer names and their corresponding EV policy identifiers.

When a CA issues a certificate, they can note the policy under which they issued the certificate. The identifier for this policy assigned by the issuer, so that's why you need a list of issuers and their EV policies.

Then you'll need to get the policy from the server certificate. Refer to RFC 5280, § 4.1.2.4 to learn more about policies in general and how they work.

You'll need to validate the certificate chain to obtain a PKIXCertPathValidatorResult. Part of the result is a policy tree. You can navigate through the policy tree to determine if it includes the EV policy for the target certificate's issuer.


Here's a detailed example of checking a certificate path result.

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"
  );
  // ...
}

static boolean isEV(PKIXCertPathValidatorResult result)
{
  /* Determine the policy to look for. */
  X500Principal root = result.getTrustAnchor().getTrustedCert().getSubjectX500Principal();
  String policy = policies.get(root);
  if (policy == null)
    /* The EV policy for this issuer is unknown (or there is none). */
    return false;
  /* 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();
  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) {
      /* 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;
}
like image 164
erickson Avatar answered Mar 05 '23 17:03

erickson