Статьи

Авторизация по SSL сертификатам

Протокол SSL позволяет организовать доступ к сайту (или его части) путем авторизации по клиентским сертификатам. При попытке входа  на защищенный ресурс, сервер запрашивает у клиента сертификат. Если проверка сертификата проходить успешно, клиент получает доступ к защищенным ресурсам.

Весьма удобно.

Реализовать этот механизм можно с помощью Apache и его модуля mod_ssl. Для этого понадобится:

Конфигурация сервера для запроса и проверки клиентских сертификатов

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

SSLCACertificateFileАбсолютный путь к файлу доверенного сертификата (CA)
SSLVerifyClient requireТребование предоставить сертификат для авторизации.
Если сертификат не будет предоставлен — доступ будет запрещен.

Секция VirtualHost для citename.ru должна выглядеть так:

/etc/apache2/vhosts.d/citename_ssl_vhost.conf

<VirtualHost 11.222.33.4:443>
    ServerName citename.ru
    ServerAdmin Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра.

    DocumentRoot «/var/www/localhost/htdocs»
    <Directory «/var/www/localhost/htdocs»>
        Options Indexes FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>

    ErrorLog /var/log/apache2/ssl_citename_error_log

    <IfModule log_config_module>
        TransferLog /var/log/apache2/ssl_citename_access_log
        CustomLog /var/log/apache2/ssl_citename_request_log \
                  «%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \»%r\» %b»
    </IfModule>

    SSLEngine              on
    SSLVerifyClient require
    SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL

    SSLCertificateFile     /etc/apache2/ssl/citename-CA.crt
    SSLCertificateKeyFile  /etc/apache2/ssl/citename-CA.key
    SSLCACertificateFile   /etc/apache2/ssl/citename-CA.crt
    
    <FilesMatch «\.(cgi|shtml|phtml|php)$»>
        SSLOptions +StdEnvVars
    </FilesMatch>
</VirtualHost>

Теперь можно сказать Apache перечитать конфигурацию

# /etc/init.d/apache2 reload

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

Подготовим структуру каталогов и файлов для хранилища сертификатов.

# mkdir /etc/ssl/citename.ru
# cd /etc/ssl/citename.ru
# mkdir db
# mkdir db/certs
# mkdir db/newcerts
# touch db/index.txt
# echo "01" > db/serial
# chmod 700 ./

В каталоге db/certs будем хранить выданные сертификаты. В файле db/index.txt будут храниться данные о выданных сертификатах. А в файле db/serial — серийный номер для нового сертификата.

Создаем конфигурационный файл для подписи сертификатов.

/etc/ssl/citename.ru/citename-CA.conf

[ ca ]
default_ca             = CA_CITENAME          # Секция по умолчанию для подписи сертификатов

[ CA_CITENAME ]
droot                  = /etc/ssl/citename.ru # Корневой каталог хранилища
dir                    = $droot/db            # Каталог базы хранилища
certs                  = $dir/certs           # Каталог сертификатов
new_certs_dir          = $dir/newcerts        # Каталог для новых сертификатов (pem)

database               = $dir/index.txt       # Файл базы сертификатов
serial                 = $dir/serial          # Файл серийного номера

# Файл доверенного сертификата
certificate            = /etc/apache2/ssl/citename-CA.crt
# Закрытый ключ доверенного сертификата
private_key            = /etc/apache2/ssl/citename-CA.key

default_days           = 365                  # Срок действия нового сертификата (дни)
default_crl_days       = 7                    # Срок действия списка отозванных сертификатов
default_md             = md5                  # Использовать алгоритм MD5

policy                 = policy_citename      # Политика секции

[ policy_citename ]
countryName            = optional             # Необязательный параметр
stateOrProvinceName    = optional             # …………………..
localityName           = optional             # …………………..
organizationName       = optional             # …………………..
organizationalUnitName = optional             # …………………..
commonName             = supplied             # обязательный параметр
emailAddress           = supplied             # …………………

Создаем запрос на клиентский сертификат.

# openssl req -new -newkey rsa:1024 -nodes -keyout user01.key -days 365 \
-subj "/C=RU/ST=Arkh/L=Arkh/O=OAO/OU=Sales/CN=user01/emailAddress=userЭтот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра." \
-out user01.csr

Аргументы команды:

reqГенерация нового сертификата
-newЗапрос на подпись сертификата
-newkey rsa:1024Новый закрытый RSA ключ длиной 1024 бита
-nodesНе шифровать закрытый ключ
-keyout user01.keyЗакрытый ключ сохранить в user01.key
-days 365Срок действия сертификата
-subjИнформация о владельце сертификата.
 

C — Двухсимвольное обозначение страны
ST — Республика/Регион/Край/Область
L — Населённый пункт
O — Организация
OU — Подразделение организации
CN — Имя сертификата. Если генерится для сервера — должно указываться имя сервера.
emailAddress — и так вроде понятно

-out user01.csrЗапрос на сертификат сохранить в user01.csr

Результатом команды будут два файла: закрытый ключ user01.key и запрос на сертификат user01.csr. Проверим что у нас получилось.

# openssl req -noout -text -in user01.csr
Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: C=RU, ST=Arkh, L=Arkh, O=OAO, OU=Sales, CN=user01/emailAddress=Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра.
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (1024 bit)
                Modulus:
                    00:d9:18:df:76:81:90:3b:f1:2f:05:bd:e1:bc:a1:
                    a8:0f:6d:ad:92:8c:ae:86:28:79:16:db:6a:7f:1a:
                    d7:38:98:78:e3:52:70:26:9a:b2:9c:1f:80:f7:d6:
                    99:82:f7:55:7d:a2:57:78:63:28:cd:70:93:96:a6:
                    d3:40:55:20:d0:45:f2:7d:8f:d1:83:0d:50:2b:d3:
                    ed:71:c6:2d:af:0a:8b:92:55:2b:25:17:22:3d:71:
                    db:ee:ef:95:e7:52:d6:aa:46:31:e2:ee:c5:09:78:
                    d4:78:a7:f0:f0:0b:4f:bb:38:2b:3b:2d:46:99:49:
                    6b:aa:42:e0:67:7c:1c:4c:5b
                Exponent: 65537 (0x10001)
        Attributes:
            a0:00
    Signature Algorithm: sha1WithRSAEncryption
         14:81:32:d3:32:ef:a8:fd:03:7a:91:0c:7c:73:b7:9c:a8:59:
         3a:18:27:30:48:6a:0c:9a:47:1f:91:12:2e:5a:8e:fd:15:37:
         05:c4:6f:04:16:51:f0:7b:b6:50:ce:08:b1:ce:5f:e6:4a:a1:
         f2:df:c5:e6:fb:cd:29:2d:32:fc:6b:cc:52:52:f6:ba:4b:88:
         d3:cd:97:14:7c:49:f5:03:82:ca:14:74:d0:6f:20:07:bc:8e:
         42:41:3c:61:17:a5:36:32:e8:08:95:18:c7:f8:63:5f:18:82:
         76:70:65:b1:c9:fe:d9:5d:e3:cf:f8:c4:08:54:dd:ca:af:f5:
         77:96
# openssl rsa -noout -text -in user01.key
Private-Key: (1024 bit)
modulus:
    00:d9:18:df:76:81:90:3b:f1:2f:05:bd:e1:bc:a1:
    a8:0f:6d:ad:92:8c:ae:86:28:79:16:db:6a:7f:1a:
    d7:38:98:78:e3:52:70:26:9a:b2:9c:1f:80:f7:d6:
    99:82:f7:55:7d:a2:57:78:63:28:cd:70:93:96:a6:
    d3:40:55:20:d0:45:f2:7d:8f:d1:83:0d:50:2b:d3:
    ed:71:c6:2d:af:0a:8b:92:55:2b:25:17:22:3d:71:
    db:ee:ef:95:e7:52:d6:aa:46:31:e2:ee:c5:09:78:
    d4:78:a7:f0:f0:0b:4f:bb:38:2b:3b:2d:46:99:49:
    6b:aa:42:e0:67:7c:1c:4c:5b
publicExponent: 65537 (0x10001)
privateExponent:
    5a:c5:85:99:bd:2e:9b:81:8a:91:b2:05:12:a3:dc:
    eb:26:86:ae:81:d7:ef:0c:39:25:0f:75:05:d4:29:
    2c:e6:c3:94:f8:c1:1f:c3:0a:ef:30:54:f2:4b:6e:
    40:4e:3e:16:9b:ac:4b:0f:da:dd:9b:36:7a:85:22:
    4b:01:cd:07:c3:32:26:31:d8:b1:fd:fa:d0:64:8b:
    3d:48:ba:be:f0:3a:82:3a:08:b8:da:18:7c:41:2f:
    05:7f:bc:09:25:21:a2:17:ec:b0:dd:36:e8:79:f4:
    4a:70:14:f1:d0:22:ad:8a:0b:d8:30:2e:ee:50:f3:
    77:c2:6d:48:c1:a5:dc:41
prime1:
    00:ed:13:50:fb:cd:03:83:1d:d3:48:02:3b:bd:12:
    b8:9a:bd:4d:4a:6e:b3:53:16:b6:67:f0:74:53:89:
    1f:2c:45:46:16:36:ee:88:aa:7c:b5:2a:f0:c0:c0:
    b3:ee:24:2f:b3:7c:19:9a:c8:8a:09:18:79:a4:a8:
    25:38:12:28:2b
prime2:
    00:ea:6d:4b:d1:b4:d3:ee:c0:24:1f:83:61:a5:67:
    33:18:da:1c:90:cf:23:bb:06:c7:3e:21:c0:77:00:
    9c:1e:52:18:38:23:61:93:27:a1:d0:61:1f:ad:0c:
    14:ad:d2:ae:eb:4e:7d:c8:03:89:f4:8a:a3:c1:2b:
    51:64:48:a4:91
exponent1:
    00:95:ca:5e:a0:ba:28:3d:ef:da:4e:e5:1a:59:9c:
    3a:87:8a:94:0b:33:66:9a:58:ff:67:2c:c6:53:01:
    90:70:a8:54:60:34:d5:02:04:b6:46:c1:9a:dc:2e:
    e5:80:d1:dc:51:cb:57:62:34:d3:02:6c:34:6f:94:
    cd:ef:5f:89:81
exponent2:
    26:19:4b:38:32:b6:3a:d8:19:46:d1:d8:5d:c4:4e:
    e6:9c:14:06:68:d3:ba:c2:98:40:fd:c5:44:d1:e1:
    8d:7f:f4:15:b3:92:59:13:18:d6:3f:e2:a1:02:14:
    9e:47:5e:4c:39:be:71:72:39:ca:77:79:b3:9c:31:
    a7:25:b3:31
coefficient:
    19:e7:3a:5b:2d:75:4f:26:ab:f1:4b:8a:9e:c1:32:
    40:9e:6e:a8:bb:8a:5f:36:8a:f1:30:39:37:01:59:
    6c:7e:67:35:3f:bd:fc:3e:eb:fb:b9:a6:af:7c:9d:
    7f:c3:83:d1:6e:11:d6:0d:56:05:9b:07:fd:27:32:
    22:8c:cf:61

Подпишем сертификат ключом сервера.

openssl ca -config citename-CA.conf -in user01.csr -out user01.crt -batch

Результатом команды будет вывод на экран что-то вроде этого:

Using configuration from citename-CA.conf
Check that the request matches the signature
Signature ok
The Subject’s Distinguished Name is as follows
countryName           :PRINTABLE:’RU’
stateOrProvinceName   :ASN.1 12:’Arkh’
localityName          :ASN.1 12:’Arkh’
organizationName      :ASN.1 12:’OAO’
organizationalUnitName:ASN.1 12:’Sales’
commonName            :ASN.1 12:’user01′
emailAddress          :IA5STRING:’Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра.
Certificate is to be certified until Jan 30 18:58:53 2016 GMT (365 days)

Write out database with 1 new entries
Data Base Updated

И файл подписанного сертификата user01.crt.

Аргументы команды:

caПодпись запроса на сертификат доверенным сертификатом
-config citename-CA.confИспользовать конфигурационный файл citename-CA.conf
-in user01.csrФайл запроса на сертификат находится в user01.csr
-out user01.crtПодписанный сертификат сохранить в user01.crt
-batchНе задавать вопросов и автоматически сертифицировать

Проверим что получилось.

# openssl x509 -noout -text -in user01.crt
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number: 2 (0x2)
    Signature Algorithm: md5WithRSAEncryption
        Issuer: C=RU, ST=Arkh, L=Arkh, O=OAO, OU=Sales, CN=citename.ru/emailAddress=Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра.
        Validity
            Not Before: Jan 30 18:58:53 2018 GMT
            Not After : Jan 30 18:58:53 2016 GMT
        Subject: C=RU, ST=Arkh, L=Arkh, O=OAO, OU=Sales, CN=user01/emailAddress=Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра.
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (1024 bit)
                Modulus:
                    00:d9:18:df:76:81:90:3b:f1:2f:05:bd:e1:bc:a1:
                    a8:0f:6d:ad:92:8c:ae:86:28:79:16:db:6a:7f:1a:
                    d7:38:98:78:e3:52:70:26:9a:b2:9c:1f:80:f7:d6:
                    99:82:f7:55:7d:a2:57:78:63:28:cd:70:93:96:a6:
                    d3:40:55:20:d0:45:f2:7d:8f:d1:83:0d:50:2b:d3:
                    ed:71:c6:2d:af:0a:8b:92:55:2b:25:17:22:3d:71:
                    db:ee:ef:95:e7:52:d6:aa:46:31:e2:ee:c5:09:78:
                    d4:78:a7:f0:f0:0b:4f:bb:38:2b:3b:2d:46:99:49:
                    6b:aa:42:e0:67:7c:1c:4c:5b
                Exponent: 65537 (0x10001)
    Signature Algorithm: md5WithRSAEncryption
         72:e5:e6:7d:e2:c1:fd:63:d1:55:72:96:2c:92:e2:1b:7f:b8:
         fc:d7:0e:ce:72:dc:24:3d:f4:db:27:79:26:c8:17:15:7f:fe:
         c4:36:6e:32:9e:40:20:c5:b6:30:ae:ee:3c:12:36:3a:45:1b:
         cc:d5:92:f6:72:d7:9d:84:b8:71:16:03:26:a9:2b:43:2a:68:
         06:91:ff:76:5b:46:98:07:18:d5:2f:2c:81:97:5c:80:f7:1d:
         55:d5:13:2e:25:10:82:ea:33:13:3c:a5:1d:c2:18:dd:e5:7c:
         f0:05:1e:ae:2a:80:01:3f:69:f4:c5:7f:da:18:b8:e9:29:90:
         67:0c

Подготовим сертификат для передачи пользователю.

# openssl pkcs12 -export -in user01.crt -inkey user01.key -out user01.p12 \
-certfile /etc/apache2/ssl/citename-CA.crt -passout pass:passwordkey

 Аргументы команды:

pkcs12Работать с файлом в формате PKCS#12.
-exportЭкспортировать сертификат в файл
-in user01.crtКлиентский сертификат
-inkey user01.keyЗакрытый ключ клиентского сертификата
-certfileДоверенный сертификат
-out user01.p12Сертификат в формате PKCS#12 для передачи пользователю
-passout pass:passwordkeyЗакрыть файл паролем passwordkey

Клиентский сертификат готов. Хранится он в файле user01.p12. Теперь его можно передать пользователю для установки в браузер.

Переместим все созданные файлы в каталог db/certs на хранение.

# mv ./user01.* db/certs/

Скрипт для создания клиентских сертификатов

Если сертификаты нужно создавать на регулярной основе — можно воспользоваться скриптом

mk-user-certs

#!/bin/bash
# script version 0.1
#########################################################################################
NO_ARGS=0
E_OPTERROR=65

### ================================================================================= ###
###                         ПЕРЕМЕННЫЕ (ПРАВИТЬ ПОД СЕБЯ)                             ###
BASE_DIR=»/etc/ssl/citename.ru»            # Путь к хранилищу сертификатов
CA_CFG=»$BASE_DIR/citename-CA.conf»        # Конфигурационный файл для подписи
CERTS=»$BASE_DIR/db/certs»                 # Каталог для хранения сертификатов
CA_FILE=»/etc/apache2/ssl/citename-CA.crt» # Доверенный сертификат (Им подписывам)
RAR=»/opt/bin/rar»                         # RAR. Потому что можно им паролить архив
### ================================================================================= ###

usage () {
  echo «Скрипт `basename $0` предназначен для создания клиентских SSL сертификатов.»
  echo «»
  echo «Использование: `basename $0` -c C -t ST -l L -o O -u OU -n CN -e eml -p pw -r -s»
  echo -e »    \033[1mОпции:\033[0m»
  echo »    -c    Двухсимвольный код страны. C сертификата»
  echo »    -e    E-Mail сертификата. Также используется для отправки сертификата»
  echo »          пользователю если установлена опция -s. Обязательная опция!»
  echo »    -l    Населенный пункт. L сертификата.»
  echo »    -n    Наименование (CN) сертификата. Также используется для наименования файлов»
  echo »          сертификата. Обязательная опция.»
  echo »    -o    Организация. O сертификата.»
  echo »    -p    Пароль сертификата. Также используется для пароля архива, если указана -s.»
  echo »    -r    Показывать содержимое сертификатов.»
  echo »    -s    Отправить упакованный сертификат пользователю по электронной почте,»
  echo »          указанной в опции -e»
  echo »    -t    Республика/Регион/Край/Область. ST сертификата.»
  echo »    -u    Подразделение организации для которой выдается сертификат. OU сертификата.»
  echo -e »    \033[1mОБРАТИТЕ ВНИМАНИЕ!\033[0m Опции \033[1m-n\033[0m и \033[1m-e\033[0m являются ОБЯЗАТЕЛЬНЫМИ.»
}

# У getopts есть неприятная фича/баг. Если у опции с аргументом не введен аргумент,
# а за ней введена следующая опция, то аргументом первой опции будет вторая опция…
# Такой хоккей нам не нужен. (с) Будем с ним бороться с помощью argchk.
argchk () {

if [[ $OPTARG =~ ^-[n/p/e/c/t/l/o/u]$ ]]
then
  echo «Неправильный аргумент $OPTARG для опции -$Option!»
  usage
  exit 1
fi
}

if [ $# -eq «$NO_ARGS» ]  # Сценарий вызван без аргументов?
then
  usage                   # Если запущен без «аргуменотов» — вывести справку
  exit $E_OPTERROR        # и выйти с кодом ошибки
fi

while getopts «rsn:p:e:c:t:l:o:u:» Option
do
  case $Option in
    n     ) argchk
            C_NAME=»$OPTARG-$(date ‘+%F’)» # добавляем дату к имени сертификата, чтобы в базе
                                           # не было совпадений имен сертификатов
            CN=»/CN=$OPTARG»;;
    p     ) argchk
            PW=$OPTARG;;
    e     ) argchk
            EML=$OPTARG
            E=»/emailAddress=$OPTARG»;;
    c     ) argchk
            C=»/C=$OPTARG»;;
    t     ) argchk
            ST=»/ST=$OPTARG»;;
    l     ) argchk
            L=»/L=$OPTARG»;;
    o     ) argchk
            O=»/O=$OPTARG»;;
    u     ) argchk
            OU=»/OU=$OPTARG»;;
    r     ) SH_RES=1;;
    s     ) SEND_EMAIL=1;;
    *     ) echo «Выбран недопустимый ключ.»

exit $E_OPTERROR;;   # ПО-УМОЛЧАНИЮ
  esac
done
shift $(($OPTIND — 1))

# Проверка обязательных опций
if [ -z $CN ] || [ -z $E ]
then
  echo «Опции -n и -e являются ОБЯЗАТЕЛЬНЫМИ!»
  usage
  exit 5
fi

SUBJ=»$C$ST$L$O$OU$CN$E»

# Проверим указан ли пароль
if [ -z $PW ]
then
  RARPW=»»
else
  RARPW=»-p$PW»
fi

# Создаем запрос на сертификат
openssl req -new -newkey rsa:1024 -nodes -keyout «$BASE_DIR/$C_NAME.key» -subj «$SUBJ» -out «$BASE_DIR/$C_NAME.csr»

if ! [ -z $SH_RES ] # Если указана опция -r
then
  # Показать содержимое запроса на сертификат
  openssl req -noout -text -in «$BASE_DIR/$C_NAME.csr»
fi

# Подписываем сертификат
openssl ca -config «$CA_CFG» -in «$BASE_DIR/$C_NAME.csr» -out «$BASE_DIR/$C_NAME.crt» -batch

if ! [ -z $SH_RES ] # Если указана опция -r
then
  # Показать содержимое сертификата

openssl x509 -noout -text -in «$BASE_DIR/$C_NAME.crt»
fi

# Упаковать сертификат в файл PKCS#12
openssl pkcs12 -export -in «$BASE_DIR/$C_NAME.crt» -inkey «$BASE_DIR/$C_NAME.key» \
               -certfile «$CA_FILE» -out «$BASE_DIR/$C_NAME.p12» -passout pass:$PW

if [ $SEND_EMAIL -eq 1 ] # Если указана опция -s
then
  # Упаковать файл PKCS#12 в архив
  $RAR a -ep $RARPW «$BASE_DIR/$C_NAME.rar» «$BASE_DIR/$C_NAME.p12»
  # и отправить пользователю по электронке
  uuencode «$BASE_DIR/$C_NAME.rar» «$C_NAME.rar» | mail -s «$C_NAME.rar» $EML && \
  echo «Сертификат отправлен по адресу: $EML»
fi

mv $BASE_DIR/$C_NAME.* «$CERTS»

exit 0

Для корректной работы скрипта требуется наличие установленных программ: mail-client/mailx (mail — консольный клиент для отправки почтовых сообщений, app-arch/rar (архиватор), app-arch/sharutils (перекодировщик двоичных файлов в текстовый вид для почтовых вложений).