Java HttpClient error for no SSL certificate found, using certificate as String within code?
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);
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.
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();
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());
}
}
}
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.
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.
精彩评论