Сертификаты клиента Java через HTTPS / SSL

116

Я использую Java 6 и пытаюсь создать HttpsURLConnectionпротиводействие удаленному серверу, используя сертификат клиента.
Сервер использует самоподписанный корневой сертификат и требует, чтобы был предоставлен защищенный паролем сертификат клиента. Я добавил корневой сертификат сервера и сертификат клиента в хранилище ключей Java по умолчанию, которое я нашел в /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts(OSX 10.5). Название файла хранилища ключей, кажется, предполагает, что сертификат клиента не должен туда входить?

В любом случае, добавление корневого сертификата в этот магазин решило печально известную javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.

Однако теперь я застрял в том, как использовать сертификат клиента. Я пробовал два подхода, и ни один меня ни к чему не привел.
Сначала попробуйте:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
URL url = new URL("https://somehost.dk:3049");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslsocketfactory);
InputStream inputstream = conn.getInputStream();
// The last line fails, and gives:
// javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

Я попытался пропустить класс HttpsURLConnection (не идеально, поскольку я хочу поговорить с сервером по HTTP) и вместо этого сделаю следующее:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("somehost.dk", 3049);
InputStream inputstream = sslsocket.getInputStream();
// do anything with the inputstream results in:
// java.net.SocketTimeoutException: Read timed out

Я даже не уверен, что проблема в сертификате клиента.

январь
источник
Я дал два сертификата от клиента, как определить, какой из них нужно добавить в хранилище ключей и хранилище доверенных сертификатов, не могли бы вы помочь определить эту проблему, поскольку вы уже прошли через подобную проблему stackoverflow.com/questions/61374276/…
henrycharles

Ответы:

100

Наконец решил это;). Есть сильный намек здесь (Gandalfs ответ коснулся немного на нем, а). Отсутствующие ссылки были (в основном) первым из параметров, приведенных ниже, и в некоторой степени я упустил из виду разницу между хранилищами ключей и надежными хранилищами.

Самозаверяющий сертификат сервера необходимо импортировать в хранилище доверенных сертификатов:

keytool -import -alias gridserver -file gridserver.crt -storepass $ PASS -keystore gridserver.keystore

Эти свойства необходимо установить (либо в командной строке, либо в коде):

-Djavax.net.ssl.keyStoreType=pkcs12
-Djavax.net.ssl.trustStoreType=jks
-Djavax.net.ssl.keyStore=clientcertificate.p12
-Djavax.net.ssl.trustStore=gridserver.keystore
-Djavax.net.debug=ssl # very verbose debug
-Djavax.net.ssl.keyStorePassword=$PASS
-Djavax.net.ssl.trustStorePassword=$PASS

Рабочий пример кода:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
URL url = new URL("https://gridserver:3049/cgi-bin/ls.py");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslsocketfactory);
InputStream inputstream = conn.getInputStream();
InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
BufferedReader bufferedreader = new BufferedReader(inputstreamreader);

String string = null;
while ((string = bufferedreader.readLine()) != null) {
    System.out.println("Received " + string);
}
январь
источник
Я использую URL-адрес, например: localhost: 8443 / Application_Name / getAttributes . У меня есть метод с сопоставлением URL-адресов / getAttribute. Этот метод возвращает список элементов. Я использовал HttpsUrlConnection, код ответа на соединение - 200, но он не дает мне список атрибутов, когда я использую inputStream, он дает мне html-контент моей страницы входа. Я выполнил аутентификацию и установил тип контента как JSON. Пожалуйста, предложите
Дипак
83

Хотя это не рекомендуется, вы также можете полностью отключить проверку сертификата SSL:

import javax.net.ssl.*;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

public class SSLTool {

  public static void disableCertificateValidation() {
    // Create a trust manager that does not validate certificate chains
    TrustManager[] trustAllCerts = new TrustManager[] { 
      new X509TrustManager() {
        public X509Certificate[] getAcceptedIssuers() { 
          return new X509Certificate[0]; 
        }
        public void checkClientTrusted(X509Certificate[] certs, String authType) {}
        public void checkServerTrusted(X509Certificate[] certs, String authType) {}
    }};

    // Ignore differences between given hostname and certificate hostname
    HostnameVerifier hv = new HostnameVerifier() {
      public boolean verify(String hostname, SSLSession session) { return true; }
    };

    // Install the all-trusting trust manager
    try {
      SSLContext sc = SSLContext.getInstance("SSL");
      sc.init(null, trustAllCerts, new SecureRandom());
      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
      HttpsURLConnection.setDefaultHostnameVerifier(hv);
    } catch (Exception e) {}
  }
}
neu242
источник
72
Следует отметить, что отключение проверки сертификата таким образом открывает соединение с возможными атаками MITM: не использовать в производстве .
Бруно
3
К счастью, код не компилируется. Это «решение» в корне небезопасно.
Marquis of Lorne
5
@ neu242, нет, на самом деле это не зависит от того, для чего вы его используете. Если вы хотите использовать SSL / TLS, вы хотите защитить свое соединение от атак MITM, в этом весь смысл. Аутентификация сервера не требуется, если вы можете гарантировать, что никто не сможет изменить трафик, но ситуации, когда вы подозреваете, что могут быть перехватчики, которые также не в состоянии изменить сетевой трафик, довольно редки.
Бруно
1
@ neu242 Спасибо за фрагмент кода. На самом деле я подумываю использовать это в производстве для очень конкретной цели (сканирование веб-сайтов), и я упомянул вашу реализацию в вопросе ( stackoverflow.com/questions/13076511/… ). Если у вас есть время, не могли бы вы взглянуть на него и сообщить мне, есть ли какие-либо риски безопасности, которые я пропустил?
Sal
1
@Bruno Если я пингую только сервер, действительно ли это повлияет на меня из-за атак MITM?
PhoonOne
21

Вы установили свойства KeyStore и / или TrustStore System?

java -Djavax.net.ssl.keyStore=pathToKeystore -Djavax.net.ssl.keyStorePassword=123456

или из с кодом

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);

То же самое с javax.net.ssl.trustStore

Гэндальф
источник
13

Если вы имеете дело с вызовом веб-службы с использованием платформы Axis, есть гораздо более простой ответ. Если все, что нужно вашему клиенту, - это иметь возможность вызывать веб-службу SSL и игнорировать ошибки сертификата SSL, просто введите этот оператор перед вызовом любых веб-служб:

System.setProperty("axis.socketSecureFactory", "org.apache.axis.components.net.SunFakeTrustSocketFactory");

Применяются обычные заявления об отказе от ответственности о том, что это очень плохо в производственной среде.

Я нашел это в вики Axis .

Марк Меуэр
источник
OP имеет дело с HttpsURLConnection, а не с Axis
neu242
2
Я понимаю. Я не имел в виду, что мой ответ в общем случае был лучше. Это просто , что , если будут с помощью рамки оси, вы можете иметь на вопрос OP еще в этом контексте. (Именно так я и нашел этот вопрос.) В этом случае предложенный мной способ проще.
Mark Meuer
5

Для меня это то, что сработало с использованием Apache HttpComponents ~ HttpClient 4.x:

    KeyStore keyStore  = KeyStore.getInstance("PKCS12");
    FileInputStream instream = new FileInputStream(new File("client-p12-keystore.p12"));
    try {
        keyStore.load(instream, "helloworld".toCharArray());
    } finally {
        instream.close();
    }

    // Trust own CA and all self-signed certs
    SSLContext sslcontext = SSLContexts.custom()
        .loadKeyMaterial(keyStore, "helloworld".toCharArray())
        //.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) //custom trust store
        .build();
    // Allow TLSv1 protocol only
    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        sslcontext,
        new String[] { "TLSv1" },
        null,
        SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //TODO
    CloseableHttpClient httpclient = HttpClients.custom()
        .setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) //TODO
        .setSSLSocketFactory(sslsf)
        .build();
    try {

        HttpGet httpget = new HttpGet("https://localhost:8443/secure/index");

        System.out.println("executing request" + httpget.getRequestLine());

        CloseableHttpResponse response = httpclient.execute(httpget);
        try {
            HttpEntity entity = response.getEntity();

            System.out.println("----------------------------------------");
            System.out.println(response.getStatusLine());
            if (entity != null) {
                System.out.println("Response content length: " + entity.getContentLength());
            }
            EntityUtils.consume(entity);
        } finally {
            response.close();
        }
    } finally {
        httpclient.close();
    }

Файл P12 содержит сертификат клиента и закрытый ключ клиента, созданный с помощью BouncyCastle:

public static byte[] convertPEMToPKCS12(final String keyFile, final String cerFile,
    final String password)
    throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException,
    NoSuchProviderException
{
    // Get the private key
    FileReader reader = new FileReader(keyFile);

    PEMParser pem = new PEMParser(reader);
    PEMKeyPair pemKeyPair = ((PEMKeyPair)pem.readObject());
    JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter().setProvider("BC");
    KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair);

    PrivateKey key = keyPair.getPrivate();

    pem.close();
    reader.close();

    // Get the certificate
    reader = new FileReader(cerFile);
    pem = new PEMParser(reader);

    X509CertificateHolder certHolder = (X509CertificateHolder) pem.readObject();
    java.security.cert.Certificate x509Certificate =
        new JcaX509CertificateConverter().setProvider("BC")
            .getCertificate(certHolder);

    pem.close();
    reader.close();

    // Put them into a PKCS12 keystore and write it to a byte[]
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
    ks.load(null);
    ks.setKeyEntry("key-alias", (Key) key, password.toCharArray(),
        new java.security.cert.Certificate[]{x509Certificate});
    ks.store(bos, password.toCharArray());
    bos.close();
    return bos.toByteArray();
}
EpicPandaForce
источник
Это keyStoreто, что содержит закрытый ключ и сертификат.
EpicPandaForce
1
Для работы кода convertPEMtoP12 необходимо включить эти 2 зависимости: <dependency> <groupId> org.bouncycastle </groupId> <artifactId> bcprov-jdk15on </artifactId> <version> 1.53 </version> </dependency> < зависимость> <groupId> org.bouncycastle </groupId> <artifactId> bcpkix-jdk15on </artifactId> <version> 1.53 </version> </dependency>
BirdOfPrey
@EpicPandaForce Я получаю сообщение об ошибке: Обнаружено: org.codehaus.groovy.runtime.typehandling.GroovyCastException: невозможно преобразовать объект с классом org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject в класс int в строке ks. setKeyEntry - любые подсказки, что может быть не так
Вишал Бияни
Да, вы используете Groovy вместо языка со строгой типизацией. (технически этот метод требует идентификатора и сертификата, а не только сертификата)
EpicPandaForce
4

Я использую пакет HTTP-клиента Apache commons для этого в моем текущем проекте, и он отлично работает с SSL и самозаверяющим сертификатом (после его установки в cacerts, как вы упомянули). Пожалуйста, взгляните на это здесь:

http://hc.apache.org/httpclient-3.x/tutorial.html

http://hc.apache.org/httpclient-3.x/sslguide.html

Тейлор Лиз
источник
1
Это кажется довольно изящным пакетом, но класс AuthSSLProtocolSocketFactory, который должен заставить все это работать, явно не является частью официального дистрибутива ни в 4.0beta (несмотря на то, что в примечаниях к выпуску это указано), ни в 3.1. Я немного поработал с ним, и теперь, кажется, постоянно застрял с 5-минутным зависанием, прежде чем он просто разорвет соединение. Это действительно странно - если я загружу ЦС и сертификат клиента в любой браузер, он просто слетит.
января
1
HTTP-клиент Apache 4 может принимать SSLContextнапрямую, поэтому вы можете настроить все это таким образом вместо использования AuthSSLProtocolSocketFactory.
Бруно,
1
Есть ли способ сделать все сертификаты клиента в памяти, а не через внешнее хранилище ключей?
Шридхар Сарнобат
4

Я думаю, что у вас проблема с сертификатом сервера, это недействительный сертификат (я думаю, это то, что означает "handshake_failure" в данном случае):

Импортируйте сертификат сервера в хранилище ключей trustcacerts на клиентской JRE. Это легко сделать с помощью keytool :

keytool
    -import
    -alias <provide_an_alias>
    -file <certificate_file>
    -keystore <your_path_to_jre>/lib/security/cacerts
sourcerebels
источник
Я попытался очистить и начать все сначала, и неудачное рукопожатие исчезло. Теперь я просто получаю 5 минут мертвой тишины до того, как соединение прерывается: o
января
1

Используя нижеприведенный код

-Djavax.net.ssl.keyStoreType=pkcs12

или

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);

совсем не требуется. Также нет необходимости создавать собственную фабрику SSL.

Я также столкнулся с той же проблемой, в моем случае возникла проблема, связанная с полной цепочкой сертификатов не была импортирована в доверенные хранилища. Импортируйте сертификаты с помощью утилиты keytool справа от корневого сертификата, также вы можете открыть файл cacerts в блокноте и посмотреть, импортирована ли вся цепочка сертификатов или нет. Сверьтесь с псевдонимом, который вы указали при импорте сертификатов, откройте сертификаты и посмотрите, сколько в них содержится, такое же количество сертификатов должно быть в файле cacerts.

Также файл cacerts должен быть настроен на сервере, на котором вы запускаете свое приложение, два сервера будут аутентифицировать друг друга с помощью открытых / закрытых ключей.

Анкур Сингхал
источник
Создание собственной фабрики SSL намного сложнее и подвержено ошибкам, чем установка двух системных свойств.
Маркиз Лорн