Java HttpClient error for no SSL certificate found, using certificate as String within code?
Asked Answered
R

5

6

I'm a bit confused in trying to use HttpClient to call an https site that uses a self-signed certificate. I have the code like below, which is enabling me to make the call but then I am getting the error like javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found I have downloaded the certificate from my web browser and understand I can import it to the keystore but I would rather just put it into the code and use it that way, is there a way to do this?

    HttpClient client = new HttpClient();

    EasySSLProtocolSocketFactory easySSLProtocolSocketFactory = new EasySSLProtocolSocketFactory();
    Protocol https = new Protocol("https", easySSLProtocolSocketFactory,
            443);
    Protocol.registerProtocol("https", https);

    BufferedReader br = null;

    String responseString = "";

    GetMethod method = new GetMethod(path);

    int returnCode = client.executeMethod(method);
Rancho answered 23/6, 2011 at 16:28 Comment(1)
Why on earth would you put it into the code? What happens when the server certificate expires and they get a new one? Don't do this.Alum
U
4

Assuming your certificate is in PEM format. You can embed it in the code and use BouncyCastle's PEMReader to turn it into an X509Certificate instance. Once this is done, create a KeyStore instance in memory and put this X.509 certificate in it. Then, instantiate a new SSLContext using that KeyStore as the trust store and make your HTTP client use it.

This would look like this (not tried, remember to close readers and catch exceptions...):

PEMReader pemReader = new PEMReader(new StringReader("----- BEGIN ......");
X509Certificate cert = (X509Certificate) pemReader.readObject();

KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry("some name", cert);

TrustManagerFactory tmf = TrustManagerFactory.getInstance(
    TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);

SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, tmf.getTrustManagers(), null);

Then, use this SSLContext for your connection. You can do this with Apache HttpClient's SSLSocketFactory if you're using version 4.x (or this if you're using version 3.x). I'd suggest using Apache HttpClient 4.x nowadays.

Unrepair answered 23/6, 2011 at 17:12 Comment(4)
thanks, this is great info, I am not familiar with SSLSocketFactory and am struggling to find an example, I'm guessing it would replace EasySSLProtocolSocketFactory in my code? Any advice on using this is greatly appreciatedRancho
actually I think I imported the wrong SSL factory (from Java.net.ssl.. I see I need the Apache one nowRancho
could you clarify which SSLContext package you are using, sorry am just a bit confused as there is more than 1Rancho
The SSLSocketFactory I've linked to is for Apache HttpClient 4. You seem to be using Apache HttpClient 3 (in which case you could use the one from jSSLutils). If you're not dealing with legacy code that uses HttpClient 3, you might as well upgrade to version 4, since version 3 is no longer supported. (I'm talking about javax.net.ssl.SSLContext)Unrepair
L
2

Building on answer by Alexander Chzhen and for HttpClient 4.3 I first create a context that trusts all:

SSLContextBuilder sslctxb = new SSLContextBuilder();

sslctxb.loadTrustMaterial(KeyStore.getInstance(KeyStore.getDefaultType()),
                          new TrustSelfSignedStrategy() {
  @Override
  public boolean isTrusted(X509Certificate[] chain,
                           String            authType)
    throws CertificateException {
    return true;
  }
});

SSLContext sslctx = sslctxb.build();

Then the client builder:

HttpClientBuilder hcb = HttpClients.custom();

and I only set the context. I don't use setSSLSocketFactory because it will interfere with setHostnameVerifier below:

hcb.setSslcontext(sslctx);

Finally I set a hostname verifier that verifies all:

hcb.setHostnameVerifier(new X509HostnameVerifier() {
  @Override
  public void verify(String host, SSLSocket ssl)
    throws IOException {
  }

  @Override
  public void verify(String host, X509Certificate cert)
    throws SSLException {
  }

  @Override
  public void verify(String host, String[] cns, String[] subjectAlts)
    throws SSLException {
  }

  @Override
  public boolean verify(String hostname, SSLSession session) {
    return true;
  }
});

Finally build the client:

HttpClient c = hcb.build();
Longe answered 9/11, 2013 at 12:20 Comment(1)
@Mike, the problem with these two solutions is that you're accepting any self-signed certificate (which is more or less the same as disabling certificate verification altogether).Unrepair
H
1

If you want to accept only this single certificate but not all self signed certificates, then you should download the certificate and store the pem file somewhere.

Now you can use this code to load the pem file, create a new truststore with this certificate and use the truststore for your HttpClient.

//use java. ... .X509Certificate, not javax. ... .X509Certificate
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;

@Test
public void testSslCertificate()
        throws IOException, KeyStoreException, NoSuchAlgorithmException,
               CertificateException, KeyManagementException {

    X509Certificate cert;
    try (FileInputStream pemFileStream = new FileInputStream(newFile("your.pem"))) {
        CertificateFactory certFactory = CertificateFactory.getInstance("X509");
        cert = (X509Certificate) certFactory.generateCertificate(pemFileStream);
    }

    //create truststore
    KeyStore trustStore = KeyStore.getInstance("JKS");
    trustStore.load(null); //create an empty trustore

    //add certificate to truststore - you can use a simpler alias
    String alias = cert.getSubjectX500Principal().getName() + "["
            + cert.getSubjectX500Principal().getName().hashCode() + "]";
    trustStore.setCertificateEntry(alias, cert);

    //configure http client
    TrustManagerFactory trustManagerFactory =
       TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(trustStore);

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

    HttpClientBuilder httpClientBuilder = HttpClientBuilder.
                                          create().setSslcontext(sslContext);

    try (CloseableHttpClient httpClient = httpClientBuilder.build()) {
        HttpGet httpGetRequest = new HttpGet("https://yourServer");
        try (CloseableHttpResponse httpResponse =
                            httpClient.execute(httpGetRequest)) {
            Assert.assertEquals(200,
                                httpResponse.getStatusLine().getStatusCode());
        }
    }
}
Heptastich answered 24/1, 2014 at 10:21 Comment(0)
F
0

This is how to make Apache HttpClient 4.3 that accept self-signed certificates:

HttpClientBuilder cb = HttpClientBuilder.create();
SSLContextBuilder sslcb = new SSLContextBuilder();
sslcb.loadTrustMaterial(KeyStore.getInstance(KeyStore.getDefaultType()),
                        new TrustSelfSignedStrategy());
cb.setSslcontext(sslcb.build());
HttpClient client = cb.build()

Now to execute POST or GET requests use standard execute method:

HttpResponse response = client.execute(...);

Reminder: You are vulnerable when you trust self-signed certificate.

Finn answered 22/9, 2013 at 8:44 Comment(1)
Getting this exception when I use your code: "Exception in thread "main" javax.net.ssl.SSLException: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty"Quickfreeze
P
0

In many situations, certificate pinning might be preferable to hardcoding a particular certificate.

Yes, you can hardcode a certificate into your code, and that will work and be secure. It's a perfectly reasonable approach. However, it does have some disadvantages. One pitfall is that eventually that certificate will expire, and then your app will stop working. Also, if you ever want to change your private key, you won't be able to.

Therefore, in many scenarios, using certificate pinning is more flexible and might be preferable. See here for references on how to implement certificate pinning.

Purtenance answered 29/5, 2014 at 0:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.