Ошибка Java HttpClient для отсутствия сертификата SSL, используя сертификат как строку в коде?
Я немного смущен в попытке использовать HttpClient для вызова https-сайта, использующего самозаверяющий сертификат. У меня есть код, как показано ниже, что позволяет мне сделать вызов, но затем я получаю сообщение об ошибке, как javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found
Я загрузил сертификат из своего веб-браузера и понимаю, что могу импортировать его в хранилище ключей, но я бы скорее просто поставил это в код и использовать его таким образом, есть ли способ сделать это?
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);
Ответы
Ответ 1
Предполагая, что ваш сертификат находится в формате PEM. Вы можете вставить его в код и использовать BouncyCastle PEMReader
, чтобы превратить его в экземпляр X509Certificate
. Как только это будет сделано, создайте экземпляр KeyStore
в памяти и поместите в него этот сертификат X.509. Затем создайте новый SSLContext
, используя KeyStore
в качестве хранилища доверия и сделайте его HTTP-клиентом.
Это будет выглядеть так (не пробовал, не забудьте закрыть читателей и уловить исключения...):
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);
Затем используйте это SSLContext
для вашего соединения. Вы можете сделать это с помощью Apache HttpClient SSLSocketFactory, если вы используете версию 4.x(или this, если вы используете версию 3.x). Я бы предложил использовать Apache HttpClient 4.x в настоящее время.
Ответ 2
Основываясь на ответе Alexander Chzhen и на HttpClient 4.3 Сначала создаю контекст, который доверяет всем:
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();
Затем клиентский строитель:
HttpClientBuilder hcb = HttpClients.custom();
и я устанавливаю только контекст. Я не использую setSSLSocketFactory, потому что это будет мешать setHostnameVerifier ниже:
hcb.setSslcontext(sslctx);
Наконец, я устанавливаю верификатор имени хоста, который проверяет все:
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;
}
});
Наконец, постройте клиента:
HttpClient c = hcb.build();
Ответ 3
Если вы хотите принять только этот единственный сертификат, но не все самоподписанные сертификаты, тогда вы должны загрузить сертификат и где-нибудь сохранить файл pem
.
Теперь вы можете использовать этот код для загрузки файла pem
, создания нового доверия с этим сертификатом и использования truststore для вашего 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());
}
}
}
Ответ 4
Вот как сделать Apache HttpClient 4.3, который принимает самозаверяющие сертификаты:
HttpClientBuilder cb = HttpClientBuilder.create();
SSLContextBuilder sslcb = new SSLContextBuilder();
sslcb.loadTrustMaterial(KeyStore.getInstance(KeyStore.getDefaultType()),
new TrustSelfSignedStrategy());
cb.setSslcontext(sslcb.build());
HttpClient client = cb.build()
Теперь для выполнения запросов POST или GET используется стандартный метод выполнения:
HttpResponse response = client.execute(...);
Напоминание: вы уязвимы, если доверяете самоподписанному сертификату.
Ответ 5
Во многих ситуациях сертификация может быть предпочтительнее жесткого кодирования конкретного сертификата.
Да, вы можете жестко закодировать сертификат в свой код, и это будет работать и быть безопасным. Это совершенно разумный подход. Однако он имеет некоторые недостатки. Одна ошибка заключается в том, что в конечном итоге срок действия сертификата истечет, а затем ваше приложение перестанет работать. Кроме того, если вы когда-либо захотите изменить свой секретный ключ, вы не сможете.
Поэтому во многих сценариях использование пиннинга сертификата является более гибким и может быть предпочтительным. См. здесь для ссылок о том, как внедрить сертификат.