klink0v (klink0v) wrote,
klink0v
klink0v

Траходром с X.509, PKCS12, OpenSSL и Java-приложениями

Чем нормальные люди в субботу занимаются? Правильно, пиво пьют. Но сисадмины — не нормальные. Они долбанутые. Ладно-ладно, уговорили. Не "они долбанутые", а "я долбанутый".

Итак. Дано: "честный" X.509-сертификат в виде base64-encoded PEM-контейнера от удостоверяющего центра "GeoTrust", он же "RapidSSL", он же "Symantec", он же "Thawte", он же бывший "Equifax" (они там в своё время друг друга перекупали, так что фактически сейчас это всё одна и та же компания). Пусть будет на имя "CN=supersite.mycompany.com". Закрытый ключ в комплекте. Также имеется какая-нибудь софтина на Java, которая взаимодействует с внешним миром посредством SSL. Хоть Jetty, хоть OpenFire, хоть ещё что.

Хочется: скормить этот сертификат вместе с ключом Java-приложению.

Трудности. О-о-о-о-о, трудностей здесь несколько.


  • К вопросу о популярности Java. Разработчики сего творенья не искали лёгких путей. И вместо использования общепринятых контейнеров для сертификатов они изобрели велосипед свой собственный стандарт: JKS. Что расшифровывается как "Java KeyStore". Конечно же, ни с чем не совместимый. И все Java-приложения по умолчанию должны использовать именно его.

  • Единственный способ поиметь в JKS сертификат вместе с уже готовым заранее сгенерированным закрытым ключом — это промежуточная его конвертация в PKCS12. (Есть ещё вариант сгенерировать закрытый ключ при помощи Java-утилит, но это не мой случай.)

  • Как правило (за редким исключением), все современные CA выдают заказчику сертификат, подписанный одним или несколькими промежуточными сертификатами. Таким образом, конвертировать из PEM в JKS нужно не один отдельно взятый сертификат, а всю цепочку целиком (до "корня"). В противном случае случится одно из двух: либо сервер его не зохавает, либо зохавает, но клиентские приложения (браузер, почтовый клиент, Jabber-клиент и т.п.) будут ругаться-материться на "недоверенные" сертификаты.

Таким образом, нам необходимо пройти три шага.


  1. Подготовить в виде PEM полную цепочку сертификатов и убедиться в её целостности. Иначе не удастся шаг 2.

  2. Сконвертировать эту цепочку из PEM в PKCS12.

  3. Сконвертировать цепочку из PKCS12 в JKS.

И на каждом шагу нас ждёт недетский траходром. Поехали.

Сперва подготавливаем сам предназначающийся серверу сертификат и закрытый ключ к нему. Пусть для примера файлы называются "mycert.crt" и "myserver.key". Проверяем, что закрытый ключ действительно соответствует нашему сертификату. Для этого проверим параметр "modulus" того и другого. Они должны совпадать.

openssl x509 -noout -modulus -in mycert.crt | openssl md5
openssl rsa -noout -modulus -in myserver.key | openssl md5

Если сопадают, едем дальше.

Теперь нам надо где-то найти все промежуточные и корневые сертификаты. Обычно они лежат в открытом доступе на сайте компании, выпустившей ваш сертификат. Но тут есть одна ба-а-а-альшая подстава. Как вы собираетесь проверять, что сертификат "Б" выпущен с использованием сертификата "А"? Может быть, вот так?

openssl verify -verbose -CAfile intermediate.crt mycert.crt

Бу-га-га-га-га! Если даже вам в ответ сказали: "mycert.crt: OK", — это ещё ничего не значит. От слова "совсем". Это говорит только о том, что сертификат "mycert.crt" (а точнее, его хеш) подписан закрытым ключом от сертификата "intermediate.crt". Это необходимое условие, но совсем не достаточное. Далеко не факт, что эти сертификаты действительно принадлежат одной цепочке. Проверять надо вот так:

openssl x509 -noout -in mycert.crt -text | grep -A1 'Authority Key Identifier'
openssl x509 -noout -in intermediate.crt -text | grep -A1 'Subject Key Identifier'

Обратите внимание, параметр "-in" там разный (уточняю на всякий случай). Сначала мы тестируем "дочерний" сертификат, затем "родительский". В ответ каждая из этих команд вам выплюнет много циферок типа того: "48:E6:68:F9:2B:D2:B2:95:D7:47:D8:23:20:10:4F:33:98:90:9F:D4". И если в обоих случаях все циферки совпали, вот тогда сертификатики точно те что надо.

В конце концов следуя этим пешим эротическим путём мы должны придти к сертификату, у которого поля "Authority Key Identifier" и "Subject Key Identifier" совпадают. То есть, обе вышеобозначенные команды применительно к одному и тому же сертификату дадут один и тот же результат. Если это случилась, то "ура": мы прошли всю цепочку вплоть до корневого сертификата (Root CA).

Как правило, нужный Root CA можно найти в папочке "/usr/share/ca-certificates/mozilla" (применительно к Debian), а промежуточные сертификаты присылает сам удостоверяющий центр по электронной почте. Но бывают и всякие неочевидные засады. Например, вот этот сертификат выглядит как CA, называется "GeoTrust Global CA", но на самом деле корневым не является. А настоящий корень — вот он. При этом у обоих одинаковый закрытый ключ, одинаковое поле "Subject Key Identifier", и вообще это братья-близнецы за исключением серийного номера. В качестве домашнего задания можете самостоятельно убедиться в этом. И кстати, вот ещё один Root CA с тем же самым закрытым ключом, но из другой цепочки. Как, не взорвался ещё мозг? Хе-хе, это называется X.509...

Наконец, мы собрали полный комплект. Пусть файлы называются так. "mycert.crt" — выданный нам сертификат для сервера, "myserver.key" — закрытый ключ от него, "intermediate1.crt" — первый промежуточный сертификат (которым подписан mycert.crt), "intermediate2.crt" — второй промежуточный сертификат (которым подписан intermediate1.crt), "rootca.crt" — корневой сертификат. При этом, повторяю, во всей цепочки должны совпадать "Authority Key Identifier" и "Subject Key Identifier" для каждой пары за исключением корневого (см. выше). В противном случае оно не соберётся.

Теперь из промежуточных и корневого сертификатов делаем bundle, она же "связка", она же "конкатенация". При этом важен порядок: от дочернего к родительскому.

cat intermediate1.crt intermediate2.crt rootca.crt > cabundle.crt

И OpenSSL-заклинание:

openssl pkcs12 -export -chain -inkey myserver.key -in mycert.crt -name "MySuperCert" -caname "Intermediate_1" -caname "Intermediate_2" -caname "Root_CA" -CAfile cabundle.crt -out mychain.p12

Первый важный момент. Обратите внимание на ключ "-chain". Без него это будет просто разрозненная пачка каких-то сертификатов в контейнере. А нам нужно, чтобы имела место быть явная связь между ними. Второй важный момент: параметр "-name". Это некое произвольное название, которое мы придумываем сами. Оно символизирует имя вашего "оконечного" сертификата и будет фигурировать на последующих шагах. Параметры "-caname", по аналогии, представляют так называемые "дружественные названия" (Friendly name) для остальных сертификатов в контейнере и должны идти в том же порядке, что и сами сертификаты в файле "cabundle.crt". В принципе, задавать "-caname" необязательно, разве что для красоты и удобочитаемости.

Общая идея такова. В "-in" и "-inkey" указываем наш целевой сертификат и закрытый ключ к нему соответственно. А в "-CAfile" должна лежать связка из всех промежуточных сертификатов, завершая корневым. Из соображений параноидальности перед проведением экспериментов лучше сделать "umask 077". И если OpenSSL вам в ответ выругается "Error unable to get local issuer certificate getting chain", значит вы положили какие-то "не те" сертфикаты в "cabundle.crt". Если же ему всё будет вкусно, то он попросит вас ввести какой-нибудь пароль для шифрования контейнера. Обязательно введите и запомните его. В моём примере я буду использовать в качестве такого пароля слово "secret".

Думаете, это конец? Нет, "стрижка только началась".

Переносим получившийся "mychain.p12" на машину с установленной Java, где есть утилита "keytool". Теперь нужно затащить PKCS12-контейнер в JKS. Но сперва придётся сделать маленькое лирическое отступление.

JKS — какой-то долбанутый формат. Он требует шифрования как всего контейнера (store) целиком (причём с паролем не менее шести символов), так и допускает шифрование отдельных элементов (ключей) внутри контейнера. И в общем случае эти пароли могут и не совпадать. При этом, вражеские ключи могут импортироваться в JKS-контейнер как по одному, так и всей толпой сразу. В первом варианте в ходе процедуры нам придётся указать то самое пресловутое "Friendly Name" (смотри параметр "-name" из заклинания "pkcs12 -export") и два пароля: для контейнера и для ключа. Во втором варианте ключи будут зашифрованы тем же самым паролем, что и в исходном материале. То есть, в нашем примере, пароль от закрытого ключа будет совпадать с паролем от PKCS12-контейнера, а пароль JKS-контейнера мы придумаем и зададим сами в процессе конвертации. Вынос мозга, правда? Лично я по некоторым причинам выбрал первый способ. Но можно пользоваться любым из них.

Итак, та-да-а-а-ам. Вот заклинание:

./keytool -importkeystore -destkeypass secret -deststorepass secret -destkeystore container.jks -srckeystore mychain.p12 -srcstoretype PKCS12 -srcstorepass secret -alias "MySuperCert"

Здесь "-destkeypass" — пароль от отдельно взятого ключа в JKS-контейнере (придумываем сами), "-deststorepass" — пароль от JKS-контейнера целиком (тоже придумываем), "-destkeystore" — имя файла с JKS-контейнером, "-srcstorepass" — пароль от PKCS12-контейнера, "-alias" — то самое Friendly Name нашего ключа, которое мы ранее задавали директивой "openssl -name". В моём примере я для простоты везде использовал пароль "secret", чего и вам советую. Один хрен, файл "container.jks" не должен быть world-readable и одновременно доступен нашему Java-приложению. Поэтому тут можно и не параноить.

Осталось совсем чуть-чуть. Протестировать получившийся контейнер командой "./keytool -keystore container.jks -list -v" и убедиться что там внутри есть всё, что мы туда положили. Скормить его нашей софтине. Если это HTTPS-сервер, то проверить его отклик откуда-нибудь снаружи заклинанием

openssl s_client -showcerts -connect supersite.mycompany.com:443

В ответ должен прилететь полный список сертификатов, включая промежуточные и корневой.

P.S. Техподдержка одной Javaписной софтинки предлагает свои услуги по инсталляции в своё творение предоставленного клиентом SSL-сертификата за 40 баксов. Стоимость выпуска самого сертификата оплачивается отдельно.

P.P.S. На написание этого псто ушло три часа. На то, чтобы разобраться, почему оно не работает, потребовалось два дня. ГОРЯЧО, ПЛАМЕННОЙ ЛЮБОВЬЮ ОБОЖАЮ ЖАВУ И ВСЁ ЧТО НА НЕЙ, СУКА, НАПИСАНО!!!!11

P.P.P.S. Извините, не сдержался, был рассержен.

Tags: it, java, openssl, x509, безопасность, криптография
Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 3 comments