Открыть список
Как стать автором
Обновить

Комментарии 31

Ну, во-первых, с openssl поставляются скрипты, делающие почти всю черную работу за вас.

В файле конфигурации сертификатов (как правило, файл называется openssl.cnf, в вашем случае ca.conf, кстати, тоже непонятно, зачем придумывать стандартным конфигам свои имена файлов) была отмечена опция default_crl_days = 7 как Срок действия CRL. Но, при этом, остальные опции CRL отсутствуют (например, директория отозванных сертификатов, следующий порядковый номер отзыва) и, вообще в статье не отмечена очень важная возможность отзывов сертификатов.

Возможно (тьфу-тьфу), если из вашей компании кто-нибудь когда-нибудь уволится, не настроив openssl с самого начала, у вас возникнут проблемы. Предлагаю вам разобраться в CRL и дополнить статью!

Насколько я помню, этот вопрос на этапе отладки действительно был упущен, но через несколько месяцев его актуальность стала очевидной. Статья была написана до этого, а сейчас я просто позабыл. CRL нужен и я постараюсь найти недостающие части конфига и добавить их в статью. Пока перемещу в черновики.

Спасибо за внимательность!
Дописать в том же духе за давностью лет тяжело, поэтому добавлю в комментарии, а не в основной текст:

в nginx можно дописать опцию проверки отозванных сертификатов, выглядит она вот так:
ssl_crl /path/to/nginx/conf/crl.pem;


Как сгенерировать файл с отозванными сертификатами статьи уже есть и не одна.
Ну вы просто не забудьте отметить этот тонкий момент. Потому что про него все забывают и нигде заранее не пишут. Я не пользовался Ubuntu, осваивал openssl на FreeBSD 6 по исходникам. А первый раз получил задание от директора отозвать сертификат менеджера за то время, пока тот не добежит бегом до компьютера, чтобы не слить клиентскую БД. Обычно, в случаях отзывов сертификатов, счет идет на секунды! Важно готовить скрипты заранее, чтобы можно было из любого окна терминала ограничить доступ любому сертификату.

А если вы не подготовите команды или скрипты заранее, возможна и такая ситуация, когда надо будет переделывать все сертификаты из-за того, что не могли отозвать один.
К сожалению, тут у Nginx есть небольшая проблема — вы не сможете сделать в рамках одного блока server одновременно открытую (не требующую проверки сертификатов) и закрытую (с проверкой) часть сайта или интерфейса. И разработчики это менять не собираются.
В таком случае нужно делать SSL-авторизуемую часть или на другом домене, или открытую часть делать вовсе без SSL или вообще ставить Apache (он такое умеет).
На форумах nginx'а есть workaround. ssl_verify_client выставляется в optional и в нужных location проверяется переменная $ssl_client_verify, см ответ Игоря (http://forum.nginx.org/read.php?29,173747,173809#msg-173809). Но, к сожалению, костыли.
Надо проверять, конечно, но при такой настройке браузер может быть будет спрашивать сертификат у пользователя при первом же посещении даже на открытой странице (например, на главной). Это может быть нежелательно.
Будет, если есть сертификаты, подписанные теми CA, которые указаны в рамках секции Certificate Request в server hello.

Т. е., если у пользователя нет сертификатов, подписанных теми CA, которые указаны в ssl_client_certificate, то в firefox и chrome (по крайней мере, на современных) пользователь не получит запроса на указание сертификата. Иначе — попросят указать сертификат (или отказаться, но тогда для входа по сертификату придется перезапустить браузер или зайти в порно-режиме).

Потому и говорю, что костыли.
Подскажите, а как сравниваются CA? по хешу ключа или текстовым данным?
Большинство CA генерируется по одной и той-же статье. Есть ли шанс что у постороннего человека с ключами для другого сайта будет выскакивать окошко на нашем?

Смотрим в https://tools.ietf.org/html/rfc5246#section-7.4.4 и видим, что в CertificateRequest отправляется набор DNов CA. Так что если DN вашего самоподписанного CA будет тупо /CN=Example CA или /CN=Root CA, то проблемы будут.


Название distinguished name кагбэ говорит, что оно должно быть отличным от других (уникальным).

сколько ни пытался — та же фигня, 400 No required SSL certificate was sent
Ну так надо добить — отправить валидный сертификат :)
Подозреваю, ошибочка то в браузере выдана была, а браузер точно свой сертификат видел? :)
То есть самый просто вариант — curl, как у автора. Для проверки и минимизации ошибок — самое то.
Нашел статью, сделал все по шагам но браузер пишет «No required SSL certificate was sent».
Задача — поднять сайт с https на локалхосте (чисто в целях обучения).

А через curl работает? Корневой сертификат, которым подписан клиентский сертификат, добавлен в конфиг nginx как опция ssl_trusted_certificate/ssl_client_certificate?

Через curl тоже самое выдает.
Пошаговая инструкция это хорошо, но я просто не понимаю как все это работает и какой из 14 сгенерированных файлов за что отвечает… надо найти что-то еще почитать:)

Промазал и ответил ниже.

Вот пошаговый мануал: по факту нужно не 14 файлов, а меньше: ключевая пара удостоверяющего центра, ключевая пара сервера, ключевая пара клиента и конфиг Nginx. 7 :-)


  1. Создание сертификатов УЦ


    openssl genrsa -out ca.key 2048
    openssl req -new -x509 -days 3650 -key ca.key -out ca.crt

  2. Создание сертификата сервера


    1. Конфигурационный файл server.cnf, пример:


      [req]
      prompt = no
      distinguished_name = dn
      req_extensions = ext
      
      [dn]
      CN = Сервер с аутентификацией клиентов
      emailAddress = my.email@example.com
      O = Private person
      OU = Alter ego dept
      L = Korolyov
      C = RU
      
      [ext]
      subjectAltName = DNS:example.com,DNS:*.example.com,IP:127.0.0.1

      В subjectAltName должны быть указаны все используемые DNS-имена и IP-адреса.


    2. Создание запроса на сертификат


      openssl req -new -utf8 -nameopt multiline,utf8 -config server.cnf -newkey rsa:2048 -keyout server.key -nodes -out server.csr

    3. Создание сертификата


      openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt -extfile server.cnf -extensions ext

    4. Настройка Nginx, пример (фрагмент)


      http {
        ssl_certificate         /path/to/server.crt;
        ssl_certificate_key     /path/to/server.key;
        ssl_client_certificate  /path/to/ca.crt;
        ssl_verify_client       on;
      
        server {
          listen       8443 ssl default_server;
          listen       [::]:8443 ssl default_server;
        }
      }


  3. Создание сертификата клиента


    openssl req -new -utf8 -nameopt multiline,utf8 -newkey rsa:2048 -nodes -keyout client.key -out client.csr
    openssl x509 -req -days 3650 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt

  4. Проверка


    Следующая команда должна отрабатывать без ошибок:


    curl -v --cacert path/to/ca.crt --cert path/to/client.crt --key path/to/client.key https://127.0.0.1:8443/

    Следующая команда должна возвращать ошибку «не предоставлен сертификат клиента»:


    curl -v --cacert path/to/ca.crt https://192.168.194.97:8443/

  5. Добавление сертификата ЦА в RHEL (от имени root, где path/to/ca.crt — путь к файлу корневого сертификата)


    cp path/to/ca.crt /etc/pki/ca-trust/source/anchors/
    update-ca-trust


Последний пункт нужен, чтобы можно было не указывать явно сертификат ЦА в командах (в том же cURL).

Одна маленькая ремарка: лучше в серверном CN указывать хост. В SAN он тоже должен присутствовать, естественно.

Вот уж пару лет развлекаюсь тем, что пихаю в CN надписи на русском (в UTF-8) — пока проблем ни с каким софтом не возникало — все смотрят в SubjectAltNames.
Впрочем, раньше я это делал чисто из эстетических соображений — чтобы видеть эти русские буквы в информации о сертификате, сейчас же, когда в хроме надо пройти целый квест, чтобы эту информацию увидеть, большая часть смысла пропала :-)

Спасибо! Все тесты с curl проходят нормально, а через браузер все равно не работает.
Система Lubuntu, поэтому добавление сертификата не такое как в пункте 5, там просто нет ca-trust внутри /etc/pki и команды update-ca-trust.
Делал так (в целом это похожие варианты)
http://manuals.gfi.com/en/kerio/connect/content/server-configuration/ssl-certificates/adding-trusted-root-certificates-to-the-server-1605.html
https://askubuntu.com/questions/73287/how-do-i-install-a-root-certificate
https://unix.stackexchange.com/questions/90450/adding-a-self-signed-certificate-to-the-trusted-list

А вы добавляли корневой сертификат в браузер? Т.е. зелёный замочек в браузере есть? Дело может быть в этом. См. https://habrahabr.ru/post/213741/#comment_7814423, например.

Нет, я думал браузер из линукса сам возьмет.
Но добавление не помогло: теперь браузер опять выдает 400 Bad Request No required SSL certificate was sent
В общем директива ssl_verify_client = off; помогла. Но я совсем не уверен что это правильно и не знаю можно ли так делать на реальном сервере…

В смысле "помогла"? Вы просто выключили эту фичу IIRC

Да, это понятно что выключил… непонятно в чем там кроется собака:) Я уже пересоздал сертификаты, все параметры ввел в cnf и при вводе в консоль одинаковые буква в букву. Есть вот такое обсуждение https://forum.nginx.org/read.php?21,182573,183073#msg-183073 но там предлагают ставить патч.

Но я бы сначала хотел в принципе разобраться что происходит в реальной жизни (когда юзер заходит на сайт по https) и что происходит под локалхолстом с самоподписанным сертификатом.
Как я понимаю, «ca» — это сертификат удостоверяющего центра (в реальной системе у меня его не будет, а будут вшитые в браузер); «server» — это сертификат сервера, то что мне выдадут например на let's encrypt для моего домена. А что такое сертификат клиента «client»? Откуда он берется в реальных условиях (браузер открывает https ссылку) и почему мы его генерируем для локалхоста?

Если на пальцах, то в случае использования клиентской аутентификации по tls есть две ветки PKI.


  1. Сторона сервера (которая есть и без клиентской authN), которая включает root CA, intermediate CAs и серверный сертификат. При подключении клиента к серверу при установлении соединения сервер отдаёт свой сертификат с реверсной цепочкой не включая CA (т. к. он есть у пользователя). Файл с этими сертификатами в nginx указывается в параметре ssl_certificate.
  2. Сторона клиента имеет независимую ветку PKI (которая может спокойно использовать приватный CA). При включенной ssl_verify_client (необходимо также указать ssl_client_certificate, если используется on или optional) сервер после ServerHello передаёт дополнительно фрейм Certificate Request, в котором перечислены dn'ы CA которым данный endpoint доверяет в контексте аутентификации клиента (в случае использования optional_no_ca этот список пустой). Клиент на этот запрос предоставляет сертификат (в случае браузера показывает окошко, где пользователь выбирает какой сертификат использовать).

И первая ветка PKI (серверная) в нормальном случае получается через публичный CA типа LetsEncrypt. Сторона же пользователя, в зависимости от сервиса может получаться:


  • от публичного или государственного CA,
  • от приватного CA самого сервиса (который, по сути, в статье и описан).

Если хочется разобраться, то рекомендую взять для начала wireshark, открыть rfc5246, выполнить описанное в 3 части статьи.


Я для тестов использовал сейчас более простую конфигурацию:


пример
# генерируем ключ
openssl genrsa -out ca.pem 1024
# генерируем самоподписанный сертификат
openssl req -x509 -subj '/CN=test ca' -days 365 -key ca.pem -out ca.crt
# смотрим глазами на самоподписанный (root) CA
openssl x509 -in ca.crt -noout -text

# генерируем клиентский ключ
openssl genrsa -out client.pem 1024
# генерируем csr и приносим его на машину с CA
openssl req -key client.pem -new -out client.csr -subj '/CN=client'

# генерируем клиентский сертификат на основе csr
openssl x509 -req -in client.csr -out client.crt -CA ca.crt -CAkey ca.pem -CAcreateserial
# смотрим на него
openssl x509 -in client.crt -noout -text
# собираем на машине с ключом pkcs12 контейнер с ключом и сертификатом для браузера
openssl pkcs12 -export -out client.p12 -in client.crt -inkey client.pem

# !!! импортируем client.p12 в браузер
# прописываем в уже работающем по https nginx'е `ssl_client_verify` on, optional или optional_no_ca.  в первых двух случаях не забываем притащить nginx'у ca.crt и прописать в `ssl_client_certificate`

# пользуемся

Для отладки ssl/tls chrome (и, вроде, ff) поддерживают переменную окружения SSLKEYLOGFILE, в которой указывается файл куда сохранять сессионные ключи. Эту переменную можно использовать только при отладке. При наличии этого файла кто угодно может расшифровать содержание SSL/TLS сессий (в том числе при использовании PFS).


Я использую следующий скрипт-обёртку:


#!/bin/bash

export SSLKEYLOGFILE=~/.cache/tls-premaster 

google-chrome-stable --user-data-dir=/tmp/tls-debug --incognito
rm -rf /tmp/tls-debug
shred -u ~/.cache/tls-premaster

Путь к этому файлу нужно указать wireshark'у в preferences -> protocols -> ssl, пункт (Pre)-Master-Secret log filename.

Вот ещё хорошая статья, если хочется знать, что там под капотом и как работает: https://tls.dxdt.ru/tls.html


Ещё могу посоветовать стэнфордский курс Crypto на Coursera — хорошо объясняют про криптографию в целом (но уже чёрт знает сколько лет не могут выпустить вторую часть курса).

3.5 Подключение к полученному ssl cерверу с помощью curl

curl -k --key client.key --cert client1.crt --url "https://site.com"

Использована опция -k, потому что сертификат в примере самоподписанный


зашёл сказать, что не надо игнорировать проверку валидности (тот самый ключ -k), но в комментируемом комменте уже есть правильный рецепт:
curl ... --cacert path/to/ca.crt
на что и хотел бы обратить внимание
а известно как аутентифицировать пользователя по его (клиентскому) сертификату?
Т.е. подписали мы клиентских сертификатов, сервер их пускает, но нам нужно определить кто есть кто

в IIS, знаю, это настраивается так
есть что-то подобное для nginx?

Когда аутентификация клиента проходит успешно, то Nginx заполняет несколько переменных данными о клиентском сертификате: https://nginx.org/en/docs/http/ngx_http_ssl_module.html#variables
Эти переменные уже можно передать в заголовках приложению и оно там будет искать клиента у себя. Вам скорее всего нужен $ssl_client_escaped_cert, $ssl_client_s_dn или $ssl_client_serial

благодарю!
очевидно, это то, что нужно
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.