• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Client authentication using self-signed ssl certificate for nginx and Django [RU/EN]

Count Zero

coder
Пользователь
Регистрация
18.06.2023
Сообщения
21
Реакции
11
Привет, друг. Расскажу, как настроить авторизацию клиента используя self-signed сертификат.

Перерыл кучу статей по настройки ssl сертификатов, а эта - результат моих исследований.

Part 1. SSL
О вериях:

Bash:
$ openssl version
OpenSSL 1.1.1n
$ nginx -v
nginx version: nginx/1.18.0
$ python3 -V
Python 3.11.4

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

Для выпуска CA сертификата нужно выполнить команду

Bash:
openssl req -new -x509 -days 9999 -keyout ca-key.pem -out ca-crt.pem -nodes \
-subj "/C=EN/ST=InAttack/L=InAttack/O=UCH/OU=CA/CN=CA/emailAddress=ca@localhost"

Результат:

ca-key.pem - приватный ключ центра сертификации. держать в строжайшем секрете.
ca-crt.pem - сертификат центра сертификации. его нужно импортировать в устройство.

-subj содержит публичную информацию о сертификате. Привел полный пример.

CA готов и теперь можно выпустить первый сертификат, это будет сертификат сервера. Для получения сертификата нужно создать приватный ключ. Создание ключа для сервера:

Bash:
openssl genrsa -out server-key.pem 4096

Вывод:

server-key.pem - приватный ключ сервера

Теперь для ключа server-key.pem можно запросить сертификат. Да, можно сразу сертификат на основе приватного ключа, но неподписанный никем сертификат сервера вызовет подозрения у браузера. Чтобы браузер доверял серверу нужно, чтобы сертификат выпустил CA. А CA надо попросить. Делается это через создание запроса на сертификат.

При создании запроса на сертификат сервера нужно прописать домены на которые распространяется действие сертификата. Для этого нужны 2 конфига. Первый can.conf:

Bash:
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = US
ST = VA
L = SomeCity
O = MyCompany
OU = MyDivision
CN = server.localhost
[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = server.localhost
DNS.2 = *.server.localhost

Конфиг содержит ответы на вопросы задаваемые командой создания запроса на сертификат. Создание запроса:

Bash:
openssl req -new -key server-key.pem -out server-csr.pem -config can.conf

server-csr.pem - файл запроса на сертификат. его нужно передать центру сертификации, чтобы тот смог выпустить подписанный сертификат.

И второй конфиг который нужен команде выпуска сертификата v3.ext.

Bash:
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer:always
keyUsage               = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign
subjectAltName         = DNS:server.localhost, DNS:*.server.localhost
issuerAltName          = issuer:copy

Информация, что указывается в конфигах, содержит данные владельца сертификата. Тут же нужно указать и доменные адреса, которые этот сертификат может посещать. Если этой инфы не будет, то вы можете авторизовать пользователя, но браузер будет выбрасывать ошибку, что сертификат сервера недействителен.

И, от лица центра сертификации, выполняем выпуск сертификата и подпись его CA.

Bash:
openssl x509 -req -days 9999 -in server-csr.pem -CA ca-crt.pem -CAkey ca-key.pem \
  -CAcreateserial -out server-crt.pem -extfile v3.ext

Выхлоп:

server-crt.pem - сертификат сервера подписанный CA.
Теперь нужно сделать ключ для клиента. Кстати, клиент может сам сгенерировать себе приватный ключ.

Bash:
openssl genrsa -out client-root-key.pem 4096

Вот:

client-root-key.pem - приватный ключ пользователя

Теперь нужно создать запрос на выпуск сертификата. Да, все по аналогии с сертификатом сервера.

Bash:
openssl req -new -key client-root-key.pem -out client-root-csr.pem -subj "/C=EN/ST=root"

В -subj /C=EN/ST=root после ST задал имя пользователя в Django. Эта строка будет передана в функцию, которая должна вернуть данные о пользователе.

Итог:

client-root-csr.pem - запрос на создание само-подписного сертификата для клиента.

Клиент передает мне запрос и Я выпускаю для него сертификат, с подписью CA.

Bash:
openssl x509 -req -days 9999 -in client-root-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out client-root-crt.pem

Результат:

client-root-crt.pem - сертификат для пользователя.

Браузер не понимает такой формат, поэтому нужно конвертировать его в PKCS#12. Это придется делать пользователю, так как мне его приватный ключ неизвестен.

Bash:
openssl pkcs12 -export -in client-root-crt.pem -inkey client-root-key.pem -out client-root.p12 -password pass:

Если хочется запаролить ключ, то надо убрать -password pass:.

Получилось:

client-root.p12 - сертификат в формате понятном браузеру

Ура! SSL пройден и у тебя на руках все необходимые сертификаты.

Part 2. NGINX
Нужно настроить веб-сервер на работу с нашими сертификатами. Мне нравится nginx. И для начала нужно сформировать файл с ключами сервера и CA сертификатом.

Bash:
cat server-crt.pem ca-crt.pem server-key.pem > ssl_bundle.pem

Этого достаточно чтобы уже сайт заработал через ssl. А для авторизации по клиентским ключам нужно еще создать базу клиентских сертификатов.

Bash:
cat client-root-crt.pem ca-crt.pem > client.certs.pem

Вот теперь все готово, чтобы сформировать конфигурационный файл виртуального сервера, который будет работать под само-подписным сертификатом и авторизовывать клиентов по их ключу. Выдать который могу только Я.

server.localhost.conf:

NGINX:
server {
    server_name server.localhost;
    listen 443 ssl;
    # сертификат сервера
    ssl_certificate /ssl/server.localhost/ssl_bundle.pem;
    # приватный ключ сервера
    ssl_certificate_key /ssl/server.localhost/server-key.pem;

    ssl_protocols TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    # требуется авторизация
    ssl_verify_client on;
    # сертификаты авторизованных клиентов
    ssl_client_certificate /ssl/server.localhost/client.certs.pem;

    client_max_body_size 100M;

    location / {
        proxy_pass http://127.0.0.1:8000;
        # оригинальный хост к которому обращается клиент
        proxy_set_header Host $host;
        # оригинальный IP клиента
        proxy_set_header X-Forwarded-Host $remote_addr;
        # Схема которую использует клиент
        proxy_set_header X-Forwarded-Proto  $scheme;
     
        # возвращает результат поверки сертификата. SUCCESS если успешно.
        proxy_set_header X-SSL-AUTHENTICATED  $ssl_client_verify;
        # строка `ST=root,C=EN`.
        proxy_set_header X-SSL-USER-DN  $ssl_client_s_dn;
        # возвращает число дней, оставшихся до истечения срока действия клиентского сертификата
        proxy_set_header X-SSL-REMAIN  $ssl_client_v_remain;
        # если убрать.
        # авторизация будет вызываться только при посещении [SSLCLIENT_]LOGIN_URL
        proxy_set_header X-REST-API  1;
    }
}

Чтобы nginx узнал о наличии нашего виртуального хоста, нужно добавить путь к своему файлу конфигурации виртуального сервера в настройки nginx: /etc/nginx/nginx.conf

user www-data;

NGINX:
<...>

http {

    <...>
 
    include /etc/nginx/conf.d/*.conf;
    # или линкануль файл конфика сюда
    include /etc/nginx/sites-enabled/*;
    # за мной повторять не обязательно
    include /server.localhost.conf;
}

<...>

Нужно позаботиться о правах и разрешить читать этот файл:

Bash:
chmod +r /server.localhost.conf

Тест настроек nginx запускается следующей командой:

Bash:
nginx -t

А после изменений конфигов nginx, его надо перезапустить.

Bash:
service nginx restart

Победа. Nginx готов к работе.

Django
Теперь нужно настроить Django, таким образом, чтобы она авторизовывала клиента по данным его ssl ключа.

Статья не о том как ставить Python, настраивать окружения и ставить зависимости. Тем не менее это нужно сделать.

requirements.txt:

Код:
Django==4.2.2
django-ssl-auth==2.2.0
gunicorn==20.1.0

Следующие настройки Django нужно задать:

Python:
# Проверяет результат ssl авторизации
MIDDLEWARE.append('django_ssl_auth.SSLClientAuthMiddleware')

AUTHENTICATION_BACKENDS = [
    # Генерирует клиентские куки (авторизует клиента)
    'django_ssl_auth.SSLClientAuthBackend'
]

# Сообщаем Django, где искать оригинальный тип протокола клиента
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# функция которая должна вернуть пользовательские данные
USER_DATA_FN = 'django.contrib.auth.models.User'

# Позволяет создавать пользователя если его нет
AUTOCREATE_VALID_SSL_USERS = True

Congratulations! Поздравляю.

Finish
Теперь нужно импортировать в браузер сертификат CA и клиентский ключ в формате PKCS#12. При попытке посетить сайт, браузер попросит выбрать сертификат для авторизации. В нормальном случае, выбор будет из одного элемента. Кликаем ОК и откроется сайт.

Артифакты клиент:

  • client-root.p12 - сертификат для браузера
  • client-root-crt.pem - это сертификат пользователя
  • client-root-key.pem - это ключ пользователя
  • ca-crt.pem - это сертификат центра сертификации, который тоже нужно добавить в браузе
Hello, friend. Next, I will tell you how to set up client authentication using a self-signed certificate.

I searched a lot of articles on configuring ssl certificates, and this one is the result of my research.

Part 1. SSL
Version info:

Bash:
$ openssl version
OpenSSL 1.1.1n
$ nginx -v
nginx version: nginx/1.18.0
$ python3 -V
Python 3.11.4

We start with SSL and first we need to issue a key and a CA certificate. The CA certificate/key will be used to sign other certificates. The CA key must be kept secret and the CA certificate must be added to the device so that there is no warning when your domain is visited. In other words, the CA certificate allows the browser to verify the authenticity of the certificate of the visited resource.

To issue a CA certificate, run the command :)

Bash:
openssl req -new -x509 -days 9999 -keyout ca-key.pem -out ca-crt.pem -nodes \
-subj "/C=EN/ST=InAttack/L=InAttack/O=UCH/OU=CA/CN=CA/emailAddress=ca@localhost"

Result:

* ca-key.pem - the CA private key.
* ca-crt.pem - CA certificate. it must be imported into the device.

CA is ready. And now you can issue the first certificate. To get the certificate you must create a private key. Creating a key for the server:

Bash:
openssl genrsa -out server-key.pem 4096

Output:

server-key.pem - server private key

Now you can request a certificate for the key `server-key.pem`. Yes, you can immediately certificate based on a private key, but unsigned by anyone server certificate will cause some suspicion to the browser. For the browser to trust the server the certificate has to be issued by a CA. And you have to ask for a CA. This is done by creating a request for a certificate.

First, copy your config: `can.conf` and change what you want to change in it.

can.conf:

Bash:
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = US
ST = VA
L = SomeCity
O = MyCompany
OU = MyDivision
CN = server.localhost
[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = server.localhost
DNS.2 = *.server.localhost

The config contains answers to the questions posed by the command to create a certificate request. Creating a request:

Bash:
openssl req -new -key server-key.pem -out server-csr.pem -config can.conf

server-csr.pem - The certificate request file must be sent to the certificate authority so that it can issue a signed certificate.

Now you can issue a certificate. This also requires a config:
Код:
v3.ext
.

Bash:
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer:always
keyUsage               = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign
subjectAltName         = DNS:server.localhost, DNS:*.server.localhost
issuerAltName          = issuer:copy

The information that is specified in the configs contains the data of the certificate owner. Here you should also specify the domain addresses that this certificate can visit. If this information is missing, you can authorize a user, but the browser will throw an error that the server certificate is invalid.

And, on behalf of the Certification Authority, we issue the certificate and sign its CA.

Bash:
openssl x509 -req -days 9999 -in server-csr.pem -CA ca-crt.pem -CAkey ca-key.pem \
  -CAcreateserial -out server-crt.pem -extfile v3.ext

Exhaust:

server-crt.pem - server certificate signed by CA

Now you need to make a key for the client. By the way, the client can generate its own private key.

Bash:
openssl genrsa -out client-root-key.pem 4096

Here:

client-root-key.pem - user private key

Now we need to create a request to issue a certificate. Yes, everything is similar to the server certificate.

Bash:
openssl req -new -key client-root-key.pem -out client-root-csr.pem -subj "/C=EN/ST=root"

In -subj /C=EN/ST=root after ST set the user name in Django. This string will be passed to the function that should return the user data.

Bottom line:

client-root-csr.pem - request to create a self-signed certificate for the client.

The client sends me a request and I issue a certificate for him, with the signature of CA.

Bash:
openssl x509 -req -days 9999 -in client-root-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out client-root-crt.pem

Result:

client-root-crt.pem - certificate for the user.

The browser does not understand this format, so you have to convert it to `PKCS#12`. This will have to be done by the user as I do not know his private key.

Bash:
openssl pkcs12 -export -in client-root-crt.pem -inkey client-root-key.pem -out client-root.p12 -password pass:

If you want to password the key, you need to remove
Код:
-password pass:
.

Here we have it:

client-root.p12 - A certificate in a format understandable to the browser

Hooray! SSL passed and you have all the necessary certificates.

Part 2. NGINX

We need to configure the web server to work with our certificates. I like nginx. And first we need to generate a file with the server keys and CA certificate.

Bash:
cat server-crt.pem ca-crt.pem server-key.pem > ssl_bundle.pem

This is enough to make the site work with ssl. And for authorization by client keys you need to create a base of client certificates.

Bash:
cat client-root-crt.pem ca-crt.pem > client.certs.pem

Now we are ready to generate a virtual server configuration file, which will run under a self-signed certificate and authorize clients with their key. Issue which only I can.

server.localhost.conf:

NGINX:
server {
    server_name server.localhost;
    listen 443 ssl;
   # server certificate bundled with CA certificate
    ssl_certificate /ssl/server.localhost/ssl_bundle.pem;
    # server private key
    ssl_certificate_key /ssl/server.localhost/server-key.pem;

    ssl_protocols TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    # ssl verification required
    ssl_verify_client on;
    # db with clients sertificates
    ssl_client_certificate /ssl/server.localhost/client.certs.pem;

    client_max_body_size 100M;

    location / {
        proxy_pass http://127.0.0.1:8000;
        # original client host
        proxy_set_header Host $host;
        # original client IP address
        proxy_set_header X-Forwarded-Host $remote_addr;
        # original client IP address
        proxy_set_header X-Forwarded-Proto  $scheme;
     
        # returns the result of certificate verification. SUCCESS if successful.
        proxy_set_header X-SSL-AUTHENTICATED  $ssl_client_verify;
        # issuer DN `ST=root,C=EN`.
        proxy_set_header X-SSL-USER-DN  $ssl_client_s_dn;
        # returns the number of days remaining until the expiration of the client certificate
        proxy_set_header X-SSL-REMAIN  $ssl_client_v_remain;
        # Authorization anywere
        proxy_set_header X-REST-API  1;
    }
}

To make nginx aware of our virtual host, we need to add the path to our virtual server configuration file to nginx settings: /etc/nginx/nginx.conf

user www-data;

NGINX:
<...>

http {

    <...>
 
    include /etc/nginx/conf.d/*.conf;
    # or link the conf file here
    include /etc/nginx/sites-enabled/*;
    # you don't have to repeat after me
    include /server.localhost.conf;
}

<...>

We need to take care of the rights and allow this file to be read:

Bash:
chmod +r /server.localhost.conf

The nginx configuration test is run with the following command:

Bash:
nginx -t

And after changing the nginx configs, it has to be restarted.

Bash:
service nginx restart

Victory. Nginx is now ready to work.

Django
Now you need to configure Django so that it will authorize the client according to its ssl key.

This article is not about installing Python, setting up environments, and installing dependencies. Nevertheless, it should be done.

requirements.txt:

Код:
Django==4.2.2
django-ssl-auth==2.2.0
gunicorn==20.1.0

The following Django settings need to be set:

Python:
# Checks ssl authorization result
MIDDLEWARE.append('django_ssl_auth.SSLClientAuthMiddleware')

AUTHENTICATION_BACKENDS = [
    # Generates client cookies (authorizes the client)
    'django_ssl_auth.SSLClientAuthBackend'
]

# Tell Django where to look for the original client protocol type
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# function which should return user data
USER_DATA_FN = 'django.contrib.auth.models.User'

# Allows you to create a user if no user exists
AUTOCREATE_VALID_SSL_USERS = True

Congratulations!

Finish
Now you need to import the CA certificate and the client key in `PKCS#12` format into the browser. When you try to visit the site, the browser will ask you to select a certificate for authorization. In the normal case, there will be one item to choose from. Click OK and the site will open.

Client artifacts

  • client-root.p12 - certificate for the browser
  • client-root-crt.pem - is the user certificate
  • client-root-key.pem - is the user key
  • ca-crt.pem - is a certificate of the CA, which must also be added to the browser

P.S. Забыл еще добавить кое-что.
К файлу client-root.p12 нужно относиться также как и к приватному ключу.
Файл client-root.p12 импортируется на вкладке Privacy & Security -> View Certificates -> Your Certificates. А файл ca-crt.pem на вкладке Authorities
А и еще, чтобы указать короткий вариант subj нужно закрывать слешем. Например: -subj "/CN=Certificate Aothority/"
 
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх