Вчера, видимо, был шабаш https и клиенты стали массово слать сертификаты. Разумеется ни корневых ни промежуточных не прилагалось и просьба их выслать вызывала такое же недоумение как встречный поток у блондинки на дороге с односторонним движением.
На 4-м сертификате дёргать их вручную стало лень (а я ленив по натуре), поэтому набросал «самокат» выцепляющий издателя и формирующий chain-файл для скармливания nginx'у.
Наверняка он не идеален и проверен лишь на полуторадесятках сертификатов, но чем богаты.
Об устройстве x.509 много сказано (в том числе на хабре), поэтому повторяться не буду.
Ниже просто пошаговая инструкция получения цепочки вперемешку с небольшой выжимкой из теории и не более того.
Всё нижесказанное актуально для:
$ uname -or
FreeBSD 10.3-STABLE
$ openssl version
OpenSSL 1.0.2h 3 May 2016
$ `echo $SHELL` --version
tcsh 6.18.01 (Astron) 2012-02-14 (x86_64-amd-FreeBSD) options wide,nls,dl,al,kan,sm,rh,color,filec
$ /usr/local/bin/bash --version
GNU bash, version 4.3.25(1)-release (amd64-portbld-freebsd10.0)
Итак предположим, что у нас есть PEM-сертификат сайта. Для примера мы возьмём сертфикат ya.ru (не только ж пинговать его).
$ echo | openssl s_client -connect ya.ru:443 | openssl x509 -certopt ca_default -out ya.pem -outform PEM
Помимо самого кодированного запроса, версии, подписи и т.п. в нём имеется ряд расширений. Одно из которых Authority Information Access нас и интересует:
$ openssl x509 -in ./ya.pem -noout -text | grep 'Authority Information Access' -A 2
Authority Information Access:
OCSP - URI:http://yandex.ocsp-responder.com
CA Issuers - URI:http://repository.certum.pl/ycasha2.cer
Параметр CA Issuers как раз и содержит следующий в цепочке сертификат. Как правило, данный сертификат либо в PEM, либо в DER(как в нашем случае) форматах.
$ fetch http://repository.certum.pl/ycasha2.cer
На деле PEM формат не более чем base64 представление DER и получить PEM из DER можно сделав base64 ./ycasha2.cer ./ycasha2.pem и обрамив кодированный текст "-----BEGIN CERTIFICATE-----","-----END CERTIFICATE-----". Однако, логичнее и проще сделать это преобразование средствами openssl:
$ openssl x509 -inform der -in ./ycasha2.cer -out ./ycasha2.pem
Едем дальше и смотрим следующий сертификат в цепочке:
$ openssl x509 -in ./ycasha2.pem -noout -text | grep 'Authority Information Access' -A 2
Authority Information Access:
OCSP - URI:http://subca.ocsp-certum.com
CA Issuers - URI:http://repository.certum.pl/ctnca.cer
$ fetch http://repository.certum.pl/ctnca.cer
Преобразовываем и его:
$ openssl x509 -inform der -in ./ctnca.cer -out ./ctnca.pem
В данном сертификате (т.к. он корневой) отсутствует расширение Authority Information Access:
$ openssl x509 -in ./ctnca.pem -noout -text | grep 'X509v3 extensions' -A 6
X509v3 extensions:
X509v3 Basic Constraints: critical
CA:TRUE
X509v3 Subject Key Identifier:
08:76:CD:CB:07:FF:24:F6:C5:CD:ED:BB:90:BC:E2:84:37:46:75:F7
X509v3 Key Usage: critical
Certificate Sign, CRL Sign
То есть на нём и закончим вытягивание цепочки. Осталось собрать это всё в chain-файл:
$ cat ya.pem ycasha2.pem ctnca.pem > chain0.pem
Вроде бы теперь можно ставить (если есть Private Key), но остановлюсь ещё на паре нюансов.
Установив свой сертификат на свой Яндекс проверяем его:
$ echo | openssl s_client -connect ya.ru:443 -CApath . | grep Verify
Verify return code: 0 (ok)
Всё хорошо, но это лишь потому, что в дефолтных путях -CApath, -CAfile моего openssl нашлись нужные хеши сертификатов. Если мы их изменим, либо по дефолтным путям их нет, либо они просто устарели, либо у кого-то версия openssl с багом, в которой не «цеплялись» default CApath (если не ошибаюсь с 1.0.1с по 1.0.1e), то получим неприятность в виде:
$ echo | openssl s_client -connect ya.ru:443 -CApath . | grep Verify
Verify return code: 20 (unable to get local issuer certificate)
Понятно, что корневой сертификат подписать некому, поэтому нужно нашей системе разрешить доверять ему. Для этого можно создать кусочек хранилища. При поиске требуемого сертифката openssl пытается отыскать его по хешу сертификата.
$ openssl x509 -noout -hash -in ./ctnca.pem
48bec511
$ ln -s `pwd`/ctnca.pem `pwd`/48bec511.0
И теперь наша система доверяет ya.ru:
$ echo | openssl s_client -connect ya.ru:443 -CApath . | grep Verify
DONE
Verify return code: 0 (ok)
Разумеется делать руками каждый раз лень, потому слегка автоматизируем:
#!/usr/local/bin/bash
cmd_grep='/usr/bin/grep '
cmd_openssl='/usr/bin/openssl '
cmd_cut='/usr/bin/cut '
cmd_fetch='/usr/bin/fetch '
tmp_der='tmp.der'
tmp_cert='tmp.cert'
#------------------------------------------------------------------------------
usage () {
#printf "function ${FUNCNAME}n"
printf "Error!nUsage:t"$0 certificate.pem"n"
exit 1
}
#------------------------------------------------------------------------------
if [ "X$1" = "X" ]
then
usage
else
cp $1 $tmp_cert
chain_cert="chain.pem"
fi
i=0
while :
do
issuer=`$cmd_openssl x509 -in $tmp_cert -noout -text | $cmd_grep 'CA Issuers' | $cmd_cut -d : -f 2,3`
if [ "X$issuer" != "X" ]
then
echo $i
echo $issuer
tmp_pem=$1$i.pem
$cmd_fetch $issuer --output=$tmp_der
is_pem=`$cmd_grep -c CERTIFICATE $tmp_der`
printf "IS PEM:t[$is_pem]n"
#echo "$tmp_der -> $tmp_pem"
if [ $is_pem -ne 0 ]
then
echo "PEM($tmp_der) -> PEM($tmp_pem)"
cp -f $tmp_der $tmp_pem
else
echo "DER($tmp_der) -> PEM($tmp_pem)"
echo "$cmd_openssl x509 -inform der -in $tmp_der -out $tmp_pem"
$cmd_openssl x509 -inform der -in $tmp_der -out $tmp_pem
fi
cp $tmp_pem $tmp_cert
let "i+=1"
#sleep 2
else
break
fi
done
if [ $i -gt 0 ]
then
echo "cat ./$1* > $chain_cert"
cat ./$1* > $chain_cert
printf "Certificate chain:n"
ls -l $chain_cert
#ls | grep -Ev ^ya.pem$ | xargs rm
fi
Выполняем:
$ ./issuers.sh ./ya.pem
0
http://repository.certum.pl/ycasha2.cer
tmp.der 100% of 1196 B 16 MBps 00m00s
IS PEM: [0]
DER(tmp.der) -> PEM(./ya.pem0.pem)
/usr/bin/openssl x509 -inform der -in tmp.der -out ./ya.pem0.pem
1
http://repository.certum.pl/ctnca.cer
tmp.der 100% of 959 B 13 MBps 00m00s
IS PEM: [0]
DER(tmp.der) -> PEM(./ya.pem1.pem)
/usr/bin/openssl x509 -inform der -in tmp.der -out ./ya.pem1.pem
cat ././ya.pem* > chain.pem
Certificate chain:
-rw-r--r-- 1 root wheel 5842 Jun 30 15:46 chain.pem
Сверяем показания:
$ md5 chain0.pem ; md5 chain.pem
MD5 (chain0.pem) = 6d32b0798d48d14764cd26cc4f730444
MD5 (chain.pem) = 6d32b0798d48d14764cd26cc4f730444
Как-то так… Разумеется скрипт не универсален, всё на скорую руку в предверии грандиозного шухера. Комментарии/пожелания приветствуются, но отвечать вряд ли смогу — у нас тут (в Беларуси) дурдом деноминация.
Автор: simpleadmin