Кирилл Данилов (donz_ru) wrote,
Кирилл Данилов
donz_ru

Category:

HTTPS сервер на Java, PKCS#12 и серверный сертификат



Хозяйке на заметку и себе, чтобы не забыть, так как все это добро ни разу не логично и не интуитивно понятно.

Итак, нам надо выпустить серверный сертификат.
1. Сначала создаем секретный ключ и запрос на сертификат:

openssl req -utf8 -config openssl.cnf -newkey rsa:2048 -nodes -keyout private/cert_priv.key -out requests/cert.req

В процессе вводим пароль к секретному ключу. Не забываем его записать на стикер и приклеить на монитор.

2. Высылаем запрос специально обученному человеку, который создаст сертификат и подпишет его родительским сертификатом, чтобы клиент (например, браузер) мог выстроить цепочку сертификатов, добравшись до доверенного корневого и убедиться, что все ок, и никакой товарищ майор посередине трафик слушать не будет. Получаем сертификат.

3. Теперь надо создать PKCS#12 контейнер - хранилище ключей, который будет содержать полученный сертификат и секретный ключ. Этот контейнер уже будет использоваться в коде.

openssl pkcs12 -export -out cert_container.p12 -inkey private/cert_priv.key -in cert.cer

Для тех, кто считает, что хорошо знает криптографию, первый вопрос: что за пароль "Enter Export Password:" запросит OpenSSL во время выполнения этой команды?

4. Переходим к яве. Нам надо загрузить это хранилище ключей и проинициализировать SSLContext в яве, где мы делаем сервер с поддержкой HTTPS. Условный, но работающий код:

final char[] password = new char[]{'1', '1', '1', '1'};

final KeyStore ks = KeyStore.getInstance("PKCS12");
final InputStream keystore = SenderActorTest.class.getClassLoader().
        getResourceAsStream("cert_container.p12");
if (keystore == null) {
    throw new RuntimeException("Keystore required!");
}
ks.load(keystore, password);

final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(ks, password);

final TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);

final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(keyManagerFactory.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());




Казалось бы все. Можно использовать. Почти.
Второй вопрос для тех, кто считает, что хорошо знает криптографию: что за пароль надо указать в строках с ks.load и keyManagerFactory.init? Только чур в исходники не подглядывать!

Третий вопрос для тех, кто считает, что хорошо знает криптографию: а где надо указывать пароль для доступа к секретному ключу из этого хранилища?

Четвертый вопрос уже для тех, кто считает, что хорошо знает явовскую инфраструктуру вокруг HTTPS: а что будет, если пароль для секретного ключа указан неверно?

[Ответы]
1. Ну это довольно просто: пароль для доступа к создаваемому PKCS#12 контейнеру

2. Тоже довольно просто: пароль для доступа к создаваемому PKCS#12 контейнеру. Или нет?

3. Хе-хе. Пароль для доступа к секретному ключу должен совпадать с паролем к PKCS#12 контейнеру! Я немного офигел. Или это действительно где-то в спецификациях прописано? Убедиться, что это так как минимум для алгоритма SunX509 можно, перейдя внутрь keyManagerFactory.init и добравшись до конструктора sun.security.ssl.SunX509KeyManagerImpl#SunX509KeyManagerImpl
И вроде бы это должно означать, что пароль для доступа к контейнеру надо указывать при вызове ks.load, а пароль для секретного ключа при вызове keyManagerFactory.init. Но если эти ключи не совпадает, то последний вызов падает. Додебажить декомпилированный код сегодня уже не могу, но там явно что-то не чисто.
У меня все это добро завести получилось только при одном условии: пароль для контейнера и пароль для секретного ключа должны совпадать.

4. А ничего не будет. Если в keyManagerFactory.init указать пароль не от контейнера, то этот вызов упадет. А если указать пароль от контейнера, но который не совпадает с паролем от секретного ключа, то инициализация пройдет отлично. Просто сервер на все HTTPS запросы будет выдавать херню, так как будет искренне думать, что использует правильный секретный ключ, а не фигню, получившуюся в результате применения неверного пароля при извлечении этого секретного ключа. И, соответственно, после обработки исходящего HTTP сообщения таким секретным ключом там будет белиберда.


Все-таки подозреваю, что я где-то что-то не так делаю. Может кто подскажет?

UPD: все в порядке. Это я в пятницу переработал, похоже. Не знаю, для чего именно я вводил пароль при создании запроса на сертификат, но повторное выполнение этой команды показало, что там никакого пароля не требуется. Возможно до этого копался с чем-то и ввел там, а запомнил, что как-будто вводил при создании секретного ключа и запроса на сертификат. Собственно, созданный секретный ключ с заголовком "RSA PRIVATE KEY" без слова ENCRYPTED тоже намекает, что ключ этот у меня создался без шифрования.
Итог: в приведенном куске кода оба раза надо использовать пароль от контейнера. В уже зашифрованном контейнере секретный ключ еще раз уже не шифруется. Так что все норм, все снова стало логично, и нечего копаться в таких темах по вечерам пятниц.
Tags: #12, #sunx509keymanagerimpl, https, java, ssl, безопасность, программирование
Subscribe
  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 6 comments