Аутентификация сертификата клиента HTTPS Java

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

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

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

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

6 ответов


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

на хранилище ключей клиента это формат PKCS#12 содержащих

  1. клиента общественные сертификат (в данном случае подписанный САМОЗАВЕРЯЮЩИМ CA)
  2. клиента частная ключ

в сгенерируйте его, я использовал OpenSSL , например:

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

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

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

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

для его генерации можно использовать стандартный 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

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

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

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

  1. проверка подлинности клиента сертификат может применяться только сервером.
  2. (важно!) когда сервер запрашивает сертификат клиента (как часть TLS рукопожатие), он также предоставит список доверенных ЦС как часть запроса сертификата. Когда сертификат клиента, который вы хотите представить для аутентификации,не подписанный одним из этих CA, он не будет представлен вообще (на мой взгляд, это странное поведение, но я уверен, что для этого есть причина). Это было основной причиной моих проблем, так как другая сторона не настроила свой сервер должным образом, чтобы принять мой самозаверяющий сертификат клиента, и мы предположили, что проблема была с моей стороны за неправильное предоставление сертификата клиента в запросе.
  3. Получить Wireshark. Он имеет большой анализ пакетов SSL/HTTPS и будет огромной помощью для отладки и поиска проблемы. Это похоже на -Djavax.net.debug=ssl но более структурирован и (возможно) легче интерпретировать, если вам неудобно с выходом отладки JAVA SSL.
  4. вполне возможно использовать библиотеку 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

другие ответы показывают, как глобально настроить сертификаты клиента. Однако, если вы хотите программно определить ключ клиента для одного конкретного соединения, а не глобально определить его в каждом приложении, работающем на вашем 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"));

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

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

разделение truststore и keystore не является обязательным, но рекомендуется. Это может быть один и тот же физический файл.

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

-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

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

}

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

двусторонняя настройка auth:

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

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

  • $ openssl genrsa-des3-out ca.ключ 4096

  • $ openssl req-new-x509-дни 365 - ключ ca.key-out ca.crt

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

  • $ openssl genrsa-des3-out сервер.ключевые 4096

  • $ openssl req-новый ключ клиента.сервер ключей.КСО

  • $ openssl x509-req-дней 365-в сервере.КСО -ца-ца.ЭЛТ -Кейки Калифорния.ключ - set_serial 100-out сервер.crt

и

  • $ openssl genrsa-des3-out клиент.ключевые 4096

  • $ openssl req-новый ключ клиента.клиент key-out.КСО

  • $ openssl x509-req-дней 365-в клиенте.КСО -ца-ца.ЭЛТ -Кейки Калифорния.ключ-set_serial 101-out клиент.crt

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

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

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

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


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