SSL note pro-domo.ddns.net Note du certificat du blog audité par www.ssllabs.com.

En 2014, l'EFF, Mozilla et bien d'autres sponsors se sont fixés pour but le déploiement massif de l'HTTPS sur Internet dans le cadre de l'opération Encrypting the Web. Aujourd'hui nous disposons d'un projet mature permettant une migration rapide et gratuite des services vers ce protocole.

Voici un bref aperçu de ce qui se cache derrière le HTTPS avec entre autres les certificats SSL, et la configuration des serveurs.

Sommaire

Le protocole HTTPS

L'HyperText Transfer Protocol Secure, plus connu sous l'abréviation HTTPS — littéralement « protocole de transfert hypertexte sécurisé » — est la combinaison du HTTP avec une couche de chiffrement comme SSL ou TLS.
HTTPS permet au visiteur de vérifier l'identité du site web auquel il accède, grâce à un certificat d'authentification émis par une autorité tierce, réputée fiable (et faisant généralement partie de la liste blanche des navigateurs internet).
Source: Wikipédia.

Avoir un site web consultable en HTTPS signifie donc qu'il faut détenir un certificat SSL signé par une autorité de certification. Les certificats auto-signés sont toujours envisageables mais lèvent des alertes dans les navigateurs.

Comme dans le chiffrement des mails, vous n'êtes vraiment sûrs de l'identité de votre correspondant que si vous avez vérifié l'empreinte de sa clé, en sa présence, ou que sa clé a été signée par d'autres personnes de confiance.

Oui mais voilà, dans le monde du web au lieu de vérifier nous-mêmes les certificats des sites que l'on visite, on fait confiance à une autorité tierce qui s'en occupe à notre place. Le problème est que le fait de faire appel à une poignée d'autorités de certification a placé des entreprises dans une situation de monopole qu'elles n'ont pas tardé à rentabiliser.

Si l'on se rend sur le site de l'ancienne autorité VeriSign, (rachetée au passage par Symantec à hauteur de 1,28 milliard de dollars) on s'aperçoit que la certification SSL est lucrative.

Vous aurez ainsi le plaisir d'acquérir un certificat pour votre blog de quelques pages, valide pendant 1 an, en déboursant à peine 349€. Si vous optez pour une validité de 2 ans, vous économisez 100€ en déboursant 598€.

Ces prix sont pour le petit certificat de base; si vous optez pour des goodies tels que l'affichage d'une barre d'adresse verte avec le nom de l'autorité de certification ainsi que le nom de votre entreprise, il faudra débourser 1299€ pour 1 an.

Note

Des initiatives comme StartSSL permettent toutefois depuis quelques années, un accès abordable/gratuit à la technologie SSL.

La sécurité sur l'Internet des GAFA a un prix. Encore faudrait-il que cela soit vraiment sécurisé. La présentation "Defeat SSL certificates" publiée à la Blackhat de 2009 par Moxie Marlinspike éliminera bien des illusions. Les illusions restantes devraient disparaitre avec les révélations de Snowden en 2013.

La mise en place d'un certificat SSL se heurte donc à ceci:

  • Le coût,
  • La configuration des serveurs,
  • La gestion des certificats (création/signature/révocation/renouvellement).

L'initiative Let's Encrypt

Let's Encrypt logo

Let's Encrypt est une nouvelle autorité de certification délivrant des certificats gratuits et disposant d'une API permettant d'automatiser une grande partie de leur déploiement. Ces certificats sont reconnus par la plupart des navigateurs depuis octobre 2015 (cf. Wikipédia).

Certbot logo

Certbot est le client de Let's Encrypt, développé également par l'EFF pour assurer ce déploiement; il peut même configurer seul des serveurs comme Apache et Nginx.

Sur la page du projet vous pouvez sélectionner votre serveur, puis votre plate-forme; des instructions adaptées apparaîtront ensuite pour installer Certbot, puis pour générer/renouveler les certificats. Le tout se fait en quelques très courtes commandes.

L'initiative est libre mais pas exempt de coût, les donations sont comme toujours bienvenues. Tout ceci paraît peut-être futile mais il s'agit bien d'une avancée majeure en faveur de la vie privée sur le Web.

Configuration de Nginx

Après installation et exécution de Certbot (voir la commande ci-dessous), nous obtenons une série de certificats et clés dont la signification est décrite dans la doc.

$ sudo certbot certonly --webroot -w /var/www/html/ -d pro-domo.ddns.net

Renouvellement des certificats:

$ sudo certbot renew --post-hook "service nginx restart"

PS: Utiliser --force-renewal pour forcer un renouvellement.

Certificats et clés:

$ ls -la /etc/letsencrypt/live/pro-domo.ddns.net
    cert.pem -> ../../archive/pro-domo.ddns.net/cert1.pem
    chain.pem -> ../../archive/pro-domo.ddns.net/chain1.pem
    fullchain.pem -> ../../archive/pro-domo.ddns.net/fullchain1.pem
    privkey.pem -> ../../archive/pro-domo.ddns.net/privkey1.pem

Ajoutons une clé Diffie-Hellman (DH) de même niveau plus élevé que le certificat de letsencrypt (4096bits):

$ openssl dhparam -out /etc/letsencrypt/live/pro-domo.ddns.net/dhparam.pem 4096

Si la génération est trop longue, ajouter le paramètre -dsaparam pour aller plus vite (voir DSA-like DH parameters.

Éditons la configuration du virtual host de Nginx /etc/nginx/sites-enabled/mon_site. La plupart des commentaires provient directement de la documentation du module ssl de Nginx. Seules les 2 premières lignes sont liées aux nouveaux certificats; le reste concerne la configuration SSL générale de ce type de serveur.

Notez que le protocole TLS est utilisé, et non le protocole SSL qui présente de sérieuses vulnérabilités.

ssl_certificate /etc/letsencrypt/live/pro-domo.ddns.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/pro-domo.ddns.net/privkey.pem;
# All versions of nginx as of 1.4.4 rely on OpenSSL for input parameters to Diffie-Hellman (DH).
# Unfortunately, this means that Ephemeral Diffie-Hellman (DHE) will use OpenSSL's defaults,
# which include a 1024-bit key for the key-exchange. Since we're using a 2048-bit certificate,
# DHE clients will use a weaker key-exchange than non-ephemeral DH clients.
# https://wiki.mozilla.org/Security/Server_Side_TLS#DHE_handshake_and_dhparam
ssl_dhparam /etc/letsencrypt/live/pro-domo.ddns.net/dhparam.pem;

# SSLv3 is disabled ref: POODLE; (TLSv1.3 on nginx 1.13+)
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
# Specifies the enabled ciphers.
# full list can be viewed using the “openssl ciphers” command.
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
# server ciphers should be preferred over client ciphers when using the SSLv3 and TLS
ssl_prefer_server_ciphers on;

# Enables or disables session resumption through TLS session tickets
# The current recommendation for web servers is to enable session resumption
# and benefit from the performance improvement, but to restart servers daily when possible.
# This ensure that sessions get purged and ticket keys get renewed on a regular basis.
#ssl_session_tickets off;

# Specifies a time during which a client may reuse the session parameters.
ssl_session_timeout 1d;

# Sets the types and sizes of caches that store session parameters.
# shared: shared between all worker processes.
# The cache size is specified in bytes; one megabyte can store about 4000 sessions.
# A cache with the same name can be used in several virtual servers.
ssl_session_cache shared:SSL:1m;

# OCSP est un protocole Internet permettant de vérifier la validité
# d'un certificat numérique TLS en temps-réel auprès de l'autorité ayant émis le certificat.
# Il permet au possesseur d'un certificat de supporter le coût de vérification de ce dernier en
# fournissant une réponse OCSP horodatée et signée par l’Autorité de certification (CA),
# attachée (agrafée) à l’échange initial TLS, permettant d'économiser au client TLS la vérification auprès de la CA.
# Many browsers will fetch OCSP from Let’s Encrypt when they load your site. This is a performance and privacy problem.
# By turning on OCSP Stapling, you can improve the performance of your website,
# provide better privacy protections for your users, and help Let’s Encrypt efficiently
# serve as many people as possible.

# For the OCSP stapling to work, the certificate of the server certificate issuer should be known.
# If the ssl_certificate file does not contain intermediate certificates, the certificate of
# the server certificate issuer should be present in the ssl_trusted_certificate file.
ssl_stapling on;

# Enables or disables verification of OCSP responses by the server.
ssl_stapling_verify on;

# Letsencrypt:
# cert.pem contains the server certificate by itself,
# and chain.pem contains the additional intermediate certificate or certificates
# that web browsers will need in order to validate the server certificate.
# If you provide one of these files to your web server, you must provide both of them,
# or some browsers will show “This Connection is Untrusted” errors for your site, some of the time.
ssl_trusted_certificate  /etc/letsencrypt/live/pro-domo.ddns.net/chain.pem;

# Solve error: no resolver defined to resolve ocsp.int-x3.letsencrypt.org [...]
# Using a resolver on localhost (resolver 127.0.0.1) is the only safe option.
# http://blog.zorinaq.com/nginx-resolver-vulns/
resolver 192.168.1.1;

# HTTP Strict-Transport-Security (HSTS)
# Header with a recommended default max-age of sixty days.
# If the customer needs to move to a hosting provider that doesn’t offer HTTPS,
# the cached HSTS setting in browsers will make their site unavailable.
# On certificate errors, browsers do not allow security exceptions for hostnames with an active HSTS header.
add_header Strict-Transport-Security "max-age=518400; includeSubDomains" always;

# Public Key Pinning (not supported by Edge 15-, Safari 10-)
# The first time a web server tells the client by using the HPKP HTTP header,
# which public keys belong to it, the client saves this information for a given period of time.
# When the client visits the server again, it expects a certificate containing a public key
# whose fingerprint is saved. If the server has an unknown public key, the client must present
# a warning to the user.
# https://developer.mozilla.org/fr/docs/Web/Security/Public_Key_Pinning
# Declare that a website's HTTPS certificate should only be treated as valid if the
# public key is contained in a specified list to prevent MITM attacks that use valid CA-issued certificates.
# add_header Public-Key-Pins 'pin-sha256="base64+primary=="; pin-sha256="base64+backup=="; max-age=518400; includeSubDomains';

Maintenant qu'un certificat sérieux et reconnu a été installé, on peut forcer l'usage du HTTPS sur tout le site, et en profiter pour migrer le service vers la récente norme HTTP2:

server {
        # FORCE HTTPS EVERYWHERE
        listen 80;
        listen [::]:80;
        server_name _;
        # Permanent redirection of all requests to the matching HTTPS page
        return 301 https://$host$request_uri;
}

server {
        # DISABLE HTTP
        #listen 80;
        #listen [::]:80;

        # SSL configuration
        # default_server : preference to ssl
        # http 2 => SSL only
        listen 443 ssl http2 default_server;
        listen [::]:443 ssl http2 default_server;

        ....
}

Test de la configuration

Vérifier l'intégralité de la configuration: ssllabs.com.

Vérifier la chaine de certification: whatsmychaincert.com.

Test de l'OCSP Stapling:

$ openssl s_client -connect pro-domo.ddns.net:443 -tls1_2  -tlsextdebug  -status

Rechercher dans les résultats un paragraphe "OCSP Response Data:"; si vous obtenez "OCSP response: no response sent", alors l'OCSP Stapling est désactivé.

Benchmarks

Pour commencer les benchmarks j'ai utilisé l'outil historique ab (Apache HTTP server benchmarking tool) disponible dans les dépôts.

Plus tard je me suis aperçu en visionnant les logs que cet outil n'émettait que des requêtes selon la norme HTTP/1.0. Rien d'étonnant pour un outil conçu en 1996 bien qu'il faille noter que la norme HTTP/1.1 date tout de même de 1997 (soit à peine 1 an après la norme 1.0)...

Dans les autres tests, j'ai utilisé le programme h2load inclus dans le paquet nghttp2 des distributions récentes. Pour ma part j'ai dû le compiler depuis le dépôt sur GitHub.

Tests avec Apache Benchmark Tool

Note

Les tests de ce paragraphe sont à rejeter. Le logiciel et le protocole utilisés sont dépassés depuis 20ans. Ils sont présentés à titre d'exemples afin de montrer que parfois on se retrouve à ne tester rien d'autre que l'outil de benchmarking !

HTTPS + HTTP/1.0:

$ ab -n 2000 -c 4 https://pro-domo.ddns.net/blog/index.html
This is ApacheBench, Version 2.3 <$Revision: 1604373 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Server Software:        nginx/1.11.10
Server Hostname:        pro-domo.ddns.net
Server Port:            443
SSL/TLS Protocol:       TLSv1.2,ECDHE-RSA-AES256-GCM-SHA384,2048,256

Document Path:          /blog/index.html
Document Length:        16127 bytes

Concurrency Level:      4
Time taken for tests:   32.919 seconds
Complete requests:      2000
Failed requests:        0
Total transferred:      32728000 bytes
HTML transferred:       32254000 bytes
Requests per second:    60.75 [#/sec] (mean)
Time per request:       65.839 [ms] (mean)
Time per request:       16.460 [ms] (mean, across all concurrent requests)
Transfer rate:          970.89 [Kbytes/sec] received

Connection Times (ms)
            min  mean[+/-sd] median   max
Connect:       54   60   8.2     56     116
Processing:     3    6   8.1      4      56
Waiting:        3    6   8.1      4      56
Total:         58   66  11.7     61     127

Percentage of the requests served within a certain time (ms)
50%     61
66%     66
75%     67
80%     68
90%     75
95%    103
98%    109
99%    111
100%    127 (longest request)

HTTP + HTTP/1.0:

$ ab -n 2000 -c 4 http://pro-domo.ddns.net/blog/index.html
This is ApacheBench, Version 2.3 <$Revision: 1604373 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Server Software:        nginx/1.11.10
Server Hostname:        pro-domo.ddns.net
Server Port:            80

Document Path:          /blog/index.html
Document Length:        16127 bytes

Concurrency Level:      4
Time taken for tests:   3.561 seconds
Complete requests:      2000
Failed requests:        0
Total transferred:      32728000 bytes
HTML transferred:       32254000 bytes
Requests per second:    561.58 [#/sec] (mean)
Time per request:       7.123 [ms] (mean)
Time per request:       1.781 [ms] (mean, across all concurrent requests)
Transfer rate:          8974.30 [Kbytes/sec] received

Connection Times (ms)
            min  mean[+/-sd] median   max
Connect:        1    2   0.4      2       4
Processing:     3    5   1.0      5      25
Waiting:        1    2   0.5      2       9
Total:          4    7   1.0      7      27

Percentage of the requests served within a certain time (ms)
50%      7
66%      7
75%      7
80%      8
90%      8
95%      8
98%      9
99%      9
100%     27 (longest request)

Voici la conclusion (fausse) que je m'apprếtais à faire:

Le passage au HTTPS est forcément couteux: Il faut 9.2 fois plus de temps pour prendre en charge les 2000 requêtes de test, et générer une page (65.8ms vs 7.1ms). Le serveur ne peut dorénavant gérer que 13 requêtes par seconde vs 94 en HTTP.

Tests avec h2load

HTTPS + HTTP/2.0:

$ ./h2load -n 2000 -c 4 https://pro-domo.ddns.net/blog/index.html
spawning thread #0: 4 total client(s). 2000 total requests
TLS Protocol: TLSv1.2
Cipher: ECDHE-RSA-AES256-GCM-SHA384
Server Temp Key: ECDH P-256 256 bits
Application protocol: h2

finished in 3.04s, 656.95 req/s, 10.19MB/s
requests: 2000 total, 2000 started, 2000 done, 2000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 2000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 31.02MB (32530196) total, 216.80KB (222000) headers (space savings 38.67%), 30.76MB (32254000) data
                    min         max         mean         sd        +/- sd
time for request:     3.18ms     24.22ms      5.20ms      1.93ms    83.90%
time for connect:    56.28ms    161.99ms    105.40ms     48.76ms    50.00%
time to 1st byte:    60.04ms    174.29ms    116.35ms     55.76ms    50.00%
req/s           :     164.28      209.87      186.94       22.61    50.00%

HTTPS + HTTP/1.1:

$ ./h2load -n 2000 -c 4 --h1 https://pro-domo.ddns.net/blog/index.html
spawning thread #0: 4 total client(s). 2000 total requests
TLS Protocol: TLSv1.2
Cipher: ECDHE-RSA-AES256-GCM-SHA384
Server Temp Key: ECDH P-256 256 bits
Application protocol: h2

finished in 2.95s, 678.14 req/s, 10.52MB/s
requests: 2000 total, 2000 started, 2000 done, 2000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 2000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 31.02MB (32530196) total, 216.80KB (222000) headers (space savings 38.67%), 30.76MB (32254000) data
                    min         max         mean         sd        +/- sd
time for request:     3.06ms     19.36ms      5.52ms       666us    83.50%
time for connect:    56.07ms    158.23ms    127.10ms     41.36ms    75.00%
time to 1st byte:    59.74ms    166.46ms    138.06ms     45.24ms    75.00%
req/s           :     169.56      183.04      173.01        5.79    75.00%

HTTP + HTTP/1.1:

$ ./h2load -n 2000 -c 4 --h1 http://pro-domo.ddns.net/blog/index.html
spawning thread #0: 4 total client(s). 2000 total requests
Application protocol: http/1.1

finished in 2.81s, 712.82 req/s, 11.12MB/s
requests: 2000 total, 2000 started, 2000 done, 2000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 2000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 31.20MB (32716148) total, 372.95KB (381900) headers (space savings 0.00%), 30.76MB (32254000) data
                    min         max         mean         sd        +/- sd
time for request:     2.49ms     12.36ms      5.58ms       681us    80.00%
time for connect:     2.45ms      3.31ms      2.68ms       361us    75.00%
time to 1st byte:     4.91ms      6.19ms      5.55ms       471us    50.00%
req/s           :     178.24      178.62      178.45        0.15    50.00%

Il est difficile de différencier ici les 3 technologies puisqu'en réalité le facteur limitant est la vitesse du réseau ethernet 100MB.

Voici quelques éléments remarqués:

  • HTTP2 semble plus efficace de HTTPS avec une attente entre la requête et sa réponse de 116ms vs 138ms. HTTP quant à lui permet une durée d'attente de 5.5ms.

  • Côté charge CPU la charge monte à 1 en https/1.1, et à 0.5 en http/1.1. Impossible de faire des mesures en http2 car h2load s'arrête en cours de route (Nginx coupe t'il la connexion lorsque un CPU atteint 100% ?).

  • L'écart type en https (55,76ms en http2 et 45.24ms en http1) est très important par rapport à celui mesuré en http (471us). La variabilité dans la gestion des requêtes est donc très importante.

Conclusion

Difficile de statuer sur un éventuel ralentissement dû au passage à HTTPS. Toutefois, il est certain qu'un goulet d'étranglement apparaît au niveau du CPU pour les très gros sites disposant d'une très grosse bande passante.

L'idée d'un HTTPS forcément lent est donc en passe d'être une légende urbaine, reposant essentiellement sur le mauvais usage de mauvais outils de benchmarking. ab est un outil conçu lors de la préhistoire du Web; il n'a jamais été mis à jour et produit forcément des résultats alarmants avec les technologies modernes (13 requêtes/sec vs 700 en HTTPS...)

Notons aussi que les tests sur HTTP2 sont probablement loin de la réalité. En effet, cette norme apporte entre autres, les fonctionnalités suivantes:

  • Exécution des requêtes en parallèle, à la place d'une file d'attente,
  • Compression des en-têtes HTTP,
  • Transfert des pages en tant que binaires, pas en tant que fichiers texte,
  • Multiplexage des connexions TCP (plusieurs requêtes en attente par connexion TCP): dorénavant une seule connexion devrait suffire.

En pratique les différences https/http devraient être invisibles pour les visiteurs.

Sources