Validate X.509 certificate against CA in Java
Asked Answered
I

3

19

Lets say I have something like this (client side code):

TrustManager[] trustAllCerts = new TrustManager[]{
    new X509TrustManager() {

        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        @Override
        public void checkClientTrusted(
                java.security.cert.X509Certificate[] certs, String authType) {
        }

        @Override
        public void checkServerTrusted(
                java.security.cert.X509Certificate[] certs, String authType) {
        }
    }
};

SSLContext sslc = SSLContext.getInstance("TLS");
sslc.init(null, trustAllCerts, null);

SocketFactory sf = sslc.getSocketFactory();
SSLSocket s = (SSLSocket) sf.createSocket("127.0.0.1", 9124);

This code is complete functional, but I really can not figure out, how to validate server's certificate against one concrete CA certificate that I have available in pem file.

All certificates are signed by my self-signed CA, and it is the CA I need to validate against (only against this one).

Every answer is appreciated.

EDIT:

In response to jglouie (thank you very much this way - can not vote up your answer).

I founded the solution:

new X509TrustManager() {

        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        @Override
        public void checkClientTrusted(
                java.security.cert.X509Certificate[] certs, String authType) {
        }

        @Override
        public void checkServerTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
                throws CertificateException {
            InputStream inStream = null;
            try {
                // Loading the CA cert
                URL u = getClass().getResource("tcp/cacert.pem");
                inStream = new FileInputStream(u.getFile());
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                X509Certificate ca = (X509Certificate) cf.generateCertificate(inStream);
                inStream.close();

                for (X509Certificate cert : certs) {
                    // Verifing by public key
                    cert.verify(ca.getPublicKey());
                }
            } catch (Exception ex) {
                Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
            } finally {
                try {
                    inStream.close();
                } catch (IOException ex) {
                    Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

        }
    }
};
Industrialist answered 8/7, 2011 at 19:30 Comment(3)
Hey hello, I am trying to implement your solution for checkServerTrusted I have my .crt file with complete chain of certificate which was Exported from browser after opening website. but in for loop to verify public I got exception.Hsiuhsu
My basic question is what is pem file? how to generate it? what it contains private key info or public key info.? sorry I am asking too basic questions..Hsiuhsu
add question #28195659Hsiuhsu
A
24

I assume that the self-signed certificate of your CA is already loaded as follows:

CertificateFactory cf = CertificateFactory.getInstance("X.509");   
FileInputStream finStream = new FileInputStream("CACertificate.pem"); 
X509Certificate caCertificate = (X509Certificate)cf.generateCertificate(finStream);  

Then in the method to check certificate:

@Override        
 public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)  throws CertificateException {

 if (certs == null || certs.length == 0) {  
      throw new IllegalArgumentException("null or zero-length certificate chain");  
 }  

 if (authType == null || authType.length() == 0) {  
            throw new IllegalArgumentException("null or zero-length authentication type");  
  }  

   //Check if certificate send is your CA's
    if(!certs[0].equals(caCertificate)){
         try
         {   //Not your CA's. Check if it has been signed by your CA
             certs[0].verify(caCertificate.getPublicKey())
         }
         catch(Exception e){   
              throw new CertificateException("Certificate not trusted",e);
         }
    }
    //If we end here certificate is trusted. Check if it has expired.  
     try{
          certs[0].checkValidity();
      }
      catch(Exception e){
            throw new CertificateException("Certificate not trusted. It has expired",e);
      }  
}

Disclaimer: Have not even atempted to compile the code

Amagasaki answered 8/7, 2011 at 21:17 Comment(2)
This doesn't do what is claimed. The certificate sent, which is in certs[0], can never be the CA certificate. It has to be the peer's own certificate, with the signing CA's certifcate being further up the chain. Furthermore there is nothing in the specification of these methods that allows the chain argument to be either null or zero length, or that allows authType to be null or an empty string. authType is in fact the name of a key exchange algorithm, which can't be either of those two things.Decomposer
LIKE A BOSS: "Disclaimer: Have not even atempted to compile the code"Latterll
B
6

The accepted answer is extremely incorrect. It doesn't cryptographically verify any connection between the server certificate and the trusted certificate authority. In general, you should almost never need to implement your own TrustManager, doing so is extremely dangerous.

As EJP stated, there's no need to implement your own TrustManager, you can just use the default one, and ensure that the trusted CA certificate has been added to your default TrustStore. See this question for more information.

Take a look at the CertPathValidator class from the JDK, which verifies a continuous chain of trust from the server's own certificate up through a trusted CA. See Oracle's docs for an introduction to certificate chain validation.

Barbbarba answered 21/10, 2017 at 2:31 Comment(0)
D
-1

This code is completely functional

This code is completely dysfunctional. It is completely insecure, as well as not even conforming to its own specification. There is rarely a need to supply your own TrustManager, the default one works really well.

All you need to do is ensure that the CA certificate you have is present in your truststore, and then set the system property javax.net.ssl.trustStore to point to it if it isn't the default Java truststore file. You don't need to write any code at all beyond possibly System.setProperty(), if you don't set it via the command line -D option.

EDIT Your 'solution' certainly won't work in general. It assumes that every certificate in the chain is signed by your certificate. That can only be true for chains of length 1, or length 2 if the signing certificate = your certificate.

Decomposer answered 10/7, 2011 at 4:31 Comment(8)
This is not really an answer, and is far from true. If you program a software, that will connect to a specific server over ssl, you should overwrite the TrustManager and actively check if the server you are connecting to is the one you trust. This helps you greatly against MIM attacks.Diageotropism
@stoikov Certainly it's true, and your own statement is entirely false. You don't need to override the TrustManager to authorize the peer: you can get its certificate directly from the socket via the SSLSession, or via a handshake listener. In any case this TrustManager here doesn't do either: neither authorization nor even authentication, which is why I deprecate it strongly whenever I see it.Decomposer
How this answer has 6 down votes? IMO is totally right! override TrustManager it's only for a rare cases normally when you're making some tests never in production mode... if as OP ask he want to validate a certificate against specific CA then create a truststore with only this CA instead of override TrustManager.Preraphaelite
@Preraphaelite I'd say it's pretty clear why - it doesn't answer the question.Burra
@immibis It has certainy answered the question since the edit of 29 January, two months prior to your comment.Decomposer
@EJP The question, the original one, was about how to validate a TLS certificate against a pinned CA. You have not provided any information about how to validate a TLS certificate against a pinned CA.Burra
This answer is correct- it's strange and worrying to me that this answer has been downvoted several times. Implementing your own TrustManager is a really bad idea unless you're extremely knowledgable about crypto and Java. In particular, the specific solution provided by Jakub above isn't doing anything like normal certificate chain validation. You should be using the JDK's X509TrustManager.Barbbarba
@immibis I have stated that the default trust manager works really well. That is all the information required.Decomposer

© 2022 - 2024 — McMap. All rights reserved.