Держите корневой CA VPN офлайн и всё равно выпускайте сертификаты
Держите корневой ключ вне сети, подписывайте листовые сертификаты промежуточным CA, публикуйте CRL вовремя и проверяйте цепочку до подключения первого клиента.
Ваш VPN отвечает на подключения в 3 часа ночи, а машина, подписывающая его сертификаты, лежит выключенной в ящике. Клиент, проверяющий сертификат сервера, никогда не обращается к вашему CA, поэтому CA не обязан быть доступным ни для одного подключения. Часть, которая требует планирования, — это день, когда сертификат нужно выпустить, продлить или отозвать пока корневой ключ остаётся под замком.
Что на самом деле проверяет подключение
Когда клиент OpenVPN подключается, он проходит цепочку подписей от листового сертификата сервера вверх до сертификата CA, которому уже доверяет, проверяет окно действия и, если профиль так указывает, обращается к списку отзыва. Он никогда не ищет ваш корневой CA. Сервер делает зеркальное действие для клиентских сертификатов. В момент подключения присутствуют только лист, промежуточный сертификат и пакет доверия, который вы распространили раньше.
На самом деле не срабатывает одно из трёх: ключ подписи недоступен при выпуске, список отзыва устарел или пакет доверия клиента старше цепочки, которую представляет сервер. Первое — проблема процесса; остальные две — проблемы распространения. Ни одно из них не требует, чтобы корневой ключ был онлайн, а управляемый сервис VPN, требующий закрытый сертификат для аутентификации туннеля, лишь переносит полномочия подписи к провайдеру. Azure «точка — сайт» работает наоборот: вы загружаете корневой сертификат, и сертификаты, подписанные под ним, считаются доверенными, поэтому спрашивайте, кто подписывает, кто проверяет и как сертификат отзывается досрочно.
Постройте корень на хосте, который никогда не получает адрес
Офлайн-корень используется редко: чтобы подписать сертификат промежуточного CA, а позже продлить или отозвать его. Всё остальное происходит на уровень ниже. Выполняйте работу с корнем на машине, которая держится вне сети, по возможности из живой системы, и записывайте, кто был в комнате. Команды предполагают OpenSSL 1.1.1 или новее, потому что -addext нет в старых сборках.
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out root.key
openssl req -x509 -new -key root.key -sha256 -days 7300 \
-subj "/CN=Example VPN Root CA" -out root.crt \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign"
Создайте промежуточный сертификат на хосте подписи и отнесите только его запрос на офлайн-машину. Корень подписывает этот запрос с нулевой длиной пути, поэтому промежуточный CA может подписывать сертификаты конечных точек, но не может создать под собой другой CA. Это единственное расширение не даёт скомпрометированному хосту подписи создать полномочие, равное корневому. Используйте ECDSA только если каждый клиент работает на OpenVPN 2.4 или новее; иначе создайте ключи RSA и сохраните тот же поток.
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out intermediate.key
openssl req -new -key intermediate.key -subj "/CN=Example VPN Intermediate CA" -out intermediate.csr
openssl x509 -req -in intermediate.csr -CA root.crt -CAkey root.key \
-CAcreateserial -days 3650 -sha256 -extfile intermediate.ext -out intermediate.crt
intermediate.ext содержит basicConstraints=critical,CA:TRUE,pathlen:0, keyUsage=critical,keyCertSign,cRLSign и authorityKeyIdentifier=keyid:always. Верните корневой ключ в сейф до повторного подключения хоста подписи. Если корень должен уметь отзывать промежуточный сертификат, подпишите его с помощью openssl ca -config root.cnf -extensions intermediate_ca против index.txt корневого уровня вместо openssl x509 -req, чтобы серийный номер был записан; затем отправьте полученный корневой список в профиле клиента, где файловый crl-verify читает его при запуске. Отправляйте клиентам пакет с обоими сертификатами CA; один лист не проверится против корня.
Выпускайте листы из промежуточного CA, с базой данных
Подпись с помощью openssl x509 -req работает до тех пор, пока не понадобится отзыв, потому что ничто не записывает серийный номер. Используйте openssl ca с первого сертификата, чтобы серийный номер, срок и состояние отзыва жили в одной базе данных. Создайте файлы базы данных один раз, затем направьте на них конфигурацию:
mkdir -p /etc/vpn-ca/intermediate/newcerts
touch /etc/vpn-ca/intermediate/index.txt
echo 1000 > /etc/vpn-ca/intermediate/serial
echo 1000 > /etc/vpn-ca/intermediate/crlnumber
[ ca ]
default_ca = CA_default
[ CA_default ]
dir = /etc/vpn-ca/intermediate
database = $dir/index.txt
new_certs_dir = $dir/newcerts
serial = $dir/serial
crlnumber = $dir/crlnumber
certificate = $dir/intermediate.crt
private_key = $dir/intermediate.key
default_md = sha256
default_days = 365
default_crl_days = 30
unique_subject = no
policy = policy_any
[ policy_any ]
commonName = supplied
[ server_cert ]
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature
extendedKeyUsage = serverAuth
subjectAltName = @server_names
[ client_cert ]
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature
extendedKeyUsage = clientAuth
[ server_names ]
DNS.1 = vpn.example.com
Строка использования ключа предполагает ключ ECDSA; добавьте keyEncipherment для RSA. Дайте каждому клиенту собственное общее имя, потому что OpenVPN идентифицирует подключение по этому полю, и два ноутбука, разделяющие CN=vpn-client, не различить в журналах, в правилах client-config-dir или в любом push для пользователя, и вы теряете привязку, которая делает решение об отзыве защитимым. Выпустите и проверьте лист так:
openssl req -new -key alice.key -subj "/CN=alice-laptop" -out alice.csr
openssl ca -config intermediate.cnf -extensions client_cert -in alice.csr -out alice.crt -batch
openssl verify -CAfile ca-bundle.crt -untrusted intermediate.crt alice.crt
Держите ключ подписи за границей политики вместо копирования на хост VPN: небольшая служба, которая аутентифицирует запрос, проверяет общее имя по списку разрешённых и возвращает только сертификат. Записывайте каждый серийный номер; журнал — единственная связь между сертификатом и человеком, который его запросил.
Публикуйте отзыв, не будя корень
Клиентские сертификаты отзывает промежуточный CA, никогда корень. Корневой список важен только тогда, когда промежуточный CA сожжён. Направьте сервер на промежуточный список с помощью crl-verify. Файловый CRL загружается при запуске службы, поэтому перезагружайте после каждой регенерации; каталоговая форма читает состояние в момент подключения и стоит один файл на серийный номер.
Отзовите и перегенерируйте двумя командами из той же базы данных:
openssl ca -config intermediate.cnf -revoke /etc/vpn-ca/intermediate/newcerts/1001.pem
openssl ca -config intermediate.cnf -gencrl -out /etc/openvpn/crl.pem
Затем следите за часами. CRL несёт поле следующего обновления, и когда эта дата проходит, проверяющая сторона отклоняет список, а не игнорирует его. Ежемесячная регенерация с default_crl_days = 30 оставляет запас; ежегодная — нет. Более короткие сроки жизни листов — более надёжный контроль, потому что отзыв действует только там, где список проверяется.
Проверьте цепочку, затем запишите церемонию
Воспроизведите четыре отказа, прежде чем доверять схеме. Проверьте цепочку с помощью openssl verify -CAfile ca-bundle.crt -untrusted intermediate.crt server.crt. Подключитесь с просроченным клиентским сертификатом и убедитесь, что сервер его отклоняет. Отзовите тестового клиента, перезагрузите и убедитесь, что список действует. Переведите часы хоста на день вперёд, потому что неверные часы могут выпустить и отклонить сертификаты, которые в другом месте выглядят действительными.
Офлайн-корень — это контроль, только если есть бумаги: два названных человека для церемонии, запись каждого подписанного серийного номера, зашифрованная резервная копия, хранимая отдельно от машины, и тест восстановления на изолированном хосте. Подпишите запасной промежуточный CA во время той же церемонии, чтобы мёртвый хост подписи не заставил вас открывать корень в полночь.
Прежде чем назвать это готовым, подтвердите пять вещей: корневой ключ не на сетевом хосте, промежуточный ключ живёт только на хосте подписи, пакет клиента несёт оба сертификата CA, у CRL есть будущее следующее обновление, и у кого-то есть запись в календаре на каждый срок в цепочке. Если одного нет, конфигурация, которая выглядит офлайн, просто недокументирована.