Дёргаем цепочку сертификатов

в 13:26, , рубрики: freebsd, openssl, SSL, x509, системное администрирование

Вчера, видимо, был шабаш 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)

Разумеется делать руками каждый раз лень, потому слегка автоматизируем:

$ cat ./issuers.sh

#!/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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js