Создание валидной цепочки SSL сертификатов
Для написания тестов на валидность загружаемых сертификатов мне потребовалось создать несколько вариантов цепочек и приватных ключей. Прошерстив некоторый объем интернета, я собрал всю необходимую информацию о том, как локально сгенерировать корректную цепочку SSL сертификатов, которая будет проходить проверку стандартными средствами.
Чтобы не писать кучу команд всякий раз, напишем bash-скрипт. Для начала, зададим несколько настроечных констант:
CSR_FILE="csrfile.csr"
KEY_BITS=2048
CONF_DIR="conf"
CHAIN_CRT="ca_chain.crt"
Затем пара функций для подготовки и последующей подчистки каталога с настройками:
function clean {
find conf -not -name '*.cnf' -type f -delete
rm -f $CSR_FILE $CHAIN_CRT
}
function prepare {
clean
echo 1000 > "$CONF_DIR/root_serial"
cp /dev/null "$CONF_DIR/root_index.txt"
echo 2000 > "$CONF_DIR/im_serial"
cp /dev/null "$CONF_DIR/im_index.txt"
}
prepare
Все готово к созданию фальшивой, но технически корректной пары корневой сертификат / приватный ключ:
ROOT_URL="mycorp.com"
ROOT_ORG="MYCORP"
ROOT_CNF="$CONF_DIR/root_openssl.cnf"
ROOT_CRT="root.crt"
ROOT_KEY="root.key"
ROOT_EXP=5000
ROOT_SUB="/C=RU/ST=Moscow/L=Moscow/O=$ROOT_ORG/CN=$ROOT_URL"
# Generate root key
openssl genrsa -out $ROOT_KEY $KEY_BITS
# Generate root certificate
openssl req -config $ROOT_CNF -new -x509 -sha256 -extensions v3_ca \
-key $ROOT_KEY -out $ROOT_CRT -days $ROOT_EXP -subj $ROOT_SUB
# Verify root certificate
openssl x509 -noout -text -in $ROOT_CRT
Хорошо, теперь создадим промежуточный сертификат с ключом:
IM_CRT="intermediate.crt"
IM_KEY="intermediate.key"
IM_CNF="$CONF_DIR/im_openssl.cnf"
IM_EXP=4000
IM_SUB="/C=RU/ST=Moscow/L=Moscow/O=${ROOT_ORG}/CN=department.${ROOT_URL}"
# Generate intermediate key
openssl genrsa -out $IM_KEY $KEY_BITS
# Generate intermediate request
openssl req -config $IM_CNF -new -key $IM_KEY -out $CSR_FILE -subj $IM_SUB
# Generate intermediate certificate
openssl ca \
-config $ROOT_CNF -batch \
-extensions v3_intermediate_ca -notext -md sha256 \
-days $IM_EXP -in $CSR_FILE -out $IM_CRT
# Verify intermediate certificate
openssl x509 -noout -text -in $IM_CRT
openssl verify -CAfile $ROOT_CRT $IM_CRT
Обратите внимание, что команда на создание промежуточного сертификата берет конфигурацию
корневого ($ROOT_CNF
), т.е. подписывать будем им. Также важно, чтобы срок подписываемого
был меньше срока подписывающего.
Ну а теперь, собственно, итог:
SERVER_CRT="server.crt"
SERVER_KEY="server.key"
SERVER_EXP=365
SERVER_SUB="/C=RU/ST=Moscow/L=Moscow/O=MYSERVER/CN=myserver.com"
# Generate key
openssl genrsa -out $SERVER_KEY $KEY_BITS
# Generate request
openssl req \
-config $IM_CNF -key $SERVER_KEY -new -sha256 \
-out $CSR_FILE -subj $SERVER_SUB
# Generate certificate
openssl ca \
-batch \
-config $IM_CNF -days $SERVER_EXP \
-extensions server_cert \
-notext -md sha256 \
-in $CSR_FILE -out $SERVER_CRT
# Verify certificate
cat $IM_CRT $ROOT_CRT > $CHAIN_CRT
openssl verify -CAfile $CHAIN_CRT $SERVER_CRT
Обратите внимение, что проверка осуществляется с указанием не только промежуточного,
но всех CA-сертификатов, предварительно собранных в один файл ($CHAIN_CRT
).
И файл запроса, и сертификат создаются с использованием конфигурации промежуточного сертификата.
Темным пятном тут осталось содержимое файлов конфигурации. Они очень похожи на стандартные конфигурации
openssl
, за исключением нескольких параметров, касающихся путей размещения служебных файлов и сертификатов для подписи.
Вот эти параметры:
[ CA_default ]
dir = conf
certs = $dir
crl_dir = $dir
new_certs_dir = $dir
Для корневого:
database = $dir/root_index.txt
serial = $dir/root_serial
private_key = $dir/../root.key
certificate = $dir/../root.crt
Для промежуточного:
database = $dir/im_index.txt
serial = $dir/im_serial
private_key = $dir/../intermediate.key
certificate = $dir/../intermediate.crt
Строго говоря, проверка корректности уже была произведена в процессе создания. Но так как мне нужно было делать это в Python, я использовал библиотеку pyOpenSSL, а именно модуль crypto. Для примера покажу, как это можно сделать без затей:
from OpenSSL import crypto
# Prepare X509 objects
root_cert = crypto.load_certificate(
crypto.FILETYPE_PEM, open('root.crt').read()
)
intermediate_cert = crypto.load_certificate(
crypto.FILETYPE_PEM, open('intermediate.crt').read()
)
server_cert = crypto.load_certificate(
crypto.FILETYPE_PEM, open('server.crt').read()
)
# Prepare X509 store
store = crypto.X509Store()
store.add_cert(root_cert)
store.add_cert(intermediate_cert)
# Verify
crypto.X509StoreContext(store, server_cert).verify_certificate()
Для X509Store
можно добавить
флаги проверок,
которые все сломают. К примеру, если добавить вот такой флаг, то получим исключение
OpenSSL.crypto.X509StoreContextError
:
store.set_flags(crypto.X509StoreFlags.CRL_CHECK)
А все потому, что будет предпринята попытка скачать CRL-файл, указанный в дополнениях сертификата,
и эта попытка провалится. Как с этим бороться, как добавить Issuer URL
и т.п. - это совсем другая история,
которая в принципе вся решается через конфигурацию openssl
.
Весь пример доступен на github