Nightbook

Тестирование и модификация Java truststore/keystore хранилищ

Я уже давно не пишу статьи по теме работы. Находки новых знаний и новый опыт присутствуют, но то ли постоянное документирование их в wiki компании то ли быстрое устаревание материала удерживают меня от публикации такой информации в персональном блоге. Текущая статья исключения. Она относится к инструментарию.

Изначально проблемой стало исследование, тестирование и модификация Java truststore и keystore хранилищ на рабочем лептопе. Нужна конкретная версия Java и т.д.. Во время работы с такими хранилищами я накидал скрипт обертку и макрос, что значительно упростило мне процедуру тестов. В купе с этим я использовал подход с непосредственным воспроизведением SSL/TLS подключений в Java приложении.

Такой простой стенд принес очень много пользы. Настолько что я решил о нем написать не только в корпоративной документации, но и записать себе на будущее.

Основу стенда составит docker образ Java-машины

java.sh

#!/bin/sh
exec docker run --rm -ti -v $(pwd):/test -w /test -e HOME=/test -u $(id -u):$(id -g) openjdk:7 $@

Укажите версию jdk, которую вам нужно тестировать

UrlConnectionTest.java

import java.io.IOException;
import javax.net.ssl.HttpsURLConnection;
import java.net.MalformedURLException;
import java.security.cert.Certificate;
import java.net.URL;
public class UrlConnectionTest {
    public static void main(String[] args) throws MalformedURLException {
        URL url = new URL(System.getProperty("url"));
        try {
            HttpsURLConnection urlConnect = (HttpsURLConnection) url.openConnection();
            urlConnect.setReadTimeout(10000);
            urlConnect.setRequestMethod("GET");
            System.out.println(urlConnect.getResponseCode());
            System.out.println(urlConnect.getCipherSuite());
            Certificate[] certs = urlConnect.getServerCertificates();
            for(Certificate cert : certs){
                System.out.println("----------------------------------------- CERT BEGIN ---------------------------------------------");
                System.out.println(cert.toString());
                System.out.println("----------------------------------------- CERT END ---------------------------------------------");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Этот кусок кода поможет тестировать SSL/TLS подключения

ну и сам макрос

Makefile

all: test_trueststore

build:
    ./java.sh javac UrlConnectionTest.java

test_trueststore: build
    ./java.sh java -Djavax.net.ssl.trustStore=/test/cacerts -Durl=${URL} UrlConnectionTest

test: build
    ./java.sh java -Djavax.net.ssl.trustStore=/test/cacerts -Djavax.net.ssl.keyStore=/test/.keystore -Djavax.net.ssl.keyStorePassword=changeit -Durl=${URL} UrlConnectionTest

я почистил его от лишнего мусора. "changeit" это стандартный пароль truststore файлов. Файлы cacerts и .keystore должны у вас уже быть. Такая настройка позволяет тестировать подключения по https адресам. Например:

make URL=https://api.cognitive.microsoft.com/sts/v1.0/issueToken

основные операции с хранилищами можно выполнять через сценарий обертку

./java.sh keytool -import -trustcacerts -file Microsoft_Azure_TLS_Issuing_CA_01.pem  -alias Microsoft_Azure_TLS_Issuing_CA_01 -keystore cacerts
./java.sh keytool --list -trustcacerts  -keystore cacerts
./java.sh keytool -delete -alias Microsoft_Azure_TLS_Issuing_CA_01 -keystore cacerts

P.S. Ну и немного теории:

"javax.net.ssl.keyStore" и "javax.net.ssl.trustStore" параметры используются для создания KeyManagers и TrustManagers (соответственно), для построения SSLContext который содержит настройки SSL/TLS для создания SSL/TLS соединения через SSLSocketFactory или SSLEngine. Система уже имеет при запуске значения по умолчанию для этих параметров, которые могут быть получены через SSLContext.getDefault() или SSLSocketFactory.getDefault(). Различия между trustStore и keyStore хранилищем можно найти в JSSERefGuide

TrustManager: Определяет каким удаленным сертификатам может быть оказано доверие.

KeyManager: Определяет какие сертификаты процесс может отсылать Java-машина для установления соединения со своей стороны.

Хранилище в javax.net.ssl.keyStore содержит ваши ключи и сертификаты, тогда как javax.net.ssl.trustStore должно содержать сертификаты центров сертификации, которым можно доверять при установке соединений с удаленными узлами. Поместив сертификаты центров сертификаций в keyStore вы не заставите Java им доверять. В свою очередь сертификаты из trustStore не могут быть использованы для установки соединения со стороны Java.

Обычно указывать truststore нет необходимости. Им комплектуется любая версия Java дистрибутива ($JAVA_HOME/lib/security/cacerts). Основная проблема в его устаревании. Так как новые сертификаты центров сертификации туда не попадают. Обновлять дистрибутив Java для смены этого файла нет необходимости. Можно обновить его (про то как это сделать частично затронуто в этой статье) либо просто скопировать из более свежей версии Java (например из их docker образов).

По умолчанию Tomcat ищет Keystore в файле .keystore в домашней дериктории пользователя от которого запущен процесс (тут тоже часто используют стандартный пароль "changeit". Вот тут есть смысл его менять).