Как передать сертификат клиента клиенту HTTP?

Я хочу использовать взаимную аутентификацию SSL между службами A и B. В настоящее время я реализую передачу клиентского сертификата из службы A на Java. Я использую Apache DefaultHttpClient для выполнения моих запросов. Мне удалось получить сертификат клиента для моей службы A из внутреннего диспетчера учетных данных, и я сохраняю его как массив байтов.

DefaultHttpClient client = new DefaultHttpClient();
byte [] certificate = localCertManager.retrieveCert();

У меня очень мало опыта в этой области, и я буду благодарен за вашу помощь!

Я подумал, что, возможно, это должно быть каким-то образом передано через аргументы в HTTP-клиенте или, возможно, в заголовках.

Как передать сертификат клиента клиенту HTTP?

Ответ 2

Сертификат клиента отправляется во время рукопожатия TLS при установлении соединения и не может быть отправлен через HTTP в этом соединении.

Сообщение выглядит следующим образом:

  • HTTP (протокол уровня приложения) в
  • TLS (протокол уровня представления) в
  • TCP (протокол транспортного уровня) в
  • IP (протокол сетевого уровня)

Вам необходимо отправить сертификат клиента во время рукопожатия TLS, прежде чем на HTTP (методы, заголовки, URL, тела запросов) будет оказано влияние. Сервер не примет клиентский сертификат, отправленный позже.

Я рекомендую перейти с DefaultHttpClient (не рекомендуется) на CloseableHttpClient, который более аккуратно работает с try-with-resources.

Apache HttpClient 4.5 делает Mutual TLS достаточно удобным. Этот ответ был протестирован с помощью Apache HttpClient 4.5.3.

Важной отправной точкой является использование loadKeyMaterial для загрузки сертификата клиента и его ключа (пары ключей клиента) в SSLContext:

SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(
                MutualHttpsMain.class.getResource(TEST_CLIENT_KEYSTORE_RESOURCE),
                password, password,
                (aliases, socket) -> aliases.keySet().iterator().next()
        ).build();

И, наконец, создаем HTTP-клиент с фабрикой сокетов:

CloseableHttpClient httpclient = HttpClients
        .custom().setSSLContext(sslContext).build();

С этим клиентом все ваши запросы могут быть выполнены с подразумеваемой взаимной аутентификацией TLS:

CloseableHttpResponse closeableHttpResponse = httpclient.execute(
        new HttpGet(URI.create("https://mutual-tls.example.com/")));

Вот полный работающий пример взаимной TLS с Apache HttpClient:

import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;

import javax.net.ssl.SSLContext;
import java.io.Console;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.security.GeneralSecurityException;

public class MutualHttpsMain {
    private static final String TEST_URL = "https://mutual-tls.example.com/";
    private static final String TEST_CLIENT_KEYSTORE_RESOURCE = "/mutual-tls-keystore.p12";

    public static void main(String[] args) throws GeneralSecurityException, IOException {
        Console console = System.console();
        char[] password = console.readPassword("Keystore password: ");
        SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(
                MutualHttpsMain.class.getResource(TEST_CLIENT_KEYSTORE_RESOURCE),
                password, password,
                (aliases, socket) -> aliases.keySet().iterator().next()
        ).build();
        try (CloseableHttpClient httpclient = HttpClients
                .custom().setSSLContext(sslContext).build();
             CloseableHttpResponse closeableHttpResponse = httpclient.execute(
                    new HttpGet(URI.create(TEST_URL)))) {
            console.writer().println(closeableHttpResponse.getStatusLine());
            HttpEntity entity = closeableHttpResponse.getEntity();
            try (InputStream content = entity.getContent();
                 ReadableByteChannel src = Channels.newChannel(content);
                 WritableByteChannel dest = Channels.newChannel(System.out)) {
                ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
                while (src.read(buffer) != -1) {
                    buffer.flip();
                    dest.write(buffer);
                    buffer.compact();
                }
                buffer.flip();
                while (buffer.hasRemaining())
                    dest.write(buffer);
            }
        }
    }
}

Как правило, лучше использовать Gradle или Maven для запуска чего-то подобного, но в целях обеспечения минимального бритья Яка я предоставляю базовые инструкции JDK для создания и запуска этого.

Загрузите файлы JAR со следующих страниц:

Сохраните приведенный выше полный пример как MutualHttpsMain.java.

Скопируйте ваш PKCS # 12 в joint-tls-keystore.p12 в том же каталоге.

Скомпилируйте его следующим образом (в macOS/Linux/* nix-likes):

javac MutualHttpsMain.java -cp httpclient-4.5.3.jar:httpcore-4.4.8.jar

или в Windows:

javac MutualHttpsMain.java -cp httpclient-4.5.3.jar;httpcore-4.4.8.jar

Выполните следующим образом (в macOS/Linux/* nix-likes):

java -cp httpclient-4.5.3.jar:commons-codec-1.10.jar:commons-logging-1.2.jar:httpcore-4.4.8.jar:. MutualHttpsMain

Запустите (в Windows) следующим образом:

java -cp httpclient-4.5.3.jar;commons-codec-1.10.jar;commons-logging-1.2.jar;httpcore-4.4.8.jar;. MutualHttpsMain