Протокол 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 (перекодировщик двоичных файлов в текстовый вид для почтовых вложений).

Добавить комментарий