📜 ⬆️ ⬇️

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. It seemed a logical decision when installing an https connection to give the client a certificate depending on the algorithms supported by his system, but until recently I hadn’t met practical implementations of this approach.

I once read the kyprizel article “How and why we do TLS in Yandex” , where it was mentioned that since version 1.0.2 OpenSSL allows assigning a server certificate depending on client parameters, but there is no implementation on the Web server side. In Nginx 1.11.0, this feature has appeared:
The ssl_certificate and ssl_certificate_key directives can now be specified multiple times to download different types of certificates (for example, RSA and ECDSA).

I decided to put together a stand in order to test the possibility of organizing an https web server with GOST certificates for visitors with an established GOST encryption provider and ECDSA certificates for the rest.

VM with Ubuntu 16.04.1 LTS acted as a test bench

We collect nginx


I collected nginx with static library OpenSSL 1.0.2h
')
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 the GOST algorithms. Even a lazy person will be able to find customization materials on the network

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 (eg 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 begin to burden myself with self-signed certificates, but created requests and signed them in the Test Center of CryptoPRO and at the Chinese “friends ” who 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 unload received requests and sign in CA.

Configuring Nginx


Below is my Nginx web server configuration file. Attention should be paid to 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 defines the list and order of 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 an init script for a more convenient launch:

/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 

Everything, we start the web server and we check. In the Internet Explorer 11 browser with the CryptoPro CSP cryptographic provider installed, the visitor is given a GOST certificate signed by CRYPTO-PRO Test Center 2 when accessing gost.example.com , Mozilla Firefox does not support GOST algorithms, and the visitor receives a “regular” certificate signed by WoSign CA Limited .

Internet Explorer



Mozilla firefox



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

Source: https://habr.com/ru/post/306544/


All Articles