Edit: I had also this «problem», solution and explanation is at the bottom of the text.
It seemed like nginx doesn’t support intermediate certificates. My certs self created: (RootCA is selfsigned, IntrermediateCA1 is signed by RootCA, etc.)
RootCA -> IntermediateCA1 -> Client1
RootCA -> IntermediateCA2 -> Client2
I want to use in nginx «IntermediateCA1», to allow access to site only to owner of the «Client1» certificate.
When I put to «ssl_client_certificate» file with IntermediateCA1 and RootCA, and set «ssl_verify_depth 2» (or more) , clients can login to site both using certificate Client1 and Client2 (should only Client1).
The same result is when I put to «ssl_client_certificate» file with only RootCA — both clients can login.
When I put to «ssl_client_certificate» file with only IntermediateCA1, and set «ssl_verify_depth 1» (or «2» or more — no matter) , it is imposible to log in, I get error 400. And in debug mode i see logs:
verify:0, error:20, depth:1, subject:"/C=PL/CN=IntermediateCA1/emailAddress=cert@asdf.com",issuer: "/C=PL/CN=RootCA/emailAddress=cert@asdf.com"
verify:0, error:27, depth:1, subject:"/C=PL/CN=IntermediateCA1/emailAddress=cert@asdf.com",issuer: "/C=PL/CN=RootCA/emailAddress=cert@asdf.com"
verify:1, error:27, depth:0, subject:"/C=PL/CN=Client1/emailAddress=cert@asdf.com",issuer: "/C=PL/CN=IntermediateCA1/emailAddress=cert@asdf.com"
(..)
client SSL certificate verify error: (27:certificate not trusted) while reading client request headers, (..)
I thing this is a bug. Tested on Ubuntu, nginx 1.1.19 and 1.2.7-1~dotdeb.1, openssl 1.0.1.
I see that nginx 1.3 has few more options about using client certificates, but I’dont see solution to this problem.
Currently, the only one way to separate clients 1 and 2 is to create two, selfsigned RootCAs, but this is only workaround..
Edit 1:
I’ve reported this issue here: http://trac.nginx.org/nginx/ticket/301
Edit 2″
*Ok, it’s not a bug, it is feature *
I get response here: http://trac.nginx.org/nginx/ticket/301
It is working, you must only check what your ssl_client_i_dn is (. Instead of issuer you can use also subject of certificate, or what you want from http://wiki.nginx.org/HttpSslModule#Built-in_variables
This is how certificate verification works: certificate must be
verified up to a trusted root. If chain can’t be built to a trusted
root (not intermediate) — verification fails. If you trust root — all
certificates signed by it, directly or indirectly, will be
successfully verified.Limiting verification depth may be used if you
want to limit client certificates to a directly issued certificates
only, but it’s more about DoS prevention, and obviously it can’t be
used to limit verificate to intermediate1 only (but not
intermediate2).What you want here is some authorization layer based
on the verification result — i.e. you may want to check that client’s
certificate issuer is intermediate1. Simplest solution would be to
reject requests if issuer’s DN doesn’t match one allowed, e.g.
something like this (completely untested):
[ Edit by me, it is working correctly in my configuration ]
server {
listen 443 ssl;
ssl_certificate ...
ssl_certificate_key ...
ssl_client_certificate /path/to/ca.crt;
ssl_verify_client on;
ssl_verify_depth 2;
if ($ssl_client_i_dn != "/C=PL/CN=IntermediateCA1/emailAddress=cert@asdf.com") {
return 403;
}
}
Содержание
- nginx проблемы с авторизацией по сертификату
- Может кому пригодится
- Модуль ngx_http_ssl_module
- Пример конфигурации
- Директивы
- Обработка ошибок
- Встроенные переменные
nginx проблемы с авторизацией по сертификату
nginx 1.6.2, самоподписанные сертификаты сервера и клиента, openssl проверку проходят:
А при запросе nginx отвечает ошибкой 400 The SSL certificate error
В логах сервера:
Я правильно понимаю, что серверу не нравится то, что сертификат самоподписанный? Как разрешить?
Вот это результата не дало:
Работает с ssl_verify_client optional_no_ca; , но результат не тот, что хотелось бы $ssl_client_verify=’FAILED’
Есть возможность получить сертификат от LetsEncrypt?
Перепроверь сертификаты: клиентский должен быть подписан ключом CA:
Если считаешь, что всё ок — покажи их.
Раньше не мог ответить. Решил проблему еще в пятницу, пересоздав сертифкаты.
Может кому пригодится
До этого момента подразумевается, что у вас уже есть nginx с fast cgi и mod ssl и вам нужно исправить (400 Bad Request No required SSL certificate was sent) и ошибки подобные (error 18 at 0 depth lookup:self signed certificate OK ) и ошибок с не достоверным сертификатом без доступа к ресурсу (NET::ERR_CERT_INVALID).
Источник
Модуль ngx_http_ssl_module
Модуль ngx_http_ssl_module обеспечивает работу по протоколу HTTPS.
По умолчанию этот модуль не собирается, его сборку необходимо разрешить с помощью конфигурационного параметра —with-http_ssl_module .
Для сборки и работы этого модуля нужна библиотека OpenSSL.
Пример конфигурации
Для уменьшения загрузки процессора рекомендуется
- установить число рабочих процессов равным числу процессоров,
- разрешить keep-alive соединения,
- включить разделяемый кэш сессий,
- выключить встроенный кэш сессий
- и, возможно, увеличить время жизни сессии (по умолчанию 5 минут):
Директивы
Синтаксис: | ssl on | off ; |
---|---|
Умолчание: | |
Контекст: | http , server |
Эта директива устарела в версии 1.15.0. Вместо неё следует использовать параметр ssl директивы listen.
Синтаксис: | ssl_buffer_size size ; |
---|---|
Умолчание: | |
Контекст: | http , server |
Эта директива появилась в версии 1.5.9.
Задаёт размер буфера, используемого при отправке данных.
По умолчанию размер буфера равен 16k, что соответствует минимальным накладным расходам при передаче больших ответов. С целью минимизации времени получения начала ответа (Time To First Byte) может быть полезно использовать меньшие значения, например:
Синтаксис: | ssl_certificate файл ; |
---|---|
Умолчание: | — |
Контекст: | http , server |
Указывает файл с сертификатом в формате PEM для данного виртуального сервера. Если вместе с основным сертификатом нужно указать промежуточные, то они должны находиться в этом же файле в следующем порядке: сначала основной сертификат, а затем промежуточные. В этом же файле может находиться секретный ключ в формате PEM.
Начиная с версии 1.11.0 эта директива может быть указана несколько раз для загрузки сертификатов разных типов, например RSA и ECDSA:
Возможность задавать отдельные цепочки сертификатов для разных сертификатов есть только в OpenSSL 1.0.2 и выше. Для более старых версий следует указывать только одну цепочку сертификатов.
Начиная с версии 1.15.9 в имени файла можно использовать переменные при использовании OpenSSL 1.0.2 и выше:
Однако нужно учитывать, что при использовании переменных сертификат загружается при каждой операции SSL handshake, что может отрицательно влиять на производительность.
Вместо файла можно указать значение data : $переменная (1.15.10), при котором сертификат загружается из переменной без использования промежуточных файлов. При этом следует учитывать, что ненадлежащее использование подобного синтаксиса может быть небезопасно, например данные секретного ключа могут попасть в лог ошибок.
Нужно иметь в виду, что из-за ограничения протокола HTTPS для максимальной совместимости виртуальные серверы должны слушать на разных IP-адресах.
Синтаксис: | ssl_certificate_key файл ; |
---|---|
Умолчание: | — |
Контекст: | http , server |
Указывает файл с секретным ключом в формате PEM для данного виртуального сервера.
Вместо файла можно указать значение engine : имя : id (1.7.9), которое загружает ключ с указанным id из OpenSSL engine с заданным именем .
Вместо файла можно указать значение data : $переменная (1.15.10), при котором секретный ключ загружается из переменной без использования промежуточных файлов. При этом следует учитывать, что ненадлежащее использование подобного синтаксиса может быть небезопасно, например данные секретного ключа могут попасть в лог ошибок.
Начиная с версии 1.15.9 в имени файла можно использовать переменные при использовании OpenSSL 1.0.2 и выше.
Синтаксис: | ssl_ciphers шифры ; |
---|---|
Умолчание: | |
Контекст: | http , server |
Описывает разрешённые шифры. Шифры задаются в формате, поддерживаемом библиотекой OpenSSL, например:
Полный список можно посмотреть с помощью команды “ openssl ciphers ”.
В предыдущих версиях nginx по умолчанию использовались другие шифры.
Синтаксис: | ssl_client_certificate файл ; |
---|---|
Умолчание: | — |
Контекст: | http , server |
Указывает файл с доверенными сертификатами CA в формате PEM, которые используются для проверки клиентских сертификатов и ответов OCSP, если включён ssl_stapling.
Список сертификатов будет отправляться клиентам. Если это нежелательно, можно воспользоваться директивой ssl_trusted_certificate.
Синтаксис: | ssl_conf_command имя значение ; |
---|---|
Умолчание: | — |
Контекст: | http , server |
Эта директива появилась в версии 1.19.4.
Задаёт произвольные конфигурационные команды OpenSSL.
Директива поддерживается при использовании OpenSSL 1.0.2 и выше.
На одном уровне может быть указано несколько директив ssl_conf_command :
Директивы наследуются с предыдущего уровня конфигурации при условии, что на данном уровне не описаны свои директивы ssl_conf_command .
Следует учитывать, что изменение настроек OpenSSL напрямую может привести к неожиданному поведению.
Синтаксис: | ssl_crl файл ; |
---|---|
Умолчание: | — |
Контекст: | http , server |
Эта директива появилась в версии 0.8.7.
Указывает файл с отозванными сертификатами (CRL) в формате PEM, используемыми для проверки клиентских сертификатов.
Синтаксис: | ssl_dhparam файл ; |
---|---|
Умолчание: | — |
Контекст: | http , server |
Эта директива появилась в версии 0.7.2.
Указывает файл с параметрами для DHE-шифров.
По умолчанию параметры не заданы, и соответственно DHE-шифры не будут использоваться.
До версии 1.11.0 по умолчанию использовались встроенные параметры.
Синтаксис: | ssl_early_data on | off ; |
---|---|
Умолчание: | |
Контекст: | http , server |
Эта директива появилась в версии 1.15.3.
Разрешает или запрещает TLS 1.3 early data.
Запросы, отправленные внутри early data, могут быть подвержены атакам повторного воспроизведения (replay). Для защиты от подобных атак на уровне приложения необходимо использовать переменную $ssl_early_data.
Директива поддерживается при использовании OpenSSL 1.1.1 и выше (1.15.4) или BoringSSL.
Синтаксис: | ssl_ecdh_curve кривая ; |
---|---|
Умолчание: | |
Контекст: | http , server |
Эта директива появилась в версиях 1.1.0 и 1.0.6.
Задаёт кривую для ECDHE-шифров.
При использовании OpenSSL 1.0.2 и выше можно указывать несколько кривых (1.11.0), например:
Специальное значение auto (1.11.0) соответствует встроенному в библиотеку OpenSSL списку кривых для OpenSSL 1.0.2 и выше, или prime256v1 для более старых версий.
До версии 1.11.0 по умолчанию использовалась кривая prime256v1 .
При использовании OpenSSL 1.0.2 и выше директива задаёт список кривых, поддерживаемых сервером. Поэтому для работы ECDSA-сертификатов важно, чтобы список включал кривые, используемые в сертификатах.
Синтаксис: | ssl_ocsp on | off | leaf ; |
---|---|
Умолчание: | |
Контекст: | http , server |
Эта директива появилась в версии 1.19.0.
Включает проверку OCSP для цепочки клиентских сертификатов. Параметр leaf включает проверку только клиентского сертификата.
Для работы проверки OCSP необходимо дополнительно установить значение директивы ssl_verify_client в on или optional .
Для преобразования имени хоста OCSP responder’а в адрес необходимо дополнительно задать директиву resolver.
Синтаксис: | ssl_ocsp_cache off | [ shared : имя : размер ]; |
---|---|
Умолчание: | |
Контекст: | http , server |
Эта директива появилась в версии 1.19.0.
Задаёт имя и размер кэша, который хранит статус клиентских сертификатов для проверки OCSP-ответов. Кэш разделяется между всеми рабочими процессами. Кэш с одинаковым названием может использоваться в нескольких виртуальных серверах.
Параметр off запрещает использование кэша.
Синтаксис: | ssl_ocsp_responder url ; |
---|---|
Умолчание: | — |
Контекст: | http , server |
Эта директива появилась в версии 1.19.0.
Переопределяет URL OCSP responder’а, указанный в расширении сертификата “Authority Information Access” для проверки клиентских сертификатов.
Поддерживаются только “ http:// ” OCSP responder’ы:
Синтаксис: | ssl_password_file файл ; |
---|---|
Умолчание: | — |
Контекст: | http , server |
Эта директива появилась в версии 1.7.3.
Задаёт файл с паролями от секретных ключей, где каждый пароль указан на отдельной строке. Пароли применяются по очереди в момент загрузки ключа.
Синтаксис: | ssl_prefer_server_ciphers on | off ; |
---|---|
Умолчание: | |
Контекст: | http , server |
Указывает, чтобы при использовании протоколов SSLv3 и TLS серверные шифры были более приоритетны, чем клиентские.
Синтаксис: | ssl_protocols [ SSLv2 ] [ SSLv3 ] [ TLSv1 ] [ TLSv1.1 ] [ TLSv1.2 ] [ TLSv1.3 ]; |
---|---|
Умолчание: | |
Контекст: | http , server |
Разрешает указанные протоколы.
Параметры TLSv1.1 и TLSv1.2 (1.1.13, 1.0.12) работают только при использовании OpenSSL 1.0.1 и выше.
Параметр TLSv1.3 (1.13.0) работает только при использовании OpenSSL 1.1.1 и выше.
Синтаксис: | ssl_reject_handshake on | off ; |
---|---|
Умолчание: | |
Контекст: | http , server |
Эта директива появилась в версии 1.19.4.
Если разрешено, то операции SSL handshake в блоке server будут отклонены.
Например в этой конфигурации отклоняются все операции SSL handshake с именем сервера, отличным от example.com :
Синтаксис: | ssl_session_cache off | none | [ builtin [: размер ]] [ shared : название : размер ]; |
---|---|
Умолчание: | |
Контекст: | http , server |
Задаёт тип и размеры кэшей для хранения параметров сессий. Тип кэша может быть следующим:
off жёсткое запрещение использования кэша сессий: nginx явно сообщает клиенту, что сессии не могут использоваться повторно. none мягкое запрещение использования кэша сессий: nginx сообщает клиенту, что сессии могут использоваться повторно, но на самом деле не хранит параметры сессии в кэше. builtin встроенный в OpenSSL кэш, используется в рамках только одного рабочего процесса. Размер кэша задаётся в сессиях. Если размер не задан, то он равен 20480 сессиям. Использование встроенного кэша может вести к фрагментации памяти. shared кэш, разделяемый между всеми рабочими процессами. Размер кэша задаётся в байтах, в 1 мегабайт может поместиться около 4000 сессий. У каждого разделяемого кэша должно быть произвольное название. Кэш с одинаковым названием может использоваться в нескольких виртуальных серверах. Также он используется для автоматического создания, хранения и периодического обновления ключей TLS session tickets (1.23.2), если они не указаны явно с помощью директивы ssl_session_ticket_key.
Можно использовать одновременно оба типа кэша, например:
однако использование только разделяемого кэша без встроенного должно быть более эффективным.
Синтаксис: | ssl_session_ticket_key файл ; |
---|---|
Умолчание: | — |
Контекст: | http , server |
Эта директива появилась в версии 1.5.7.
Задаёт файл с секретным ключом, применяемым при шифровании и расшифровании TLS session tickets. Директива необходима, если один и тот же ключ нужно использовать на нескольких серверах. По умолчанию используется случайно сгенерированный ключ.
Если указано несколько ключей, то только первый ключ используется для шифрования TLS session tickets. Это позволяет настроить ротацию ключей, например:
Файл должен содержать 80 или 48 байт случайных данных и может быть создан следующей командой:
В зависимости от размера файла для шифрования будет использоваться либо AES256 (для 80-байтных ключей, 1.11.8), либо AES128 (для 48-байтных ключей).
Синтаксис: | ssl_session_tickets on | off ; |
---|---|
Умолчание: | |
Контекст: | http , server |
Эта директива появилась в версии 1.5.9.
Разрешает или запрещает возобновление сессий при помощи TLS session tickets.
Синтаксис: | ssl_session_timeout время ; |
---|---|
Умолчание: | |
Контекст: | http , server |
Задаёт время, в течение которого клиент может повторно использовать параметры сессии.
Синтаксис: | ssl_stapling on | off ; |
---|---|
Умолчание: | |
Контекст: | http , server |
Эта директива появилась в версии 1.3.7.
Разрешает или запрещает прикрепление OCSP-ответов сервером. Пример:
Для работы OCSP stapling’а должен быть известен сертификат издателя сертификата сервера. Если в заданном директивой ssl_certificate файле не содержится промежуточных сертификатов, то сертификат издателя сертификата сервера следует поместить в файл, заданный директивой ssl_trusted_certificate.
Для преобразования имени хоста OCSP responder’а в адрес необходимо дополнительно задать директиву resolver.
Синтаксис: | ssl_stapling_file файл ; |
---|---|
Умолчание: | — |
Контекст: | http , server |
Эта директива появилась в версии 1.3.7.
Если задано, то вместо опроса OCSP responder’а, указанного в сертификате сервера, ответ берётся из указанного файла .
Ответ должен быть в формате DER и может быть сгенерирован командой “ openssl ocsp ”.
Синтаксис: | ssl_stapling_responder url ; |
---|---|
Умолчание: | — |
Контекст: | http , server |
Эта директива появилась в версии 1.3.7.
Переопределяет URL OCSP responder’а, указанный в расширении сертификата “Authority Information Access”.
Поддерживаются только “ http:// ” OCSP responder’ы:
Синтаксис: | ssl_stapling_verify on | off ; |
---|---|
Умолчание: | |
Контекст: | http , server |
Эта директива появилась в версии 1.3.7.
Разрешает или запрещает проверку сервером ответов OCSP.
Для работоспособности проверки сертификат издателя сертификата сервера, корневой сертификат и все промежуточные сертификаты должны быть указаны как доверенные с помощью директивы ssl_trusted_certificate.
Синтаксис: | ssl_trusted_certificate файл ; |
---|---|
Умолчание: | — |
Контекст: | http , server |
Эта директива появилась в версии 1.3.7.
Задаёт файл с доверенными сертификатами CA в формате PEM, которые используются для проверки клиентских сертификатов и ответов OCSP, если включён ssl_stapling.
В отличие от ssl_client_certificate, список этих сертификатов не будет отправляться клиентам.
Синтаксис: | ssl_verify_client on | off | optional | optional_no_ca ; |
---|---|
Умолчание: | |
Контекст: | http , server |
Разрешает проверку клиентских сертификатов. Результат проверки доступен через переменную $ssl_client_verify.
Параметр optional (0.8.7+) запрашивает клиентский сертификат, и если сертификат был предоставлен, проверяет его.
Параметр optional_no_ca (1.3.8, 1.2.5) запрашивает сертификат клиента, но не требует, чтобы он был подписан доверенным сертификатом CA. Это предназначено для случаев, когда фактическая проверка сертификата осуществляется внешним по отношению к nginx’у сервисом. Содержимое сертификата доступно через переменную $ssl_client_cert.
Синтаксис: | ssl_verify_depth число ; |
---|---|
Умолчание: | |
Контекст: | http , server |
Устанавливает глубину проверки в цепочке клиентских сертификатов.
Обработка ошибок
Модуль ngx_http_ssl_module поддерживает несколько нестандартных кодов ошибок, которые можно использовать для перенаправления с помощью директивы error_page:
495 при проверке клиентского сертификата произошла ошибка; 496 клиент не предоставил требуемый сертификат; 497 обычный запрос был послан на порт HTTPS.
Перенаправление делается после того, как запрос полностью разобран и доступны такие переменные, как $request_uri , $uri , $args и другие переменные.
Встроенные переменные
Модуль ngx_http_ssl_module поддерживает встроенные переменные:
$ssl_alpn_protocol возвращает протокол, выбранный при помощи ALPN во время операции SSL handshake, либо пустую строку (1.21.4); $ssl_cipher возвращает название используемого шифра для установленного SSL-соединения; $ssl_ciphers возвращает список шифров, поддерживаемых клиентом (1.11.7). Известные шифры указаны по имени, неизвестные указаны в шестнадцатеричном виде, например:
Переменная полностью поддерживается при использовании OpenSSL версии 1.0.2 и выше. При использовании более старых версий переменная доступна только для новых сессий и может содержать только известные шифры.
Переменная устарела, вместо неё следует использовать переменную $ssl_client_escaped_cert .
До версии 1.11.6 переменная называлась $ssl_client_s_dn .
До версии 1.11.6 переменная называлась $ssl_client_s_dn .
До версии 1.11.7 результат “ FAILED ” не содержал строку reason .
Переменная поддерживается при использовании OpenSSL версии 3.0 и выше. При использовании более старых версий значением переменной будет пустая строка.
Переменная поддерживается при использовании OpenSSL версии 1.0.2 и выше. При использовании более старых версий значением переменной будет пустая строка.
Переменная доступна только для новых сессий.
Источник
Модуль ngx_http_ssl_module
обеспечивает работу
по протоколу HTTPS.
По умолчанию этот модуль не собирается, его сборку необходимо
разрешить с помощью конфигурационного параметра
--with-http_ssl_module
.
Для сборки и работы этого модуля нужна библиотека
OpenSSL.
Пример конфигурации
Для уменьшения загрузки процессора рекомендуется
-
установить число
рабочих процессов
равным числу процессоров, -
разрешить
keep-alive
соединения, - включить разделяемый кэш сессий,
- выключить встроенный кэш сессий
-
и, возможно, увеличить время жизни сессии
(по умолчанию 5 минут):
worker_processes auto; http { ... server { listen 443 ssl; keepalive_timeout 70; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5; ssl_certificate /usr/local/nginx/conf/cert.pem; ssl_certificate_key /usr/local/nginx/conf/cert.key; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ... }
Директивы
Синтаксис: |
ssl
|
---|---|
Умолчание: |
ssl off; |
Контекст: |
http , server
|
Эта директива устарела в версии 1.15.0.
Вместо неё следует
использовать параметр ssl
директивы listen.
Синтаксис: |
ssl_buffer_size
|
---|---|
Умолчание: |
ssl_buffer_size 16k; |
Контекст: |
http , server
|
Эта директива появилась в версии 1.5.9.
Задаёт размер буфера, используемого при отправке данных.
По умолчанию размер буфера равен 16k, что соответствует минимальным
накладным расходам при передаче больших ответов.
С целью минимизации времени получения начала ответа (Time To First Byte)
может быть полезно использовать меньшие значения,
например:
ssl_buffer_size 4k;
Синтаксис: |
ssl_certificate
|
---|---|
Умолчание: |
— |
Контекст: |
http , server
|
Указывает файл
с сертификатом в формате PEM
для данного виртуального сервера.
Если вместе с основным сертификатом нужно указать промежуточные,
то они должны находиться в этом же файле в следующем порядке: сначала
основной сертификат, а затем промежуточные.
В этом же файле может находиться секретный ключ в формате PEM.
Начиная с версии 1.11.0
эта директива может быть указана несколько раз
для загрузки сертификатов разных типов, например RSA и ECDSA:
server { listen 443 ssl; server_name example.com; ssl_certificate example.com.rsa.crt; ssl_certificate_key example.com.rsa.key; ssl_certificate example.com.ecdsa.crt; ssl_certificate_key example.com.ecdsa.key; ... }
Возможность задавать отдельные
цепочки
сертификатов
для разных сертификатов есть только в OpenSSL 1.0.2 и выше.
Для более старых версий следует указывать только одну цепочку сертификатов.
Начиная с версии 1.15.9 в имени файла можно использовать переменные
при использовании OpenSSL 1.0.2 и выше:
ssl_certificate $ssl_server_name.crt; ssl_certificate_key $ssl_server_name.key;
Однако нужно учитывать, что при использовании переменных
сертификат загружается при каждой операции SSL handshake,
что может отрицательно влиять на производительность.
Вместо файла
можно указать значение
data
:$переменная
(1.15.10),
при котором сертификат загружается из переменной
без использования промежуточных файлов.
При этом следует учитывать, что ненадлежащее использование
подобного синтаксиса может быть небезопасно,
например данные секретного ключа могут попасть в
лог ошибок.
Нужно иметь в виду, что из-за ограничения протокола HTTPS
для максимальной совместимости виртуальные серверы должны слушать на
разных
IP-адресах.
Синтаксис: |
ssl_certificate_key
|
---|---|
Умолчание: |
— |
Контекст: |
http , server
|
Указывает файл
с секретным ключом в формате PEM
для данного виртуального сервера.
Вместо файла
можно указать значение
engine
:имя
:id
(1.7.9),
которое загружает ключ с указанным id
из OpenSSL engine с заданным именем
.
Вместо файла
можно указать значение
data
:$переменная
(1.15.10),
при котором секретный ключ загружается из переменной
без использования промежуточных файлов.
При этом следует учитывать, что ненадлежащее использование
подобного синтаксиса может быть небезопасно,
например данные секретного ключа могут попасть в
лог ошибок.
Начиная с версии 1.15.9 в имени файла можно использовать переменные
при использовании OpenSSL 1.0.2 и выше.
Синтаксис: |
ssl_ciphers
|
---|---|
Умолчание: |
ssl_ciphers HIGH:!aNULL:!MD5; |
Контекст: |
http , server
|
Описывает разрешённые шифры.
Шифры задаются в формате, поддерживаемом библиотекой
OpenSSL, например:
ssl_ciphers ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
Полный список можно посмотреть с помощью команды
“openssl ciphers
”.
В предыдущих версиях nginx по умолчанию использовались
другие
шифры.
Синтаксис: |
ssl_client_certificate
|
---|---|
Умолчание: |
— |
Контекст: |
http , server
|
Указывает файл
с доверенными сертификатами CA в формате
PEM, которые используются для
проверки клиентских сертификатов и
ответов OCSP, если включён ssl_stapling.
Список сертификатов будет отправляться клиентам.
Если это нежелательно, можно воспользоваться директивой
ssl_trusted_certificate.
Синтаксис: |
ssl_conf_command
|
---|---|
Умолчание: |
— |
Контекст: |
http , server
|
Эта директива появилась в версии 1.19.4.
Задаёт произвольные конфигурационные
команды
OpenSSL.
Директива поддерживается при использовании OpenSSL 1.0.2 и выше.
На одном уровне может быть указано
несколько директив ssl_conf_command
:
ssl_conf_command Options PrioritizeChaCha; ssl_conf_command Ciphersuites TLS_CHACHA20_POLY1305_SHA256;
Директивы наследуются с предыдущего уровня конфигурации при условии, что
на данном уровне не описаны свои директивы ssl_conf_command
.
Следует учитывать, что изменение настроек OpenSSL напрямую
может привести к неожиданному поведению.
Синтаксис: |
ssl_crl
|
---|---|
Умолчание: |
— |
Контекст: |
http , server
|
Эта директива появилась в версии 0.8.7.
Указывает файл
с отозванными сертификатами (CRL)
в формате PEM, используемыми для
проверки клиентских сертификатов.
Синтаксис: |
ssl_dhparam
|
---|---|
Умолчание: |
— |
Контекст: |
http , server
|
Эта директива появилась в версии 0.7.2.
Указывает файл
с параметрами для DHE-шифров.
По умолчанию параметры не заданы,
и соответственно DHE-шифры не будут использоваться.
До версии 1.11.0 по умолчанию использовались встроенные параметры.
Синтаксис: |
ssl_early_data
|
---|---|
Умолчание: |
ssl_early_data off; |
Контекст: |
http , server
|
Эта директива появилась в версии 1.15.3.
Разрешает или запрещает TLS 1.3
early data.
Запросы, отправленные внутри early data, могут быть подвержены
атакам повторного воспроизведения (replay).
Для защиты от подобных атак на уровне приложения
необходимо использовать
переменную $ssl_early_data.
proxy_set_header Early-Data $ssl_early_data;
Директива поддерживается при использовании OpenSSL 1.1.1 и выше (1.15.4) или
BoringSSL.
Синтаксис: |
ssl_ecdh_curve
|
---|---|
Умолчание: |
ssl_ecdh_curve auto; |
Контекст: |
http , server
|
Эта директива появилась в версиях 1.1.0 и 1.0.6.
Задаёт кривую для ECDHE-шифров.
При использовании OpenSSL 1.0.2 и выше
можно указывать несколько кривых (1.11.0), например:
ssl_ecdh_curve prime256v1:secp384r1;
Специальное значение auto
(1.11.0) соответствует
встроенному в библиотеку OpenSSL списку кривых для OpenSSL 1.0.2 и выше,
или prime256v1
для более старых версий.
До версии 1.11.0
по умолчанию использовалась криваяprime256v1
.
При использовании OpenSSL 1.0.2 и выше
директива задаёт список кривых, поддерживаемых сервером.
Поэтому для работы ECDSA-сертификатов
важно, чтобы список включал кривые, используемые в сертификатах.
Синтаксис: |
ssl_ocsp
|
---|---|
Умолчание: |
ssl_ocsp off; |
Контекст: |
http , server
|
Эта директива появилась в версии 1.19.0.
Включает проверку OCSP для цепочки клиентских сертификатов.
Параметр leaf
включает проверку только клиентского сертификата.
Для работы проверки OCSP
необходимо дополнительно установить значение директивы
ssl_verify_client в
on
или optional
.
Для преобразования имени хоста OCSP responder’а в адрес необходимо
дополнительно задать директиву
resolver.
Пример:
ssl_verify_client on; ssl_ocsp on; resolver 192.0.2.1;
Синтаксис: |
ssl_ocsp_cache
|
---|---|
Умолчание: |
ssl_ocsp_cache off; |
Контекст: |
http , server
|
Эта директива появилась в версии 1.19.0.
Задаёт имя
и размер
кэша,
который хранит статус клиентских сертификатов для проверки OCSP-ответов.
Кэш разделяется между всеми рабочими процессами.
Кэш с одинаковым названием может использоваться в нескольких
виртуальных серверах.
Параметр off
запрещает использование кэша.
Синтаксис: |
ssl_ocsp_responder
|
---|---|
Умолчание: |
— |
Контекст: |
http , server
|
Эта директива появилась в версии 1.19.0.
Переопределяет URL OCSP responder’а, указанный в расширении сертификата
“Authority
Information Access”
для проверки клиентских сертификатов.
Поддерживаются только “http://
” OCSP responder’ы:
ssl_ocsp_responder http://ocsp.example.com/;
Синтаксис: |
ssl_password_file
|
---|---|
Умолчание: |
— |
Контекст: |
http , server
|
Эта директива появилась в версии 1.7.3.
Задаёт файл
с паролями от
секретных ключей,
где каждый пароль указан на отдельной строке.
Пароли применяются по очереди в момент загрузки ключа.
Пример:
http { ssl_password_file /etc/keys/global.pass; ... server { server_name www1.example.com; ssl_certificate_key /etc/keys/first.key; } server { server_name www2.example.com; # вместо файла можно указать именованный канал ssl_password_file /etc/keys/fifo; ssl_certificate_key /etc/keys/second.key; } }
Синтаксис: |
ssl_prefer_server_ciphers
|
---|---|
Умолчание: |
ssl_prefer_server_ciphers off; |
Контекст: |
http , server
|
Указывает, чтобы при использовании протоколов SSLv3 и TLS
серверные шифры были более приоритетны, чем клиентские.
Синтаксис: |
ssl_protocols
|
---|---|
Умолчание: |
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; |
Контекст: |
http , server
|
Разрешает указанные протоколы.
Параметры
TLSv1.1
иTLSv1.2
(1.1.13, 1.0.12) работают только при использовании OpenSSL 1.0.1 и выше.
Параметр
TLSv1.3
(1.13.0) работает только
при использовании OpenSSL 1.1.1 и выше.
Синтаксис: |
ssl_reject_handshake
|
---|---|
Умолчание: |
ssl_reject_handshake off; |
Контекст: |
http , server
|
Эта директива появилась в версии 1.19.4.
Если разрешено, то операции SSL handshake в
блоке server будут отклонены.
Например в этой конфигурации отклоняются все операции SSL handshake с
именем сервера, отличным от example.com
:
server { listen 443 ssl default_server; ssl_reject_handshake on; } server { listen 443 ssl; server_name example.com; ssl_certificate example.com.crt; ssl_certificate_key example.com.key; }
Синтаксис: |
ssl_session_cache
|
---|---|
Умолчание: |
ssl_session_cache none; |
Контекст: |
http , server
|
Задаёт тип и размеры кэшей для хранения параметров сессий.
Тип кэша может быть следующим:
off
-
жёсткое запрещение использования кэша сессий:
nginx явно сообщает клиенту, что сессии не могут использоваться повторно. none
-
мягкое запрещение использования кэша сессий:
nginx сообщает клиенту, что сессии могут использоваться повторно, но
на самом деле не хранит параметры сессии в кэше. builtin
-
встроенный в OpenSSL кэш, используется в рамках только одного рабочего процесса.
Размер кэша задаётся в сессиях.
Если размер не задан, то он равен 20480 сессиям.
Использование встроенного кэша может вести к фрагментации памяти. shared
-
кэш, разделяемый между всеми рабочими процессами.
Размер кэша задаётся в байтах, в 1 мегабайт может поместиться
около 4000 сессий.
У каждого разделяемого кэша должно быть произвольное название.
Кэш с одинаковым названием может использоваться в нескольких
виртуальных серверах.
Также он используется для автоматического создания, хранения и
периодического обновления ключей TLS session tickets (1.23.2),
если они не указаны явно
с помощью директивы ssl_session_ticket_key.
Можно использовать одновременно оба типа кэша, например:
ssl_session_cache builtin:1000 shared:SSL:10m;
однако использование только разделяемого кэша без встроенного должно
быть более эффективным.
Синтаксис: |
ssl_session_ticket_key
|
---|---|
Умолчание: |
— |
Контекст: |
http , server
|
Эта директива появилась в версии 1.5.7.
Задаёт файл
с секретным ключом, применяемым при шифровании и
расшифровании TLS session tickets.
Директива необходима, если один и тот же ключ нужно использовать
на нескольких серверах.
По умолчанию используется случайно сгенерированный ключ.
Если указано несколько ключей, то только первый ключ
используется для шифрования TLS session tickets.
Это позволяет настроить ротацию ключей, например:
ssl_session_ticket_key current.key; ssl_session_ticket_key previous.key;
Файл
должен содержать 80 или 48 байт случайных данных
и может быть создан следующей командой:
openssl rand 80 > ticket.key
В зависимости от размера файла для шифрования будет использоваться либо
AES256 (для 80-байтных ключей, 1.11.8), либо AES128 (для 48-байтных ключей).
Синтаксис: |
ssl_session_tickets
|
---|---|
Умолчание: |
ssl_session_tickets on; |
Контекст: |
http , server
|
Эта директива появилась в версии 1.5.9.
Разрешает или запрещает возобновление сессий при помощи
TLS session tickets.
Синтаксис: |
ssl_session_timeout
|
---|---|
Умолчание: |
ssl_session_timeout 5m; |
Контекст: |
http , server
|
Задаёт время, в течение которого клиент может повторно
использовать параметры сессии.
Синтаксис: |
ssl_stapling
|
---|---|
Умолчание: |
ssl_stapling off; |
Контекст: |
http , server
|
Эта директива появилась в версии 1.3.7.
Разрешает или запрещает
прикрепление
OCSP-ответов сервером.
Пример:
ssl_stapling on; resolver 192.0.2.1;
Для работы OCSP stapling’а должен быть известен сертификат издателя
сертификата сервера.
Если в заданном директивой ssl_certificate
файле не содержится промежуточных сертификатов,
то сертификат издателя сертификата сервера следует поместить в файл,
заданный директивой ssl_trusted_certificate.
Для преобразования имени хоста OCSP responder’а в адрес необходимо
дополнительно задать директиву
resolver.
Синтаксис: |
ssl_stapling_file
|
---|---|
Умолчание: |
— |
Контекст: |
http , server
|
Эта директива появилась в версии 1.3.7.
Если задано, то вместо опроса OCSP responder’а,
указанного в сертификате сервера,
ответ берётся из указанного файла
.
Ответ должен быть в формате DER и может быть сгенерирован командой
“openssl ocsp
”.
Синтаксис: |
ssl_stapling_responder
|
---|---|
Умолчание: |
— |
Контекст: |
http , server
|
Эта директива появилась в версии 1.3.7.
Переопределяет URL OCSP responder’а, указанный в расширении сертификата
“Authority
Information Access”.
Поддерживаются только “http://
” OCSP responder’ы:
ssl_stapling_responder http://ocsp.example.com/;
Синтаксис: |
ssl_stapling_verify
|
---|---|
Умолчание: |
ssl_stapling_verify off; |
Контекст: |
http , server
|
Эта директива появилась в версии 1.3.7.
Разрешает или запрещает проверку сервером ответов OCSP.
Для работоспособности проверки сертификат издателя сертификата сервера,
корневой сертификат и все промежуточные сертификаты должны быть указаны
как доверенные с помощью директивы
ssl_trusted_certificate.
Синтаксис: |
ssl_trusted_certificate
|
---|---|
Умолчание: |
— |
Контекст: |
http , server
|
Эта директива появилась в версии 1.3.7.
Задаёт файл
с доверенными сертификатами CA в формате PEM,
которые используются для проверки
клиентских сертификатов и ответов OCSP,
если включён ssl_stapling.
В отличие от ssl_client_certificate, список этих сертификатов
не будет отправляться клиентам.
Синтаксис: |
ssl_verify_client
|
---|---|
Умолчание: |
ssl_verify_client off; |
Контекст: |
http , server
|
Разрешает проверку клиентских сертификатов.
Результат проверки доступен через переменную
$ssl_client_verify.
Параметр optional
(0.8.7+) запрашивает клиентский
сертификат, и если сертификат был предоставлен, проверяет его.
Параметр optional_no_ca
(1.3.8, 1.2.5)
запрашивает сертификат
клиента, но не требует, чтобы он был подписан доверенным сертификатом CA.
Это предназначено для случаев, когда фактическая проверка сертификата
осуществляется внешним по отношению к nginx’у сервисом.
Содержимое сертификата доступно через переменную
$ssl_client_cert.
Синтаксис: |
ssl_verify_depth
|
---|---|
Умолчание: |
ssl_verify_depth 1; |
Контекст: |
http , server
|
Устанавливает глубину проверки в цепочке клиентских сертификатов.
Обработка ошибок
Модуль ngx_http_ssl_module
поддерживает несколько
нестандартных кодов ошибок, которые можно использовать для
перенаправления с помощью директивы
error_page:
- 495
- при проверке клиентского сертификата произошла ошибка;
- 496
- клиент не предоставил требуемый сертификат;
- 497
- обычный запрос был послан на порт HTTPS.
Перенаправление делается после того, как запрос полностью разобран
и доступны такие переменные, как $request_uri
,
$uri
, $args
и другие переменные.
Встроенные переменные
Модуль ngx_http_ssl_module
поддерживает
встроенные переменные:
$ssl_alpn_protocol
-
возвращает протокол, выбранный при помощи ALPN во время операции SSL handshake,
либо пустую строку (1.21.4); $ssl_cipher
- возвращает название используемого шифра для установленного SSL-соединения;
$ssl_ciphers
-
возвращает список шифров, поддерживаемых клиентом (1.11.7).
Известные шифры указаны по имени, неизвестные указаны в шестнадцатеричном виде,
например:AES128-SHA:AES256-SHA:0x00ff
Переменная полностью поддерживается при использовании OpenSSL версии 1.0.2
и выше.
При использовании более старых версий переменная доступна
только для новых сессий и может содержать только известные шифры. $ssl_client_escaped_cert
-
возвращает клиентский сертификат в формате PEM
(закодирован в формате urlencode) для установленного SSL-соединения (1.13.5); $ssl_client_cert
-
возвращает клиентский сертификат
для установленного SSL-соединения в формате PEM
перед каждой строкой которого, кроме первой, вставляется символ табуляции;
предназначена для использования в директиве
proxy_set_header;Переменная устарела, вместо неё следует использовать
переменную$ssl_client_escaped_cert
. $ssl_client_fingerprint
-
возвращает SHA1-отпечаток клиентского сертификата
для установленного SSL-соединения (1.7.1); $ssl_client_i_dn
-
возвращает строку “issuer DN” клиентского сертификата
для установленного SSL-соединения согласно
RFC 2253 (1.11.6); $ssl_client_i_dn_legacy
-
возвращает строку “issuer DN” клиентского сертификата
для установленного SSL-соединения;До версии 1.11.6 переменная называлась
$ssl_client_s_dn
. $ssl_client_raw_cert
-
возвращает клиентский сертификат
для установленного SSL-соединения в формате PEM; $ssl_client_s_dn
-
возвращает строку “subject DN” клиентского сертификата
для установленного SSL-соединения согласно
RFC 2253 (1.11.6); $ssl_client_s_dn_legacy
-
возвращает строку “subject DN” клиентского сертификата
для установленного SSL-соединения;До версии 1.11.6 переменная называлась
$ssl_client_s_dn
. $ssl_client_serial
-
возвращает серийный номер клиентского сертификата
для установленного SSL-соединения; $ssl_client_v_end
- возвращает дату окончания срока действия клиентского сертификата (1.11.7);
$ssl_client_v_remain
-
возвращает число дней,
оставшихся до истечения срока действия клиентского сертификата (1.11.7); $ssl_client_v_start
- возвращает дату начала срока действия клиентского сертификата (1.11.7);
$ssl_client_verify
-
возвращает результат проверки клиентского сертификата:
“SUCCESS
”, “FAILED:
reason
”
и, если сертификат не был предоставлен, “NONE
”;До версии 1.11.7 результат “
FAILED
”
не содержал строкуreason
. $ssl_curve
-
возвращает согласованную кривую, использованную для
обмена ключами во время операции SSL handshake (1.21.5).
Известные кривые указаны по имени, неизвестные указаны в шестнадцатеричном виде,
например:prime256v1
Переменная поддерживается при использовании OpenSSL версии 3.0 и выше.
При использовании более старых версий значением переменной будет пустая строка. $ssl_curves
-
возвращает список кривых, поддерживаемых клиентом (1.11.7).
Известные кривые указаны по имени, неизвестные указаны в шестнадцатеричном виде,
например:0x001d:prime256v1:secp521r1:secp384r1
Переменная поддерживается при использовании OpenSSL версии 1.0.2 и выше.
При использовании более старых версий значением переменной будет пустая строка.Переменная доступна только для новых сессий.
$ssl_early_data
-
возвращает “
1
”, если
используется TLS 1.3 early data
и операция handshake не завершена, иначе “” (1.15.3). $ssl_protocol
- возвращает протокол установленного SSL-соединения;
$ssl_server_name
-
возвращает имя сервера, запрошенное через
SNI
(1.7.0); $ssl_session_id
- возвращает идентификатор сессии установленного SSL-соединения;
$ssl_session_reused
-
возвращает “
r
”, если сессия была использована повторно,
иначе “.
” (1.5.11).
0
2
nginx 1.6.2, самоподписанные сертификаты сервера и клиента, openssl проверку проходят:
$ openssl verify -CAfile server/server.crt client01.crt
client01.crt: C = RU, ST = Moscow, L = Moscow, O = Companyname, OU = User, CN = etc, emailAddress = support@site.com
error 18 at 0 depth lookup:self signed certificate
OK
А при запросе nginx отвечает ошибкой 400 The SSL certificate error
В логах сервера:
2016/04/22 11:16:20 [info] 1465#0: *35 client SSL certificate verify error: (18:self signed certificate) wh
ile reading client request headers, client: 192.168.2.11, server: _, request: "GET /test HTTP/1.1", host: "
192.168.2.6"
2016/04/22 11:16:20 [debug] 1465#0: *35 http finalize request: 495, "/test?" a:1, c:1
2016/04/22 11:16:20 [debug] 1465#0: *35 event timer del: 8: 1461313040230
2016/04/22 11:16:20 [debug] 1465#0: *35 http special response: 495, "/test?"
2016/04/22 11:16:20 [debug] 1465#0: *35 http set discard body
2016/04/22 11:16:20 [debug] 1465#0: *35 echo header filter, uri "/test?"
2016/04/22 11:16:20 [debug] 1465#0: *35 xslt filter header
2016/04/22 11:16:20 [debug] 1465#0: *35 posix_memalign: 0000000001F02580:4096 @16
2016/04/22 11:16:20 [debug] 1465#0: *35 HTTP/1.1 400 Bad Request
Настройки nginx:
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl on;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.nopass.key;
ssl_client_certificate /etc/nginx/ssl/ca.crt;
ssl_verify_client on;
location {
proxy_pass http://192.168.2.6:9080;
}
}
Я правильно понимаю, что серверу не нравится то, что сертификат самоподписанный? Как разрешить?
Edit: I had also this «problem», solution and explanation is at the bottom of the text.
It seemed like nginx doesn’t support intermediate certificates. My certs self created: (RootCA is selfsigned, IntrermediateCA1 is signed by RootCA, etc.)
RootCA -> IntermediateCA1 -> Client1
RootCA -> IntermediateCA2 -> Client2
I want to use in nginx «IntermediateCA1», to allow access to site only to owner of the «Client1» certificate.
When I put to «ssl_client_certificate» file with IntermediateCA1 and RootCA, and set «ssl_verify_depth 2» (or more) , clients can login to site both using certificate Client1 and Client2 (should only Client1).
The same result is when I put to «ssl_client_certificate» file with only RootCA — both clients can login.
When I put to «ssl_client_certificate» file with only IntermediateCA1, and set «ssl_verify_depth 1» (or «2» or more — no matter) , it is imposible to log in, I get error 400. And in debug mode i see logs:
verify:0, error:20, depth:1, subject:"/C=PL/CN=IntermediateCA1/emailAddress=cert@asdf.com",issuer: "/C=PL/CN=RootCA/emailAddress=cert@asdf.com"
verify:0, error:27, depth:1, subject:"/C=PL/CN=IntermediateCA1/emailAddress=cert@asdf.com",issuer: "/C=PL/CN=RootCA/emailAddress=cert@asdf.com"
verify:1, error:27, depth:0, subject:"/C=PL/CN=Client1/emailAddress=cert@asdf.com",issuer: "/C=PL/CN=IntermediateCA1/emailAddress=cert@asdf.com"
(..)
client SSL certificate verify error: (27:certificate not trusted) while reading client request headers, (..)
I thing this is a bug. Tested on Ubuntu, nginx 1.1.19 and 1.2.7-1~dotdeb.1, openssl 1.0.1.
I see that nginx 1.3 has few more options about using client certificates, but I’dont see solution to this problem.
Currently, the only one way to separate clients 1 and 2 is to create two, selfsigned RootCAs, but this is only workaround..
Edit 1:
I’ve reported this issue here: http://trac.nginx.org/nginx/ticket/301
Edit 2″
*Ok, it’s not a bug, it is feature *
I get response here: http://trac.nginx.org/nginx/ticket/301
It is working, you must only check what your ssl_client_i_dn is (. Instead of issuer you can use also subject of certificate, or what you want from http://wiki.nginx.org/HttpSslModule#Built-in_variables
This is how certificate verification works: certificate must be
verified up to a trusted root. If chain can’t be built to a trusted
root (not intermediate) — verification fails. If you trust root — all
certificates signed by it, directly or indirectly, will be
successfully verified.Limiting verification depth may be used if you
want to limit client certificates to a directly issued certificates
only, but it’s more about DoS prevention, and obviously it can’t be
used to limit verificate to intermediate1 only (but not
intermediate2).What you want here is some authorization layer based
on the verification result — i.e. you may want to check that client’s
certificate issuer is intermediate1. Simplest solution would be to
reject requests if issuer’s DN doesn’t match one allowed, e.g.
something like this (completely untested):
[ Edit by me, it is working correctly in my configuration ]
server {
listen 443 ssl;
ssl_certificate ...
ssl_certificate_key ...
ssl_client_certificate /path/to/ca.crt;
ssl_verify_client on;
ssl_verify_depth 2;
if ($ssl_client_i_dn != "/C=PL/CN=IntermediateCA1/emailAddress=cert@asdf.com") {
return 403;
}
}
I’m having some difficulty with nginx’s client authentication while using an intermediate CA (self-created).
Although the same certificate bundle (intermediate + root certificates in a single .pem file) works just fine for client authentication in IMAP (dovecot) and SMTP (postfix), I just can’t seem to get it to work with nginx. Instead I’m getting the following error:
[info] 23383#23383: *14583139 client SSL certificate verify error: (27:certificate not trusted) while reading client request headers, client: 82.39.81.156, server: <hostname>, request: "GET /mailboxes HTTP/1.1", host: "<hostname>"
As I understand it, an error type 27 from openssl is X509_V_ERR_CERT_UNTRUSTED
, or some kind of issue with a certificate being untrusted for a particular purpose, however I can’t get it to elaborate any further.
The individual and bundled certificates all seem to validate correctly with openssl verify
(I can verify client certificates against intermediate or the bundle, and the intermediate certificate validates against the root certificate, i.e- it’s all valid in every combination I can think of).
My root and intermediate CAs should be set with the correct extensions in order to verify my client certificates, here’s the relevant sample of my custom openssl.conf
file:
[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
Meanwhile the client certificates that I’m issuing are configured to work for both client authentication and e-mail encryption/signing like so:
[ user_cert ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectAltName = email:move
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = critical, clientAuth, emailProtection
(indeed, if they weren’t set correctly then OS X’s Safari and Mail wouldn’t let me send them at all, as they tend to be really strict).
A number of similar questions have recommended setting ssl_verify_depth
to a value of 2 (to verify both the intermediate and root certificates), but this doesn’t seem to help.
Thanks, that clarifies things a lot!
First of all, this is not a bug in nginx
Enabling debug in error log in nginx, I could see the following error when trying to auth using generated certificates:
2017/06/06 15:59:58 [info] 6#6: *2 client SSL certificate verify error: (26:unsupported certificate purpose) while reading client request headers, client: 172.17.0.1, server: localhost, request: «GET / HTTP/1.1», host: «server:3001»
And this did not happen when I compiled the same nginx with an old openssl version — which led me thinking that some bug might have been fixed or introduced in openssl. I’ve also checked old nginx version against openssl 1.1.0 — which exhibited the same exact problem of not validating the certificates.
I’ve took a closer look on the certificates. The ca.crt generated has the following (via openssl x509 -in ./certs/ca.crt -noout -purpose
):
Certificate purposes:
SSL client : Yes
SSL client CA : Yes
SSL server : Yes
SSL server CA : Yes
Although coagent.crt and all the following ones:
Certificate purposes:
SSL client : Yes
SSL client CA : No
SSL server : Yes
SSL server CA : No
So it looks like that prior to 1.1.0 openssl did not actually check the purposes of the certificates when checking if they are valid:
/cert/certs # openssl version
OpenSSL 1.0.2k 26 Jan 2017
/cert/certs # openssl x509 -in ./ca.crt -noout -purpose |grep ^SSL
SSL client : Yes
SSL client CA : Yes (WARNING code=3)
SSL server : Yes
SSL server CA : Yes (WARNING code=3)
/cert/certs # openssl x509 -in ./coagent.crt -noout -purpose |grep ^SSL
SSL client : Yes
SSL client CA : No
SSL server : Yes
SSL server CA : No
/cert/certs # openssl verify -verbose -CAfile ./ca.crt coagent.crt
coagent.crt: OK
/cert/certs # cat ./ca.crt ./coagent.crt > ca_coagent.crt
/cert/certs # openssl verify -verbose -CAfile ./ca_coagent.crt ./t1.crt
./t1.crt: OK
/cert/certs # cat ./ca.crt ./coagent.crt ./t1.crt > ca_coagent_t1.crt
/cert/certs # openssl x509 -in ./t1.crt -noout -purpose |grep ^SSL
SSL client : Yes
SSL client CA : No
SSL server : Yes
SSL server CA : No
/cert/certs # openssl verify -verbose -CAfile ./ca_coagent_t1.crt ./v1.crt
./v1.crt: OK
However things are different in 1.1.0:
root@deb9-test1:/etc/nginx/cert/certs# openssl version
OpenSSL 1.1.0e 16 Feb 2017
root@deb9-test1:/etc/nginx/cert/certs# openssl x509 -in ./ca.crt -noout -purpose |grep ^SSL
SSL client : Yes
SSL client CA : Yes (WARNING code=3)
SSL server : Yes
SSL server CA : Yes (WARNING code=3)
root@deb9-test1:/etc/nginx/cert/certs# openssl x509 -in ./coagent.crt -noout -purpose |grep ^SSL
SSL client : Yes
SSL client CA : No
SSL server : Yes
SSL server CA : No
root@deb9-test1:/etc/nginx/cert/certs# openssl verify -verbose -CAfile ./ca.crt coagent.crt
coagent.crt: OK
root@deb9-test1:/etc/nginx/cert/certs# cat ./ca.crt ./coagent.crt > ca_coagent.crt
root@deb9-test1:/etc/nginx/cert/certs# openssl verify -verbose -CAfile ./ca_coagent.crt ./t1.crt
C = US, ST = Denial, L = Springfield, O = Dis, CN = coagent
error 24 at 1 depth lookup: invalid CA certificate
error ./t1.crt: verification failed
root@deb9-test1:/etc/nginx/cert/certs# cat ./ca.crt ./coagent.crt ./t1.crt > ca_coagent_t1.crt
root@deb9-test1:/etc/nginx/cert/certs# openssl x509 -in ./t1.crt -noout -purpose |grep ^SSL
SSL client : Yes
SSL client CA : No
SSL server : Yes
SSL server CA : No
root@deb9-test1:/etc/nginx/cert/certs# openssl verify -verbose -CAfile ./ca_coagent_t1.crt ./v1.crt
C = US, ST = Denial, L = Springfield, O = Dis, CN = t1
error 24 at 1 depth lookup: invalid CA certificate
C = US, ST = Denial, L = Springfield, O = Dis, CN = coagent
error 24 at 2 depth lookup: invalid CA certificate
error ./v1.crt: verification failed
So the problem here is that openssl has fixed the bug, and now actually checks for the purposes of the certificates. I guess prior to that clients could generate new leaf certificates and if you did not set the ssl_verify_depth in nginx properly, you could have been abused…
Anyway, I’ve re-generated the certificates with:
openssl x509 -req -days 365 -in coagent.csr -CA ca.crt -CAkey ca.key -set_serial 1002 -out coagent.crt -extfile ./openssl.cnf -extensions v3_ca
where openssl.cnf is:
[ v3_ca ]
basicConstraints = CA:true
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid
Now the certificates have SSL client CA: Yes
and SSL server CA: Yes
and validation goes just fine. But I guess you would want to set it only for the intermediaries, not the actual clients.
We’re using client side certificates on an Nginx host to ensure the credentials of the connecting users and haven’t used the site for a while.
I tried to logon with a known good client certificate and know that nothing on the site config has changed and all I get in return is a 400 error with the message “SSL Certificate Error”, which is not at all helpful.
First I thought I’d regenerate my client certificate, no joy there. Still the same error. So I went through the process of verifying the CA cert matched my source and was still valid. Use openssl to verify my client certs, all looked good. Nothing I did allowed me to access the site unless I turned client certificate verification off.
ssl_verify_client off;
So what was my problem? I checked the nginx.conf
and our logging was set to push out to a syslog server:
error_log syslog:server=logserver:515,severity=debug;
But I wasn’t seeing anything in the log about any errors. I checked the syntax of the config entry and found it to be missing the debug option – confusing I know, it looks like it should be using debug logging, but that’s just the severity setting for the syslog server, not Nginx. I added debug
onto the end of the line:
error_log syslog:server=logserver:515,severity=debug debug;
Now I can see what the problem is with the certificates – there is no problem with the certificates, it’s a problem with my certificate revocation list being too old! I just need to regenerate and reissue a new one.
<190>Oct 14 20:38:22 proxy3.domain.tld nginx: 2020/10/14 20:38:22 [info] 12373#12373: *83301603 client SSL certificate verify error: (12:CRL has expired) while reading client request headers, client: 81.8.151.23, server: server.domain.tld, request: "GET /favicon.ico HTTP/1.1", host: "server.domain.tld", referrer: "https://server.domain.tld/web/database/selector"
By using openssl I can check the validity of my crl using:
$ openssl crl -in crl.pem -text
Certificate Revocation List (CRL):
Version 2 (0x1)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN = myCA
Last Update: Oct 14 19:40:03 2020 GMT
Next Update: Apr 12 19:40:03 2021 GMT
Now all I need to do is make sure I automate this process and dump a new crl onto the server at least every 6 months – I’ll probably do it monthly to be sure.
References
https://www.djouxtech.net/posts/nginx-the-ssl-certificate-error/
I’m currently struggling against a tenacious problem while setting up client certificate authentication for our mailservers via an NginX reverse proxy.
The setup seems to be working in most parts without the client certificates. But when I enable the checking of those and run a test with openssl s_client
I allways get:
Verify return code: 2 (unable to get issuer certificate)
The relevant part of my nginx.conf is as follows:
ssl on;
ssl_certificate /etc/ssl/certs/server_cert.pem;
ssl_certificate_key /etc/ssl/private/server_key.pem;
ssl_client_certificate /etc/ssl/certs/IntermediateCA_chain.crt;
ssl_crl /etc/ssl/crl.pem;
ssl_verify_client on;
The file IntermediateCA_chain.crt is in PEM-format, and consists of both the IntermediateCA’s certificate and afterwards our RootCA’s cert.
Side-note: when I do
openssl x509 -text -noout -in IntermediateCA_chain.crt
only the IntermediateCA’s cert is shown. I expected the chain to be displayed. Is that the correct behaviour?
I test the connection with the following command:
openssl s_client -connect server:995 -cert mycert.pem
-key mykeyfile.pem -debug -CAfile IntermediateCA_chain.pem
Resulting in
[...]
0b50 - b3 c3 3b 17 66 8e 52 b3-ad 7f 14 ..;.f.R....
depth=1 DC = top, DC = ad, CN = Intermediate CA
verify error:num=2:unable to get issuer certificate
issuer= C = DE, O = My Company, CN = My Companies Root CA, emailAddress = certmgnt@mycompany.com
read from 0x10f6a10 [0x10fe333] (5 bytes => 5 (0x5))
[...]
I tried every variation of IntermediateCA_chain.pem I could think of (IntermediateCA.pem, RootCA.pem, IntermediateCA_chein.pem) on both sides.
It seems as though the failure is on the client-side, because it changes slightly with the used -CAfile
and the server’s logs show nothing (literaly nothing — no connection attempt or anything else). It seems to me as if either the IntermediateCA_chain.pem on the server- or the client-side are not read correctly. The error seems to result in openssl not beeing able to verify the IntermediateCA, and the certificates issued by it. Can someone help me solve this riddle?