Проверка подлинности клиента HTTPS на Java

Я довольно HTTPS/SSL/TLS с HTTPS/SSL/TLS и меня немного смущает то, что именно клиенты должны представлять при аутентификации с помощью сертификатов.

Я пишу Java-клиент, который должен сделать простой POST данных для определенного URL. Эта часть работает нормально, единственная проблема заключается в том, что она должна выполняться через HTTPS. Часть HTTPS довольно проста в обращении (либо с HTTPclient либо с использованием встроенной поддержки HTTPS Java), но я застрял на аутентификации с помощью клиентских сертификатов. Я уже заметил здесь очень похожий вопрос, который я еще не пробовал с моим кодом (сделаю это достаточно скоро). Моя текущая проблема заключается в том, что - что бы я ни делал - клиент Java никогда не отправляет сертификат (я могу проверить это с помощью дампов PCAP).

Я хотел бы знать, что именно клиент должен представить серверу при аутентификации с помощью сертификатов (особенно для Java - если это вообще имеет значение)? Это файл JKS или PKCS#12? Что должно быть в них; просто сертификат клиента или ключ? Если так, какой ключ? Там довольно много путаницы по поводу всех видов файлов, типов сертификатов и тому подобного.

Как я уже говорил ранее, я новичок в HTTPS/SSL/TLS поэтому я был бы признателен также за некоторую справочную информацию (это не должно быть эссе; я остановлюсь на ссылках на хорошие статьи).

Ответ 1

Наконец удалось решить все проблемы, поэтому я отвечу на свой вопрос. Это настройки/файлы, которые я использовал, чтобы решить мою конкретную проблему (проблемы);

клиентское хранилище - это файл , содержащий

  • Клиент общедоступный сертификат (в этом случае подписанный самозаверяющим ЦС)
  • Клиент закрытый ключ

Чтобы сгенерировать его, я использовал команду OpenSSL pkcs12, например:

openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "Whatever"

Совет. Убедитесь, что вы получили последнюю версию OpenSSL, не версии 0.9.8h, потому что это, похоже, страдает от ошибки, которая не позволяет вам правильно генерировать файлы PKCS # 12.

Этот файл PKCS # 12 будет использоваться клиентом Java для представления сертификата клиента на сервер, когда сервер явно запросил аутентификацию клиента. См. Статью статьи в Википедии о TLS для обзора того, как фактически работает протокол проверки подлинности сертификата клиента (также объясняется, почему здесь нужен закрытый ключ клиента).

Консоль доверия - это прямой файл JKS format, содержащий root или промежуточные сертификаты CA. Эти сертификаты CA будут определять, с какими конечными точками вы будете иметь возможность общаться, в этом случае он позволит вашему клиенту подключиться к тому, какой сервер представляет сертификат, который был подписан одним из CA доверительного центра.

Чтобы сгенерировать его, вы можете использовать стандартный Java keytool, например:

keytool -genkey -dname "cn=CLIENT" -alias truststorekey -keyalg RSA -keystore ./client-truststore.jks -keypass whatever -storepass whatever
keytool -import -keystore ./client-truststore.jks -file myca.crt -alias myca

Используя этот доверенный магазин, ваш клиент попытается выполнить полное SSL-соединение со всеми серверами, которые представляют сертификат, подписанный CA, идентифицированный myca.crt.

Файлы, указанные выше, предназначены исключительно для клиента. Если вы хотите настроить сервер, сервер нуждается в собственных файлах ключей и доверенных хранилищ. Отличный шаг для создания полностью рабочего примера для Java-клиента и сервера (с использованием Tomcat) можно найти на этом веб-сайте.

Вопросы/замечания/советы

  • Аутентификация сертификата клиента может выполняться только сервером.
  • (Важно!) Когда сервер запрашивает сертификат клиента (как часть рукопожатия TLS), он также предоставляет список доверенных ЦС как часть запроса сертификата. Когда сертификат клиента, который вы хотите представить для аутентификации, не, подписанный одним из этих ЦС, он вообще не будет представлен (на мой взгляд, это странное поведение, но я уверен, что там причина для этого). Это было основной причиной моих проблем, так как другая сторона не настроила свой сервер должным образом, чтобы принять мой самозаверяющий клиентский сертификат, и мы предположили, что проблема была в моем конце за неправильное предоставление сертификата клиента в запросе.
  • Получить Wireshark. Он обладает отличным анализом пакетов SSL/HTTPS и станет огромной помощью для отладки и поиска проблемы. Он похож на -Djavax.net.debug=ssl, но более структурирован и (возможно) проще интерпретировать, если вам неудобно работать с отладочным выходом Java SSL.
  • Вполне возможно использовать библиотеку Apache httpclient. Если вы хотите использовать httpclient, просто замените целевой URL эквивалентом HTTPS и добавьте следующие аргументы JVM (которые одинаковы для любого другого клиента, независимо от библиотеки, которую вы хотите использовать для отправки/получения данных через HTTP/HTTPS)

    -Djavax.net.debug=ssl
    -Djavax.net.ssl.keyStoreType=pkcs12
    -Djavax.net.ssl.keyStore=client.p12
    -Djavax.net.ssl.keyStorePassword=whatever
    -Djavax.net.ssl.trustStoreType=jks
    -Djavax.net.ssl.trustStore=client-truststore.jks
    -Djavax.net.ssl.trustStorePassword=whatever

Ответ 2

Другие ответы показывают, как глобально настроить клиентские сертификаты. Однако, если вы хотите программно определить ключ клиента для одного конкретного соединения, а не глобально определять его в каждом приложении, запущенном на вашей JVM, тогда вы можете настроить свой собственный SSLContext следующим образом:

String keyPassphrase = "";

KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("cert-key-pair.pfx"), keyPassphrase.toCharArray());

SSLContext sslContext = SSLContexts.custom()
        .loadKeyMaterial(keyStore, null)
        .build();

HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
HttpResponse response = httpClient.execute(new HttpGet("https://example.com"));

Ответ 3

Они JKS файл - это просто контейнер для сертификатов и пар ключей. В сценарии аутентификации на стороне клиента различные части ключей будут расположены здесь:

  • В хранилище клиент будет содержаться клиентская частная и общедоступная пара ключей. Он называется хранилищем.
  • В хранилище сервер будет находиться клиентский общедоступный ключ. Он называется truststore.

Разделение доверительного хранилища и хранилища ключей необязательно, но рекомендуется. Они могут быть одним и тем же физическим файлом.

Чтобы установить расположение файловой системы в двух магазинах, используйте следующие системные свойства:

-Djavax.net.ssl.keyStore=clientsidestore.jks

и на сервере:

-Djavax.net.ssl.trustStore=serversidestore.jks

Чтобы экспортировать сертификат клиента (открытый ключ) в файл, чтобы вы могли скопировать его на сервер, используйте

keytool -export -alias MYKEY -file publicclientkey.cer -store clientsidestore.jks

Чтобы импортировать открытый ключ клиента в хранилище ключей сервера, используйте (как упомянутый плакат, это уже было сделано администраторами сервера)

keytool -import -file publicclientkey.cer -store serversidestore.jks

Ответ 4

Maven pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>some.examples</groupId>
    <artifactId>sslcliauth</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>sslcliauth</name>
    <dependencies>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.4</version>
        </dependency>
    </dependencies>
</project>

Код Java:

package some.examples;

import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.http.entity.InputStreamEntity;

public class SSLCliAuthExample {

private static final Logger LOG = Logger.getLogger(SSLCliAuthExample.class.getName());

private static final String CA_KEYSTORE_TYPE = KeyStore.getDefaultType(); //"JKS";
private static final String CA_KEYSTORE_PATH = "./cacert.jks";
private static final String CA_KEYSTORE_PASS = "changeit";

private static final String CLIENT_KEYSTORE_TYPE = "PKCS12";
private static final String CLIENT_KEYSTORE_PATH = "./client.p12";
private static final String CLIENT_KEYSTORE_PASS = "changeit";

public static void main(String[] args) throws Exception {
    requestTimestamp();
}

public final static void requestTimestamp() throws Exception {
    SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(
            createSslCustomContext(),
            new String[]{"TLSv1"}, // Allow TLSv1 protocol only
            null,
            SSLConnectionSocketFactory.getDefaultHostnameVerifier());
    try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(csf).build()) {
        HttpPost req = new HttpPost("https://changeit.com/changeit");
        req.setConfig(configureRequest());
        HttpEntity ent = new InputStreamEntity(new FileInputStream("./bytes.bin"));
        req.setEntity(ent);
        try (CloseableHttpResponse response = httpclient.execute(req)) {
            HttpEntity entity = response.getEntity();
            LOG.log(Level.INFO, "*** Reponse status: {0}", response.getStatusLine());
            EntityUtils.consume(entity);
            LOG.log(Level.INFO, "*** Response entity: {0}", entity.toString());
        }
    }
}

public static RequestConfig configureRequest() {
    HttpHost proxy = new HttpHost("changeit.local", 8080, "http");
    RequestConfig config = RequestConfig.custom()
            .setProxy(proxy)
            .build();
    return config;
}

public static SSLContext createSslCustomContext() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException {
    // Trusted CA keystore
    KeyStore tks = KeyStore.getInstance(CA_KEYSTORE_TYPE);
    tks.load(new FileInputStream(CA_KEYSTORE_PATH), CA_KEYSTORE_PASS.toCharArray());

    // Client keystore
    KeyStore cks = KeyStore.getInstance(CLIENT_KEYSTORE_TYPE);
    cks.load(new FileInputStream(CLIENT_KEYSTORE_PATH), CLIENT_KEYSTORE_PASS.toCharArray());

    SSLContext sslcontext = SSLContexts.custom()
            //.loadTrustMaterial(tks, new TrustSelfSignedStrategy()) // use it to customize
            .loadKeyMaterial(cks, CLIENT_KEYSTORE_PASS.toCharArray()) // load client certificate
            .build();
    return sslcontext;
}

}

Ответ 5

Для тех из вас, кто просто хочет настроить двустороннюю аутентификацию (серверные и клиентские сертификаты), комбинация этих двух ссылок поможет вам:

Настройка двусторонней аутентификации:

https://linuxconfig.org/apache-web-server-ssl-authentication

Вам не нужно использовать конфигурационный файл openssl, который они упоминают; просто используйте

  • $ openssl genrsa -des3 -out ca.key 4096

  • $ openssl req -new -x509 -days 365 -key ca.key -out ca.crt

создать свой собственный сертификат CA, а затем сгенерировать и подписать ключи сервера и клиента с помощью:

  • $ openssl genrsa -des3 -out server.key 4096

  • $ openssl req -new -key server.key -out server.csr

  • $ openssl x509 -req -days 365 -in server.csr -CA ca.crt -CA ключ ca.key -set_serial 100 -out server.crt

а также

  • $ openssl genrsa -des3 -out client.key 4096

  • $ openssl req -new -key client.key -out client.csr

  • $ openssl x509 -req -days 365 -in client.csr -CA ca.crt -CA ключ ca.key -set_serial 101 -out client.crt

В остальном следуйте инструкциям по ссылке. Управление сертификатами для Chrome работает так же, как в примере для Firefox, который упоминается.

Далее настройте сервер через:

https://www.digitalocean.com/community/tutorials/how-to-create-a-ssl-certificate-on-apache-for-ubuntu-14-04

Обратите внимание, что вы уже создали серверы .crt и .key, поэтому вам больше не нужно делать этот шаг.

Ответ 6

Я думаю, что исправление здесь было типом хранилища ключей, pkcs12 (pfx) всегда имеет закрытый ключ, а тип JKS может существовать без закрытого ключа. Если вы не укажете в своем коде или не выберите сертификат через браузер, сервер не может знать, что он представляет клиента на другом конце.