Universal https using GOST certificate

When trying to organize https connections for various web services using GOST encryption, there were always questions with visitors whose browsers do not support GOST algorithms. When setting up an https connection, it seemed logical to give the client a certificate, depending on the algorithms supported by its system, but until recently, practical implementations of this approach have not occurred to me.

Once I read the kyprizel article “How and why we do TLS in Yandex” , where it was mentioned that OpenSSL starting from version 1.0.2 allows you to assign a server certificate depending on client parameters, but there is no implementation on the side of the Web server. Nginx 1.11.0 introduced this feature:
ssl_certificate and ssl_certificate_key directives can now be specified several times to download certificates of various types (for example, RSA and ECDSA).

I decided to assemble a stand in order to test the possibility of organizing a https web server with GOST certificates for visitors with the installed GOST encryption cryptographic provider and ECDSA certificates for the rest.

VM with Ubuntu 16.04.1 LTS acted as a test bench

Build nginx


I compiled nginx with the OpenSSL 1.0.2h static library

cd /opt/src/
#Скачиваем nginx
wget http://nginx.org/download/nginx-1.11.2.tar.gz
#Скачиваем openssl
wget https://openssl.org/source/openssl-1.0.2h.tar.gz
#Распаковываем
tar -zxvf nginx-1.11.2.tar.gz
tar -zxvf openssl-1.0.2h.tar.gz
cd nginx-1.11.2
#И собираем
./configure --prefix=/opt/work/nginx2 --user=nginx --group=nginx --with-http_ssl_module --with-openssl=/opt/src/openssl-1.0.2h/
make
make install

Next, you need to configure OpenSSL to support GOST algorithms. On the network, even the lazy one will be able to find tuning materials.

my openssl.cnf
cat /opt/src/openssl-1.0.2h/.openssl/ssl/openssl.cnf
openssl_conf=openssl_def
HOME                    = .
RANDFILE                = $ENV::HOME/.rnd
oid_section             = new_oids
[ new_oids ]
tsa_policy1 = 1.2.3.4.1
tsa_policy2 = 1.2.3.4.5.6
tsa_policy3 = 1.2.3.4.5.7
[ ca ]
default_ca      = CA_default
[ CA_default ]
dir             = ./demoCA
certs           = $dir/certs
crl_dir         = $dir/crl
database        = $dir/index.txt
new_certs_dir   = $dir/newcerts
certificate     = $dir/cacert.pem
serial          = $dir/serial
crlnumber       = $dir/crlnumber
crl             = $dir/crl.pem
private_key     = $dir/private/cakey.pem
RANDFILE        = $dir/private/.rand
x509_extensions = usr_cert
name_opt        = ca_default
cert_opt        = ca_default
default_days    = 365
default_crl_days= 30
default_md      = default
preserve        = no
policy          = policy_match
[ policy_match ]
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional
[ policy_anything ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional
[ req ]
default_bits            = 2048
default_keyfile         = privkey.pem
distinguished_name      = req_distinguished_name
attributes              = req_attributes
req_extensions = v3_req
x509_extensions = v3_ca
string_mask = utf8only
[ req_distinguished_name ]
countryName                     = Country Name (2 letter code)
countryName_default             = RU
countryName_min                 = 2
countryName_max                 = 2
stateOrProvinceName             = State or Province Name (full name)
stateOrProvinceName_default     = Moscow region
localityName                    = Locality Name (eg, city)
localityName_default            = Moscow
0.organizationName              = Organization Name (eg, company)
0.organizationName_default      = JSC Example
organizationalUnitName          = Organizational Unit Name (eg, section)
organizationalUnitName_default  = It Department
commonName                      = Common Name (e.g. server FQDN or YOUR name)
commonName_max                  = 64
emailAddress                    = Email Address
emailAddress_max                = 64
[ req_attributes ]
challengePassword               = A challenge password
challengePassword_min           = 4
challengePassword_max           = 20
unstructuredName                = An optional company name
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = test.example.ru
DNS.2 = gost.example.ru
[ usr_cert ]
basicConstraints=CA:FALSE
nsComment                       = "OpenSSL Generated Certificate"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
[ v3_ca ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = CA:true
[ crl_ext ]
authorityKeyIdentifier=keyid:always
[ proxy_cert_ext ]
basicConstraints=CA:FALSE
nsComment                       = "OpenSSL Generated Certificate"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
[ tsa ]
default_tsa = tsa_config1
[ tsa_config1 ]
dir             = ./demoCA
serial          = $dir/tsaserial
crypto_device   = builtin
signer_cert     = $dir/tsacert.pem
certs           = $dir/cacert.pem
signer_key      = $dir/private/tsakey.pem
default_policy  = tsa_policy1
other_policies  = tsa_policy2, tsa_policy3
digests         = md5, sha1
accuracy        = secs:1, millisecs:500, microsecs:100
clock_precision_digits  = 0
ordering                = yes
tsa_name                = yes
ess_cert_id_chain       = no
[openssl_def]
engines = engine_section
[engine_section]
gost = gost_section
[gost_section]
engine_id = gost
default_algorithms = ALL
CRYPT_PARAMS = id-Gost28147-89-CryptoPro-A-ParamSet

We issue certificates for a test web resource. I did not burden myself with self-signed certificates, but I created requests and signed them in the Test Center of CryptoPro and with Chinese “friends ”, which have long been issuing free certificates.

#Формируем закрытый ключ
openssl genrsa -out test.example.ru.key 2048
#генерируем запрос
openssl req -new -sha256 -key test.example.ru.key -out test.example.ru.csr
#генерирум закрытый ключ по алгоритму ГОСТ
openssl genpkey -algorithm gost2001 -pkeyopt paramset:A -out gost.example.ru.key
#генерируем запрос
openssl req -engine gost -new -key gost.example.ru.key -out gost.example.ru.csr

We upload the received requests and sign it in the CA.

Configuring Nginx


Below is my Nginx Web server configuration file. You should pay attention to the duplicate ssl_certificate and ssl_certificate_key directives , which specify 2 certificates for one https server, as well as the ssl_ciphers line GOST2001-GOST89-GOST89: HIGH: MEDIUM , which determines the list and order of the encryption algorithms used.

user  nginx;
worker_processes  1;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       443 ssl;
        server_name  gost.example.ru;
        ssl_certificate      keys/gost.example.ru_bundle.crt;
        ssl_certificate_key  keys/gost.example.ru.key;
        ssl_certificate      keys/test.example.ru_bundle.crt;
        ssl_certificate_key  keys/test.example.ru.key;
        ssl_ciphers GOST2001-GOST89-GOST89:HIGH:MEDIUM;
        ssl_protocols   TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers  on;
        location / {
            proxy_pass http://192.168.1.249;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}


In the vastness of the web, I found the init script to run more conveniently:

/etc/init.d/nginx
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DESC="Nginx Daemon"
NAME=nginx
PREFIX=/opt/work/nginx2
DAEMON=$PREFIX/sbin/$NAME
CONF=$PREFIX/conf/$NAME.conf
PID=$PREFIX/logs/$NAME.pid
SCRIPT=/etc/init.d/$NAME
if [ ! -x "$DAEMON" ] || [ ! -f "$CONF" ]; then
    echo -e "\033[33m $DAEMON has no permission to run. \033[0m"
    echo -e "\033[33m Or $CONF doesn't exist. \033[0m"
    sleep 1
    exit 1
fi
do_start() {
    if [ -f $PID ]; then
        echo -e "\033[33m $PID already exists. \033[0m"
        echo -e "\033[33m $DESC is already running or crashed. \033[0m"
        echo -e "\033[32m $DESC Reopening $CONF ... \033[0m"
        $DAEMON -s reopen -c $CONF
        sleep 1
        echo -e "\033[36m $DESC reopened. \033[0m"
    else
        echo -e "\033[32m $DESC Starting $CONF ... \033[0m"
        $DAEMON -c $CONF
        sleep 1
        echo -e "\033[36m $DESC started. \033[0m"
    fi
}
do_stop() {
    if [ ! -f $PID ]; then
        echo -e "\033[33m $PID doesn't exist. \033[0m"
        echo -e "\033[33m $DESC isn't running. \033[0m"
    else
        echo -e "\033[32m $DESC Stopping $CONF ... \033[0m"
        $DAEMON -s stop -c $CONF
        sleep 1
        echo -e "\033[36m $DESC stopped. \033[0m"
    fi
}
do_reload() {
    if [ ! -f $PID ]; then
        echo -e "\033[33m $PID doesn't exist. \033[0m"
        echo -e "\033[33m $DESC isn't running. \033[0m"
        echo -e "\033[32m $DESC Starting $CONF ... \033[0m"
        $DAEMON -c $CONF
        sleep 1
        echo -e "\033[36m $DESC started. \033[0m"
    else
        echo -e "\033[32m $DESC Reloading $CONF ... \033[0m"
        $DAEMON -s reload -c $CONF
        sleep 1
        echo -e "\033[36m $DESC reloaded. \033[0m"
    fi
}
do_quit() {
    if [ ! -f $PID ]; then
        echo -e "\033[33m $PID doesn't exist. \033[0m"
        echo -e "\033[33m $DESC isn't running. \033[0m"
    else
        echo -e "\033[32m $DESC Quitting $CONF ... \033[0m"
        $DAEMON -s quit -c $CONF
        sleep 1
        echo -e "\033[36m $DESC quitted. \033[0m"
    fi
}
do_test() {
    echo -e "\033[32m $DESC Testing $CONF ... \033[0m"
    $DAEMON -t -c $CONF
}
do_info() {
    $DAEMON -V
}
case "$1" in
 start)
 do_start
 ;;
 stop)
 do_stop
 ;;
 reload)
 do_reload
 ;;
 restart)
 do_stop
 do_start
 ;;
 quit)
 do_quit
 ;;
 test)
 do_test
 ;;
 info)
 do_info
 ;;
 *)
 echo "Usage: $SCRIPT {start|stop|reload|restart|quit|test|info}"
 exit 2
 ;;
esac
exit 0

That's it, run the web server and check. In Internet Explorer 11 with the installed cryptographic provider CryptoPro CSP, the visitor is given a GOST certificate signed by CRYPTO-PRO Test Center 2 when accessing gost.example.ru , Mozilla Firefox does not support GOST algorithms and the visitor receives a “normal” certificate signed by WoSign CA Limited .

Internet explorer



Mozilla firefox



From my point of view, it turned out to be a rather interesting option for using the technology, I would like to hope that support will soon appear in openresty ssl_certificate_by_lua_block .

Also popular now: