Сертифікати клієнта 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 . У мене є метод з / getAttribute URL-відображенням. Цей метод повертає список елементів. Я використовував 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) {}
  }
}

72
Слід зазначити, що відключення перевірки сертифікатів на зразок цього відкриває з'єднання з можливими атаками MITM: не використовуйте у виробництві .
Бруно

3
Код не складається, на щастя. Це "рішення" є докорінно небезпечним.
Маркіз Лорн

5
@ neu242, ні, це насправді не залежить від того, для чого ти його використовуєш. Якщо ви хочете використовувати SSL / TLS, ви хочете захистити своє з'єднання від MITM-атак, у цьому вся суть. Автентифікація сервера не потрібна, якщо ви можете гарантувати, що ніхто не зможе змінити трафік, але ситуації, коли ви підозрюєте, що можуть бути підслуховувачі, які теж не зможуть змінити мережевий трафік, є досить рідкісними.
Бруно

1
@ neu242 Дякуємо за фрагмент коду. Я насправді думаю про використання цього у виробництві для дуже конкретної мети (сканування веб-сторінок), і я згадав про вашу реалізацію у питанні. ( Stackoverflow.com/questions/13076511/… ). Якщо у вас є час, ви можете, будь ласка, поглянути на це, і повідомте мені, чи є якісь ризики для безпеки, які я пропустив?
Сал

1
@Bruno Якщо я лише пінг-сервер, чи дійсно це вплине на мене, нападками MITM?
PhoonOne

21

Ви встановили властивості KeyStore та / або System TrustStore?

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 .


ОП має справу з HttpsURLConnection, а не
віссю

2
Я розумію. Я не збирався натякати на те, що моя відповідь була кращою у загальному випадку. Це просто , що , якщо будуть з допомогою рамки осі, ви можете мати на питання OP ще в цьому контексті. (Ось як я знайшов це питання в першу чергу.) У такому випадку спосіб, який я запропонував, простіший.
Марк Меуер

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();
}

Це keyStoreте, що містить приватний ключ та сертифікат.
EpicPandaForce

1
Вам потрібно включити ці 2 залежності для роботи коду converPEMtoP12: <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 - будь-які підказки, що може бути не так
Vishal Biyani

Так, ви використовуєте Groovy замість строго набраної мови. (технічно цей метод приймає ідентифікатор та сертифікат, а не лише сертифікат)
EpicPandaForce

4

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

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-хвилинною повішенням, перш ніж це просто перестане з'єднання. Це дійсно дивно - якщо я завантажую CA та клієнтський сертифікат у будь-який браузер, він просто летить.
січня

1
Apache HTTP Client 4 може приймати SSLContextбезпосередньо, так що ви можете налаштувати все це таким чином, а не використовувати AuthSSLProtocolSocketFactory.
Бруно

1
Чи є спосіб зробити всі сертифікати клієнта в пам'яті замість зовнішньої сховища ключів?
Шрідхар Сарнобат

4

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

Імпортуйте ваш серверний сертифікат у ваш сховище довірених клавіш на JRE клієнта. Це легко зробити за допомогою keytool :

keytool
    -import
    -alias <provide_an_alias>
    -file <certificate_file>
    -keystore <your_path_to_jre>/lib/security/cacerts

Я спробував прибирати та починати спочатку, і невдача рукостискання зникла. Тепер я отримую 5 хвилин мертвої тиші до припинення зв'язку: o
січня

1

Використовуючи код нижче

-Djavax.net.ssl.keyStoreType=pkcs12

або

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

зовсім не потрібно. Також немає необхідності створювати власну фабрику SSL.

Я також зіткнувся з тим же питанням, у моєму випадку виникла проблема, що повний ланцюжок сертифікатів не був імпортований у довірені магазини. Імпортуйте сертифікати за допомогою утиліти keytool right root для кореневого сертифіката, також ви можете відкрити файл кесерів у блокноті і побачити, чи імпортується повна ланцюжок сертифікатів чи ні. Перевірте ім'я псевдоніму, яке ви вказали під час імпорту сертифікатів, відкрийте сертифікати і подивіться, скільки в них міститься, стільки ж сертифікатів повинно бути там у файлі касертів.

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


Створення власного заводу SSL на замовлення набагато складніше та схильне до помилок, ніж встановлення двох системних властивостей.
Маркіз Лорн
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.