Seguretat de la capa de transport (TLS)

Transport Security Layer (TLS) no fa que els servidors web siguin segurs.

La icona que apareix en el navegador quan la connexió és HTTPS no garantitza que la informació de la teva targeta de crèdit no s'acabi utilitzant per comprar un iPhone per a una altra persona.

El que fa TLS és:

  • Xifrar una connexió de xarxa durant el trànsit mitjançant criptografia simètrica (AES o ChaCha20)

  • Verificar l’autenticitat del servidor mitjançant criptografia asimètrica (RSA o EC) mijançant l’ús de certificats, documents digitals firmats per una autoritat de certificació que garantitza que el servidor és qui diu que és.

Encara que la majoria de connexions TLS són HTTPS, qualsevol aplicació compatible amb TLS pot aplicar TLS a qualsevol connexió de xarxa TCP o UDP.

Història

El primer navegador web el va crear Netscape Corporation.

L’any 1994 van crear un protocol de seguretat de la capa de transport, conegut com Secure Socket Layer, per establir una conexió segura entre el navegador Netscape i els servidors web.

Com sol ser habitual quan una és dissenya un pròtocol nou, aquest tenia bastants defectes, i Netscape va llançara ràpidament la versió 2 de SSL el 1995, i la versió ·el 1996.

De totes maneres el disseny criptogràfic bàsic de SSL era defectuós i molt difícil de solucionar.

L’any 1999 l’IETF va desenvolupar un protocol a partir de SSLv3, que va anomenar TLD per motius legals.

Com és d’esperar van anar apareixent noves versions de TLS: el 2006 TLSv1.1, el 2008 TLSv1.2 i el 2018 TLSv1.3.

** I tot això perquè interessa saber-ho?**

Perquè quan busques per internet sapigues que quan es parla de SSL o TLS estem parlant del mateix.

Durant molt de temps tothom ha fet servir el nom SSL, i per això l’eina que farem servir i que vas fer servir a criptografia s’anomena OpenSSL encara que fem connexions TLS.

No obstant, cada cop més el nom que es fa servir és TLS, perquè avui en dia només es considera segur la versió 3 de TLS.

Desde el 2021, la NSA i diversos organismes de seguretat d’altres països recomanen de manera molt ferma que ja no es faci servir TLSv2, perquè es considera insegur.

Ara el nom modern que es fa servis és TLS.

Per què TLS?

TLS no és l’únic protocol segur a Internet: tenim IPSec, Wireguard, OpenVPN i d’altres que ja no es fan servir.

Llavors que té d’especial TLS perqué sigui el protocol de connexió segura més utilitzat a Internet? TLS és un protocol genèric que es va dissenyar per permetre encriptar una connexió entre client i servidor, i que es pogués integrar amb una altre protocol de manera senzilla.

Netscape tenia que crear un protocol que fes que HTTP fos segur, sense tocar el protocol HTTP.

D’aquesta manera un client pot comprobar si el servidor fa servir TLS, si és així encriptar mitjançant TLS, i si no és així seguir amb el protocol sense encriptar si ho considera adient.

A més, d’aquesta manera el protocol TLS pot seguir evolucionat de manera independent del protocol que encripta, ja sigui HTTP, FTP, etc.

Per això, quan parlem de HTTPS potser estem parlant de HTTP sobre SSL (gens segur) o HTTP sobre TLS (més segur, i molt segur depenent de la versió de TLS)

openssl

OpenSSL (i forks com LibreSSL) són llibreries de propòsit general per a tot el que te a veure amb la criptografia i els aspectes relacionats.

Amb openssl pots crear un parell de claus assimètriques, signar fitxers digitalment, llegir fitxers ASN.1 i X.509, generar números aleatoris i verificar certificats TLS.

La majoria d'administradors de sistema no entenen les bases matemàtiques de la criptografia perquè és difícil d'entendre, molt més que les equacions diferencial multivariable.

Però per portar un patinet elèctric no cal conèixer electromècanica, només com fer funcionar el patinet.

L'únic que has d’entendre és que la criptografia és molt complexa, i openssl ha heretat aquesta complexitat i el tenir que ser compatible amb tota la seva història.

De totes maneres, com que avui en dia l’únic "segur" és TLSv1.3, hi ha llibreries noves que només implementes aquest protocol.

Tal com pots apendre a Criptografia una ordre openssl té aquesta sintaxis: `

$ openssl subcommand flags

El paràmetre subcommand determina amb quin conjunt de funcions criptogràfiques estem treballant.

Per exemple, la subordre genrsap genera un parell de claus assimètriques RSA, mentres que la subordre x509` té a veure amb els X.509.

Moltes subordres fan servir els mateixos flags per funcions semblants: per exepmple -in i -out per input i output, -text per format textual.

Tingues en compte que openssl no permet ajuntar flags com pots fer per exemple amb tar, com quan vols comprimir un conjunt de fitxers amb l’ordre tar -czvfp.

Per veure quina versió d'OpenSSL tens:

$ openssl version

OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022)

La versió final 3.0 d’OpenSSL es va publicar a finals del 2021: OpenSSL 3.0 Has Been Released!.

Per tenir més informació de com s’ha empaquetat i configurat la distribució concreta de openssl que estas fen servir:

$ openssl version -a

OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022)
built on: Fri Feb 16 08:51:30 2024 UTC
platform: debian-amd64
options:  bn(64,64)
compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -Wa,--noexecstack -g -O2 -ffile-prefix-map=/build/openssl-olCZw9/openssl-3.0.2=. -flto=auto -ffat-lto-objects -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security -DOPENSSL_TLS_SECURITY_LEVEL=2 -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_BUILDING_OPENSSL -DNDEBUG -Wdate-time -D_FORTIFY_SOURCE=2
OPENSSLDIR: "/usr/lib/ssl"
ENGINESDIR: "/usr/lib/x86_64-linux-gnu/engines-3"
MODULESDIR: "/usr/lib/x86_64-linux-gnu/ossl-modules"
Seeding source: os-specific
CPUINFO: OPENSSL_ia32cap=0xfedab2035f8bffff:0x1c2fb9

Moltes ordres estan dissenyades per funcionar amb altres ordres openssl mijantçant l’ús de pipes ( | ):

$ openssl s_client -showcerts -connect www.google.es:443 < /dev/null | openssl x509 -text -noout

depth=2 C = US, O = Google Trust Services LLC, CN = GTS Root R1
verify return:1
depth=1 C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
verify return:1
depth=0 CN = *.google.es
verify return:1
DONE
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            28:c3:ed:e3:65:48:2e:0a:0a:85:3d:e5:6b:53:0d:35
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
...

L’ordre s_client fa una funció semblant a netcat, però amb TLS, negociant una connexió TLS amb el servidor en el host i port que li diguis.

$ openssl s_client -connect www.google.es:443

CONNECTED(00000003)
depth=2 C = US, O = Google Trust Services LLC, CN = GTS Root R1
...

Es tracta d’una ordre interactiva com pots veure a continuació:

$ openssl s_client -connect www.google.es:443
...
Verify return code: 0 (ok)
---
GET / HTTP/1.1

---
...
read R BLOCK
HTTP/1.1 200 OK
Date: Sat, 01 Jun 2024 08:21:57 GMT
...

Amb el text GET / HTTP/1.1 envies una sol.licitut HTTP al servidor amb la ruta /.

Recorda que has de premer dos cops ENTER perquè s'enviï la sol.licitud!

Amb la lletra Q tanquem la sessió:

---
Q
DONE

El flag -showcert fa que es mostrin tots els certificats enviats pel servidor:

$ openssl s_client -showcerts -connect www.google.es:443

...
---
Certificate chain
 0 s:CN = *.google.es
   i:C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
   a:PKEY: id-ecPublicKey, 256 (bit); sigalg: RSA-SHA256
   v:NotBefore: May 13 07:47:02 2024 GMT; NotAfter: Aug  5 07:47:01 2024 GMT
-----BEGIN CERTIFICATE-----
MIIEjTCCA3WgAwIBAgIQKMPt42VILgoKhT3la1MNNTANBgkqhkiG9w0BAQsFADBG
...
EwO9FwFC4q9WKz7fAanM8Dg=
-----END CERTIFICATE-----

En l'exemple mostrem el certificat 0 pel domini *.google.es firmat per "Google Trust Services LLC".

Si no volem una sessió interactiva podem passar com a input /dev/null per tal de que openssl no esperi una entrada per teclat.

$ openssl s_client -showcerts -connect www.google.es:443 < /dev/null
...
---
DONE

Pots veure com la sessió es tanca automàticament.

Si vols fer un "parse" del certificat X.509 pots utilitzar l'ordre x509 amb el flag -noout per indicar que no et mostri el certificat codificat.

$ openssl s_client -showcerts -connect www.google.es:443 < /dev/null | openssl x509 -nout

depth=2 C = US, O = Google Trust Services LLC, CN = GTS Root R1
verify return:1
depth=1 C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
verify return:1
depth=0 CN = *.google.es
verify return:1
DONE
-----BEGIN CERTIFICATE-----
MIIEjTCCA3WgAwIBAgIQKMPt42VILgoKhT3la1MNNTANBgkqhkiG9w0BAQsFADBG
...
EwO9FwFC4q9WKz7fAanM8Dg=
-----END CERTIFICATE-----

D'aquesta manera podem veure de manera resumida la cadena de certificats.

El certificat està codificat en Base64.

Si vols veure el seu contingut pots utilitzar el flag -text:

$ openssl s_client -showcerts -connect www.redhat.com:443 < /dev/null | openssl x509 -nout -text

depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 Extended Validation Server CA
verify return:1
depth=0 jurisdictionC = US, jurisdictionST = Delaware, businessCategory = Private Organization, serialNumber = 2945436, C = US, ST = North Carolina, L = Raleigh, O = "Red Hat, Inc.", CN = www.redhat.com
verify return:1
DONE
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            08:09:81:5d:a4:40:48:15:29:29:a7:41:28:63:89:7c
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 Extended Validation Server C
...

Composant aquestes dues ordres amb una pipe ( | ) pots veure el certificat de qualsevol lloc web que es fa servir per autenticar els llocs web.

TLS versus DTLS

Els dos protocols a nivell de transport que es fan servir a Internet són TCP i UDP.

TLS, com http i altres protocols, només funcionen amb TCP.

No obstant, moltes aplicacions UDP també necessiten encriptar la connexió.

En lloc d'inventar un protocol completament nou, es va afegir un sistema de seguiment de l'estat al protocol TLS per gestionar tota la comptabilitat de la xarxa.

El resultat és el protocol Datagram Transport Layer Security (DTLS).

DTLS va ser dissenyat deliberadament per funcionar exactament com TLS.

El codi és diferent i l'estructura del paquet és diferent, però per a un administrador del sistema és el mateix. Un certificat TLS funciona bé a DTLS.

DTLS s'utilitza principalment en xarxes virtuals privades.

Suite de xifrat

Per establir una connexió segura es necessiten diferents eines criptogràfiques, i cada eina criptogràfica té diferents implementacions, i el client i el servidor s’han de posar d’acord de quines fer servir.

Un protocol TLS específica quines combinaciones es poden utilitzar de clau simètrica, asimètrica i hash.

Per veure tots els ciphers (“xifrats”) que té implementats ( flag -s) la teva distribució OpenSSL:

$ openssl ciphers -v -s
TLS_AES_256_GCM_SHA384         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(256)            Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256   TLSv1.3 Kx=any      Au=any   Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(128)            Mac=AEAD
...

Com pots veure són uns quants, 30 en total:

$ openssl ciphers -v -s | wc -l
30

Els que ens interessa són els ciphers que es poden fer servir en el protocol TLSv1.3:

$ openssl ciphers -v -s | grep TLSv1.3
TLS_AES_256_GCM_SHA384         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(256)            Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256   TLSv1.3 Kx=any      Au=any   Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(128)            Mac=AEAD

Pots veure que només en tenim tres per escollir:

Si vas a la wiki oficial veurás que el protocol TLSv1.3 inclou cinc xifrats posibles: TLS1.3 - OpenSSLWiki.

TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_GCM_SHA256
TLS_AES_256_GCM_SHA384
TLS_AES_128_CCM_SHA256
TLS_AES_128_CCM_8_SHA256

Però el nostre client openssl només en fa servir tres (considera que les variants de l'AES_128_CCM no aporten res).

Si vol establir una sessió TLSv1.3 amb un servidor haurà de negociar el xifratge amb el servidor i esperar que li accepti algun d’aquest tres

Simplificació

Una manera de que un sistema sigui segur és simplificar, i una de les grans novetats de TLSv1.3 va ser que l’algorisme d’intercanvi de claus (Kx) i el d’autenticacació (Au) no formen part de la Cipher Suite perquè no formen part de la negociació entre client i servidor:

Per aquest motiu en TLSv1.3 els valor de Kx i Au és any:

$ openssl ciphers -v -s | grep TLSv1.3
TLS_AES_256_GCM_SHA384         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(256)            Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256   TLSv1.3 Kx=any      Au=any   Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(128)            Mac=AEAD

Per tant, l'únic que s’ha de negociar en TLSv1.3 és si fem servir AES_128, AES_256 o CHACHA20.

Compara aquesta simplicitat amb el protocol TLSv1.2:

$ openssl ciphers -v -s | grep TLSv1.2
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256)            Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2 Kx=ECDH     Au=RSA   Enc=AESGCM(256)            Mac=AEAD
DHE-RSA-AES256-GCM-SHA384      TLSv1.2 Kx=DH       Au=RSA   Enc=AESGCM(256)            Mac=AEAD

Hi ha 19 implementacions de xifratges TLSv1.2:

$ openssl ciphers -v -s | grep TLSv1.2 | wc -l
19

Connexió TLS

Una forma de depurar servei de xarxa és no fer servir una aplicació client i connectar-te de manera interactiva amb el client amb una eina com netcat.

Si vull saber si es pot accedir a un servei SSH o SMTP puc utilitzar netcat per connectar-me al port 22 o 25.

A continuació tens un exemple de connexió a un servei SMTP (port 25):

$ nc test.mailu.io 25
220 test.mailu.io ESMTP ready
HELO xtec.dev
250 test.mailu.io
MAIL FROM: ddemingo@xtec.cat
250 2.0.0 OK
RCPT TO: admin@xtec.cat

Tens que buscar a Google o ChatGPT com es fa exactament, però funcionar funciona, l’objectiu era parlar directament amb un servidor SMTP 😀.

Si coneixes el protocol SMTP pots interacturar amb el servidor amb comandes de text.

Pots fer servir netcat amb qualsevol protocol basat en text i interactuar amb ell de manera nativa.

$ nc google.com 80
GET / HTTP/1.1

HTTP/1.0 200 OK
Date: Sat, 01 Jun 2024 09:07:59 GMT
...

Recorda! La sol.licitud HTTP acaba amb una línia en blanc. Per tant has d'apretar un altre cop ENTER per enviar la sol.licitut.

Però si la connexió està encriptada mitjançant TLS només rebràs dades binaries sense sentit o es produirà un error:

$ nc google.com 443
GET / HTTP/1.1

Hi ha variants de netcat, com la de nmap que poden gestionar connexions tls: https://nmap.org/ncat/.

Però nosaltres farem servir s_client per entendre com funciona una connexió TLS.

Connexió a un servei web

openssl et permet amb l'ordre s_client conectar-te a un servidor i interactuar amb ell a nivell de text.

Depuració

L'ordre s_client es va escriure per depurar connexions TLS.

Per tant, l'ordre s_client no fa gaire cas si rep un certificat que no és vàlid: l’accepta i continua.

$ openssl s_client -quiet -connect self-signed.badssl.com:443

depth=0 C = US, ST = California, L = San Francisco, O = BadSSL, CN = *.badssl.com
verify error:num=18:self-signed certificate
verify return:1
depth=0 C = US, ST = California, L = San Francisco, O = BadSSL, CN = *.badssl.com
verify return:1
GET / HTTP/1.1
Host: badssl.com

HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
...

Pots veure que openssl només t'informa que s'ha produït l'error num=18:self-signed certificate.

Faig servir el flag -quiet perquè no em mostri tota la informació de l’establiment de la connexió.

D’aquesta manera openssl silencia tot excepte un resum de la cadena de certificats.

En canvi, el comportament general de la majoria d’eines com curl és no acceptar un certificat que no està firmat per una autoritat de certificació:

$ curl https://self-signed.badssl.com:443

curl: (60) SSL certificate problem: self-signed certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

Si vols el mateix comportament amb openssl utilitza el flag -verify_return_error:

$ openssl s_client -quiet -verify_return_error -connect self-signed.badssl.com:443

depth=0 C = US, ST = California, L = San Francisco, O = BadSSL, CN = *.badssl.com
verify error:num=18:self-signed certificate
4017EC864D7F0000:error:0A000086:SSL routines:tls_post_process_server_certificate:certificate verify failed:../ssl/statem/statem_clnt.c:1883:

Fi de línia

En un text, com es marca una línia nova?

  • Unix (i per tant Linux) van decidir que seria amb el caràcter invisible LF (Line Feed)
  • Els Macs van decidir que seria amb el caràcter invisible CR (Carriage Return), però després van passar ràpidament a LF.
  • Windows va decidir que tots dos a la vegada: CR+LF.

I quina importància té això en TLS?

Doncs que molts protocols de serveis d’Internet fan servir text ("plain text"), i d’alguna manera s’ha de marcar el fi de línia.

Quan amb openssl et connectes amb l’argument -connect en un servei web, i escrius una ordre i apretes la tecla enter, openssl envia un caràcter LF.

Si el servidor espera un CR+LF això no funciona.

Per aquest motiu tens el flag -crlf per indicar a openssl que envii CR+LF enlloc de LF.

HTTP és un protocol que fa servir CR+LF per indicar una línia nova, ja que és l’estandard a Internet que es va especificar originalment en la RFC 158 com a part del protocol TELNET.

Aquesta convenció també la fant servir protocols com FTP i SMTP.

$ openssl s_client -quiet -connect www.microsoft.es:443
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root G2
verify return:1
depth=1 C = US, O = Microsoft Corporation, CN = Microsoft Azure RSA TLS Issuing CA 04
verify return:1
depth=0 C = US, ST = WA, L = Redmond, O = Microsoft Corporation, CN = *.oneroute.microsoft.com
verify return:1
GET / HTTP/1.1
HTTP/1.1 400 Bad Request
Content-Length: 0
Connection: close
Date: Sat, 01 Jun 2024 11:41:32 GMT
Server: Kestrel

40E7FB13A77F0000:error:0A000126:SSL routines:ssl3_read_n:unexpected eof while reading:../ssl/record/rec_layer_s3.c:308:

El servidor respon inmediatament perquè no interpreta lf com una nova linia, i per tant, encara queda un tros de sol.licitud per enviar.

L'error unexpected eof while ... és perqué espera la directiva Host.

Per tant caldrà fer servir el flag -crlf quan et connectes a un servei HTTP que es estricte al respecte:

$ openssl s_client -quiet -crlf -connect www.microsoft.es:443

depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root G2
verify return:1
depth=1 C = US, O = Microsoft Corporation, CN = Microsoft Azure RSA TLS Issuing CA 04
verify return:1
depth=0 C = US, ST = WA, L = Redmond, O = Microsoft Corporation, CN = *.oneroute.microsoft.com
verify return:1
GET / HTTP/1.1
Host: www.microsoft.es

HTTP/1.1 301 Moved Permanently
Content-Length: 0
Date: Sat, 01 Jun 2024 11:45:33 GMT
Server: Kestrel
Location: https://www.microsoft.com/es-es/
Strict-Transport-Security: max-age=31536000

Encara que moltes vegades no fa falta com havies vist fins ara:

$ openssl s_client -quiet -connect www.redhat.com:443

depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 Extended Validation Server CA
verify return:1
depth=0 jurisdictionC = US, jurisdictionST = Delaware, businessCategory = Private Organization, serialNumber = 2945436, C = US, ST = North Carolina, L = Raleigh, O = "Red Hat, Inc.", CN = www.redhat.com
verify return:1
GET / HTTP/1.1
Host: www.redhat.com

HTTP/1.1 301 Moved Permanently
Server: AkamaiGHost
...

Port dedicat

Els serveis de xarxa comuns com ara web, correu electrònic i FTP escolten en uns ports dedicats.

HTTP

Per exemple el servei HTTP normalment és troba en el port 80.

La manera més senzilla de crear un versió segura d’aquest servei web és utilitzar una altre port on es fa servir TLS per establir una connexió segura entre client i servidor.

El servei protegit per TLS més conegut és HTTPS que escolta al port 443.

HTTP no sap res de TLS, i podem treballar i desenvolupar en HTTP sense ocupar-nos de TLS com hem estat fins ara quan arrequem un servei nginx o apache per fer proves i apendre.

POP3

POP3 és un protocol com HTTP que funciona amb text.

Per realitzar un connexió segura a un servei web POP3 pots conectar al port que fa servir TLS per encriptar la connexió.

Pots mirar quina és la contrasenya a Demonstration server — Mailu.

$ openssl s_client -quiet -connect test.mailu.io:995

depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = mailu.io
verify return:1
+OK Dovecot ready.
USER admin@test.mailu.io
+OK
PASS Mailuxxxx
+OK Logged in.
LIST
+OK 0 messages:
.
QUIT
+OK Logging out.

També pots provar que passa amb gmail:

$ openssl s_client -quiet -crlf -connect imap.gmail.com:995
depth=2 C = US, O = Google Trust Services LLC, CN = GTS Root R1
verify return:1
depth=1 C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
verify return:1
depth=0 CN = imap.gmail.com
verify return:1
+OK Gpop ready for requests from 83.51.197.12 k16mb6507847wrq
USER ddemingo@xtec.cat
+OK send PASS
PASS password
-ERR [AUTH] Username and password not accepted.

No és un problema de contrasenya tal com pots veure en aquest article: Sign in with app passwords

Això és molt important, perqè a vegades una llibreria o una aplicació et diu que hi ha un error, però no et diu exactament quin.

Ordres de connexió

La sessió s_client roman oberta fins que la finalitzes.

Si no fas servir l’opció -quiet pots passar ordres a openssl:

$ openssl s_client -connect test.mailu.io:995
CONNECTED(00000003)
...
+OK Dovecot ready.
Q
DONE

Q ("quit") tanca la connexió TLS de manera "neta" (millor que CTRL-C).

En canvi quan faig servir el flag -quiet no puc.

$ openssl s_client -quiet -connect test.mailu.io:995

depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = mailu.io
verify return:1
+OK Dovecot ready.
Q
-ERR Unknown command.

Si vull puc passar l'ordre k a openssl perqué actualitzi la clau TLS 1.3:

$ openssl s_client -quiet -connect test.mailu.io:995
...
+OK Dovecot ready.
k
KEYUPDATE
Silenciant s_client

Com hem vist abans amb l’opció -quiet podem ometre tot el procés de negociació:

$ openssl s_client -quiet -connect www.apple.com:443
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA
verify return:1
depth=1 C = US, O = Apple Inc., CN = Apple Public EV Server RSA CA 2 - G1
verify return:1
depth=0 businessCategory = Private Organization, jurisdictionC = US, jurisdictionST = California, serialNumber = C0806592, C = US, ST = California, L = Cupertino, O = Apple Inc., CN = www.apple.com
verify return:1

Però si també vols veure un resum de les característiques TLS que s’han negociat, pots fer servir el flag -brief.

$ openssl s_client -quiet -brief -connect www.apple.com:443
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: businessCategory = Private Organization, jurisdictionC = US, jurisdictionST = California, serialNumber = C0806592, C = US, ST = California, L = Cupertino, O = Apple Inc., CN = www.apple.com
Hash used: SHA256
Signature type: RSA-PSS
Verification: OK
Server Temp Key: X25519, 253 bits

Versions específiques TLS

Quan un client TLS com s_client es connecta per primera vegada a un servidor, el client enumera les versions de TLS que admet, i el servidor tria la versió més alta que admet (la més segura).

Per provar un protocol determinat pots escollir la versió de TLS que vols fer servir-

En aquest exemple demanem al serviror utlitzar la versió TLSv1.2:

$ openssl s_client -quiet -brief -tls1_2 -connect www.apple.com:443
CONNECTION ESTABLISHED
Protocol version: TLSv1.2
Ciphersuite: ECDHE-RSA-AES256-GCM-SHA384
Peer certificate: businessCategory = Private Organization, jurisdictionC = US, jurisdictionST = California, serialNumber = C0806592, C = US, ST = California, L = Cupertino, O = Apple Inc., CN = www.apple.com
Hash used: SHA256
Signature type: RSA-PSS
Verification: OK
Supported Elliptic Curve Point Formats: uncompressed:ansiX962_compressed_prime:ansiX962_compressed_char2
Server Temp Key: ECDH, prime256v1, 256 bits

Pots veure que la llibreria OpenSSL que tenim instal.lada no implementa el protocol TLSv1.1, ni els protocols SSL que encara estan més obsolets:

$ openssl s_client -quiet -brief -tls1_1 -connect www.apple.com:443
400713098F7F0000:error:0A0000BF:SSL routines:tls_setup_handshake:no protocols available:../ssl/statem/statem_lib.c:104:

Si volem provar el protocol TLSv1.1 tindrem que fer servir una versió antiga d’ubuntu que tingui una versió antiga de openssl.

La manera més fàcil és amb Docker:

$ docker run --rm -it ubuntu:14.04 /bin/bash
... $ cat /etc/os-release | grep VERSION
    VERSION="14.04.6 LTS, Trusty Tahr"

Ja podem realitzar una connexió TLSv1.1:

... $ openssl version
    OpenSSL 1.0.1f 6 Jan 2014
    $ openssl s_client -connect tls-v1-1.badssl.com:101
    ...
    SSL-Session:
        Protocol  : TLSv1.1
    ...

Pots verificar que la connexió fa servir el protocol TLSv1.1:

I la connexió funciona, puc fer sol.licituts HTTP:

... ---
    GET / HTTP/1.1
    Host: tls-v1-1.badssl.com

    HTTP/1.1 200 OK
    Server: nginx/1.10.3 (Ubuntu)

Em puc connectar a www.google.com sense fer servir TLSv1.2:

... $ openssl s_client -no_tls1_2 -connect www.google.es:443
    CONNECTED(00000003)
    ...
    SSL-Session:
        Protocol  : TLSv1.1

Doncs si, han negociat fer servir TLSv1.1.

I sense TLSv1.2 i TLSv1.1 que passa?

... $ openssl s_client -no_tls1_2 -no_tls1_1 -connect www.google.es:443
    ...
    SSL-Session:
        Protocol  : TLSv1

Doncs llavors TLSv1.

I sense TLSv1.2,TLSv1.1 i TLSv1 que passa?

... $ openssl s_client -no_tls1_2 -no_tls1_1 -no_tls1 -connect www.google.es:443
    CONNECTED(00000003)
    139992654096032:error:14077102:SSL routines:SSL23_GET_SERVER_HELLO:unsupported protocol:s23_clnt.c:740:
    ...

Que els servidor de Google no admeten els vells protocol SSL, però admeten tots els TLS.

Google necessita que clients antics, sobretot versions antigues d’Android, puguin seguir accedint als seus servidors.

Si tens un mòbil antic amb un Android antic és el teu problema (als clients no els importa tant la seguretat).

En canvi, els servidors de Microsoft són més restrictius:

... $ openssl s_client -no_tls1_2 -connect www.microsoft.com:443
    CONNECTED(00000003)
    139805134345888:error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version:s23_clnt.c:770:
    ...

Ja pots sortir del contenidor:

... $ exit
    exit

Selecció de xifratge

També et pot interessar és saber si un servidor pot utilitzar un xifratge específic.

Pots forçar a openssl que en la negociació amb el servidor només accepti un xifratge específic per veure si el servidor el té implementat.

Mira els "cipers" que té TLSv1.3:

$ openssl ciphers -v | grep TLSv1.3
TLS_AES_256_GCM_SHA384         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(256)            Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256   TLSv1.3 Kx=any      Au=any   Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256         TLSv1.3 Kx=any      Au=any   Enc=AESGCM(128)            Mac=AEAD

En el protocol TLSv1.3, amb l'opció -ciphersuites pots forçar un protocol concret:

$ openssl s_client -quiet -brief -ciphersuites TLS_CHACHA20_POLY1305_SHA256 -connect www.redhat.com:443
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_CHACHA20_POLY1305_SHA256
...

Aquí tens un altre exemple:

$ openssl s_client -quiet -brief -ciphersuites TLS_AES_128_GCM_SHA256 -connect www.facebook.com:443
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_128_GCM_SHA256
...

En el protocol TLSv1.2 l’opció és -cipher:

$ openssl ciphers -v | grep TLSv1.2 | head -n 5

ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256)            Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2 Kx=ECDH     Au=RSA   Enc=AESGCM(256)            Mac=AEAD
DHE-RSA-AES256-GCM-SHA384      TLSv1.2 Kx=DH       Au=RSA   Enc=AESGCM(256)            Mac=AEAD
ECDHE-ECDSA-CHACHA20-POLY1305  TLSv1.2 Kx=ECDH     Au=ECDSA Enc=CHACHA20/POLY1305(256) Mac=AEAD
ECDHE-RSA-CHACHA20-POLY1305    TLSv1.2 Kx=ECDH     Au=RSA   Enc=CHACHA20/POLY1305(256) Mac=AEAD

$ openssl s_client -quiet -brief -tls1_2 -cipher ECDHE-ECDSA-CHACHA20-POLY1305 -connect www.google.com:443
CONNECTION ESTABLISHED
Protocol version: TLSv1.2
Ciphersuite: ECDHE-ECDSA-CHACHA20-POLY1305
...

En l’exemple anterior has de forçar el protocol TLSv1.2, perquè sinò es negociarà fer servir el protocol TLSv1.3.

Però com hem explicat abans no tots els servidors ho admeten:

$ openssl s_client -quiet -brief -tls1_2 -cipher ECDHE-ECDSA-CHACHA20-POLY1305 -connect www.apple.com:443
4037BE193F7F0000:error:0A000410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:../ssl/record/rec_layer_s3.c:1584:SSL alert number 40

Negociació TLS

TLS és un protocol complicat, dissenyat per evolucionar, on cada servidor s’actualitza al seu ritme.

Per tant, quan un client es connecta a un servidor mitjançant TLS han de negociar quina versió de TLS faran servir, i quins algorismes faran servir.

Com qualsevol negociació cada part pot sol·licitar o exigir opcions de protocol específiques. El servidor i el client ofereixen els seus millors algorismes, presenten les seves peticions i demandes i intenten arribar a un acord.

Tant si fem servir TLS 1.2 com 1.3, veureu tres parts principals de la connexió: la validació del certificat, la configuració del protocol i la represa.

Validació del certificat

Cada negociació TLS comença validant tots els certificats implicats.

El certificat del host pot estar validat per un certificat arrel de confiança a través d’un arbre extens de validacions entre el certificat arrel i el del host.

openssl s'atura al primer camí vàlid que descobreix i és el que et presenta

$ openssl s_client ubuntu.com:443
CONNECTED(00000003)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = ubuntu.com
verify return:1
---
Certificate chain
 0 s:CN = ubuntu.com
   i:C = US, O = Let's Encrypt, CN = R3
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Mar 17 08:29:56 2024 GMT; NotAfter: Jun 15 08:29:55 2024 GMT
 1 s:C = US, O = Let's Encrypt, CN = R3
   i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Sep  4 00:00:00 2020 GMT; NotAfter: Sep 15 16:00:00 2025 GMT
---
...

depth=0

  • Pots veure que el certificat del host sempre es troba a depth=0.
  • Aquest certificat autentica el host (subject) ubuntu.com: s:CN = ubuntu.com
  • El CN ha de coincidir amb el nom del servidor al qual hem fet la connexió TLS.

Però com sabem que aquest certificat és autèntic?

  • L’emissor del certificat (issuer) és Let’s Encrypt: i:C = US, O = Let's Encrypt, CN = R3
  • El certificat està firmat per Let’s Encrypt mitjantçant RSA: a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256.
  • Com qualsevol certificat té una data de caducitat: v:...; NotAfter: Jan 15 09:20:22 2024 GMT

depth=1

Com podem saber que la firma digital de Let’s Encrypt és autèncitca?

Perquè el servidor també ens mostra el certificat de ‘Let’s Encrypt’ que està firmat per ‘Internet Security Research Group’.

depth=3

Com podem saber que la firma digital de ‘Internet Security Research Group’ és autèncitca?

Perquè el servidor també ens mostra el certificat de ‘Internet Security Research Group’ que està firmat per ‘Digital Signature Trust Co’.

Autoritat certificadora

El servidor no envia el certificat de 'Digital Signature Trust Co'.

No fa falta perquè 'Internet Security Research Group' és una autoritat certificadara ( CA: Certificate Authority) i el seu certificat el tenim nosaltres en la nostra distribució ubuntu en el fitxer /etc/ssl/certs/ca-certificates.crt.

Mirem totes les CA en format llegible:

$ awk -v cmd='openssl x509 -noout -subject' '/BEGIN/{close(cmd)};{print | cmd}' < /etc/ssl/certs/ca-certificates.crt | head -n 10

subject=CN = ACCVRAIZ1, OU = PKIACCV, O = ACCV, C = ES
subject=C = ES, O = FNMT-RCM, OU = AC RAIZ FNMT-RCM
subject=C = ES, O = FNMT-RCM, OU = Ceres, organizationIdentifier = VATES-Q2826004J, CN = AC RAIZ FNMT-RCM SERVIDORES SEGUROS
subject=serialNumber = G63287510, C = ES, O = ANF Autoridad de Certificacion, OU = ANF CA Raiz, CN = ANF Secure Server Root CA
subject=C = IT, L = Milan, O = Actalis S.p.A./03358520967, CN = Actalis Authentication Root CA
subject=C = US, O = AffirmTrust, CN = AffirmTrust Commercial
subject=C = US, O = AffirmTrust, CN = AffirmTrust Networking
subject=C = US, O = AffirmTrust, CN = AffirmTrust Premium
subject=C = US, O = AffirmTrust, CN = AffirmTrust Premium ECC
subject=C = US, O = Amazon, CN = Amazon Root CA 1

Pots veure que aquest fitxer té una còpia el certificat de 'Internet Security Research Group':

$ awk -v cmd='openssl x509 -noout -subject' '/BEGIN/{close(cmd)};{print | cmd}' < /etc/ssl/certs/ca-certificates.crt | grep ISRG
subject=C = US, O = Internet Security Research Group, CN = ISRG Root X1
subject=C = US, O = Internet Security Research Group, CN = ISRG Root X2

Com que openssl troba aquest certificat en la llista dels que confia, i verifica que la còpia del servidor és la mateixa que la còpia que te ell, valida el certificat.

Com que el certificat de depth=3 està validat, valida també la cadena de firmes digitals que arriben al certificat de depth=0.

Errors de certificat

Els principals motius pels quals falla una connexió TLS és perquè el client TLS no acceptarà un certificat.

Sovint el servidor no configura bé els certificats i el client té tota la raó per rebutjar certificats caducats, autofirmats, revocats i altres errors que fan que el certificat no sigui vàlid.

Per exemple, un error és que el servidor no ofereixi una cadena de certificats:

$ curl -I https://incomplete-chain.badssl.com
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html

Si vols procedir de totes maneres pots fer-ho amb el flag -k:

$ curl -I -k https://incomplete-chain.badssl.com
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)

Per conèixer exactament que està passant fem servir openssl:

e$ openssl s_client -quiet -brief  incomplete-chain.badssl.com:443
depth=0 C = US, ST = California, L = Walnut Creek, O = Lucas Garron Torres, CN = *.badssl.com
verify error:num=20:unable to get local issuer certificate
depth=0 C = US, ST = California, L = Walnut Creek, O = Lucas Garron Torres, CN = *.badssl.com
verify error:num=21:unable to verify the first certificate
depth=0 C = US, ST = California, L = Walnut Creek, O = Lucas Garron Torres, CN = *.badssl.com
verify error:num=10:certificate has expired
notAfter=May 17 12:00:00 2022 GMT
notAfter=May 17 12:00:00 2022 GMT
CONNECTION ESTABLISHED
Protocol version: TLSv1.2
Ciphersuite: ECDHE-RSA-AES128-GCM-SHA256
Peer certificate: C = US, ST = California, L = Walnut Creek, O = Lucas Garron Torres, CN = *.badssl.com
Hash used: SHA512
Signature type: RSA
Verification error: certificate has expired
Supported Elliptic Curve Point Formats: uncompressed:ansiX962_compressed_prime:ansiX962_compressed_char2
Server Temp Key: ECDH, prime256v1, 256 bits

Pots veure que només hi ha un certificat i que presenta diversos problemes: no està firmat per una CA i està expirat.

Per defecte, openssl continua el procés de connexió encara que hi hagi problemes de certificat (el comportament contrari d’eines com curl).

Si vols que no es relitzi la connexió has de passar l’opció -verify_return_error com s'ha explicat abans:

$ openssl s_client -quiet -brief -verify_return_error incomplete-chain.badssl.com:443
depth=0 C = US, ST = California, L = Walnut Creek, O = Lucas Garron Torres, CN = *.badssl.com
verify error:num=20:unable to get local issuer certificate
40875B20197F0000:error:0A000086:SSL routines:tls_post_process_server_certificate:certificate verify failed:../ssl/statem/statem_clnt.c:1883:

Clau compartida (K)

A continuació s’ha de generar un clau d’encriptació compartida entre el client i el servidor que només ha de ser coneguda per ells dos i que només es farà servir en aquesta sessió.

Per aconseguir això es fa servir l’algorisme efímer de corba el·líptica Diffie-Hellman (ECDHE - Elliptic-curve Diffie-Hellman Ephemeral).

Però per explicar com funciona farem sevir RSA enlloc d’Elliptic-curve:

  1. L’algorisme fa servir un nombre primer P i una arrel primitiva de P, G, que el servidor pot enviar al client sense necessitat que la comunicació estigui xifrada.
  2. El client genera un valor privat a i el servidor genera un valor privat b.
  3. Amb els valoras P, G i a el client genera una clau pública que envia al servidor.
  4. Amb els valoras P, G i b el servidor genera una clau pública que envia al client.
  5. El client i el servidor, amb la clau pública de l’altre, poden generar una clau secreta que serà la mateixa.

Anem a veure un exemple pas a pas.

En aquest enllaç pots obtenir les arrels primitives d’un nombre primer: Primitive Roots Calculator

Client Servidor
P = 97; G = 5
servidor envia P i G al client
a = 33 b = 8
x = Ga mod P (78) y = Gb mod P (6)
client envia x al servidor servidor envia y al client
k = ya mod P (75) k = xb mod P (75)

La clau compartida de xifratge simètric (k) és 75

Aquí tens els càlculs amb Python:

$ python3
>>> x = pow(5,33) % 97
>>> y = pow(5,8) % 97
>>> k = pow(y,33) % 97
>>> k
75
>>> k = pow(x,8) % 97
>>> k
75

Podem crear un programa en Python perquè ens calculi la clau privada k:

from random import randint

P = 10007
G = 3

a = randint(1, P-1)
x = pow(G, a) % P

b = randint(1, P-1)
y = pow(G, b) % P

print("a: %d; b: %d" % (a,b))

k_client = (pow(y, a)) % P
k_server = (pow(x, b)) % P

print("k_client: %d; k_server: %d" % (k_client,k_server) )

No importa que P i G siguin coneguts, tampoc que x i y s’enviin sense xifrar.

Si P és un número molt gran és gairebé impossible d’adivinar quina pot ser la k a partir de x i y.

$ python3 df.py
a: 9436; b: 7854
k_client: 9203; k_server:9203

$ python3 df.py
a: 5744; b: 5062
k_client: 9022; k_server:9022

En una connexió TLS la clau pública que envia el servidor és de 253 bits!

$ openssl s_client -brief -connect www.repsol.es:443
CONNECTION ESTABLISHED
Protocol version: TLSv1.2
Ciphersuite: ECDHE-ECDSA-AES256-GCM-SHA384
Peer certificate: C = ES, ST = Comunidad de Madrid, L = Madrid, O = "REPSOL, SA", CN = www.repsol.energy
Hash used: SHA256
Signature type: ECDSA
Verification: OK
Supported Elliptic Curve Point Formats: uncompressed:ansiX962_compressed_prime:ansiX962_compressed_char2
Server Temp Key: ECDH, prime256v1, 256 bits

La Server Temp Key és fa servir per el que es coneix com Perfect Forward Secrecy.

X25519 és un tipus de corba i un algorisme per a l'acord de clau Elliptic Curve Diffie-Hellman Ephemeral (ECDHE)

El client generarà el seu propi parell de claus X25519 i enviarà la clau pública.

Servior i client faran servir aquestes claus ECDHE per generar una clau simètrica compartida que s'utilitzarà per protegir les dades en trànsit.

Configuració del protocol

A continuació, tenim la configuració acordada per a aquesta sessió TLS.

$ openssl s_client -connect www.ibm.com:443
...
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
...
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 Pots veure que estem utilitzant TLS 1.3, amb el xifratgeTLS_AES_256_GCM_SHA384.
Aquest xifratge utilitza AES de 256 bits amb mode Galois/Counter (GCM) per a l'encriptació i SHA384 per a MAC.
Secure Renegotiation IS NOT supported TLSv1.3 no admet Secure Renegotiation.
Compression: NONE TLSv1.3 tampoc adment la compressió de dades perquè posa en risc la confidencialitat i la integritat.
No ALPN negotiated La negociació de protocols de capa d'aplicació (ALPN) és un complement de TLS que permet a les aplicacions integrar la configuració de TLS a la resta de la configuració del seu protocol.
S'utilitza a HTTP/2.
Pots habilitar ALPN amb el flag -alpn
Early data was not sent

Compara aquesta configuració amb la que s'acorda en TLSv1.2:

$ openssl s_client -tls1_2 -connect www.ibm.com:443
...
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
...

Python

socket

El primer que farem es generar un certificat pel servidor:

$ openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout key.pem -subj "/C=ES/ST=Barcelona/O=XTEC/CN=xtec.dev"
$ ls
cert.pem  key.pem

CN és el nom de l’anfitrió.

Ara creem un servidor server.py:

from socket import socket, AF_INET, SOCK_STREAM
from ssl import SSLContext, PROTOCOL_TLS_SERVER

context = SSLContext(PROTOCOL_TLS_SERVER)
context.load_cert_chain("cert.pem","key.pem")

with socket(AF_INET, SOCK_STREAM) as server:
  server.bind(('0.0.0.0',8443))
  server.listen(1)
  with context.wrap_socket(server, server_side=True) as tls:
    connection, address = tls.accept()
    print(f'Connected by {address}\n')

    data = connection.recv(1024)
    print(f'Client says: {data}')

    connection.sendall(b"You're welcome")

I el client que ha d’acceptar el certificat del servidor:

from socket import create_connection
from ssl import SSLContext, PROTOCOL_TLS_CLIENT

context = SSLContext(PROTOCOL_TLS_CLIENT)
context.load_verify_locations("cert.pem")

with create_connection(('127.0.0.1',8443)) as client:
  with context.wrap_socket(client, server_hostname="xtec.dev") as tls:
    print(f'Using {tls.version}\n')
    tls.sendall(b"Hello, world!")

    data = tls.recv(1024)
    print(f"Server says {data}")

Pots veure que TLS és completament indiferent al protocol subjacent.

Es limitat a crear un wrapper al voltant del socket i enviar i rebre tot el que li passen, això sí, encriptat i protegit:

$ python3 server.py 
Connected by ('127.0.0.1', 60416)

Client says: b'Hello, world!'
$ python3 client.py 
Using <bound method SSLSocket.version of <ssl.SSLSocket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('127.0.0.1', 60416), raddr=('127.0.0.1', 8443)>>

Server says b"You're welcome"

També ens podem connectar amb el client openssl:

p$ python3 server.py 
Connected by ('127.0.0.1', 33564)

Client says: b'Hello from OpenSSL\n'
$ openssl s_client -brief -connect 127.0.0.1:8443
Can't use SSL_get_servername
depth=0 C = ES, ST = Barcelona, O = XTEC, CN = xtec.dev
verify error:num=18:self-signed certificate
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: C = ES, ST = Barcelona, O = XTEC, CN = xtec.dev
Hash used: SHA256
Signature type: RSA-PSS
Verification error: self-signed certificate
Server Temp Key: X25519, 253 bits
Hello from OpenSSL
You're welcome40C76CA3667F0000:error:0A000126:SSL routines:ssl3_read_n:unexpected eof while reading:../ssl/record/rec_layer_s3.c:308:

Com sempre openssl ens avisa que el certifcat està autofirmat i segueix fent.

Python fa servir la llibreria OpenSSL que tenim instal.lada al sistema operatiu. Per tant el resultat és l’esperat.

El que no li agrada és la resposta del servidor i peta 😅.

A continuació modificarem el client de tal forma que no accepti el certificat del servidor, i es produirà un error perquè el certificat és autofirmat.

És com si tu mateix et firmessis el DNI:

from socket import create_connection
from ssl import SSLContext, PROTOCOL_TLS_CLIENT

context = SSLContext(PROTOCOL_TLS_CLIENT)
# context.load_verify_locations("cert.pem")

with create_connection(('127.0.0.1',8443)) as client:
...

La connexió no es pot establir.

HTTP

Un dels usos més habituals de TLS és per xifrar connexions HTTP.

Amb Python és molt fàcil arrencar un servidor http al port 8000 que escolta en totes les interficies disponibles de la màquina:

$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

Des d'un altre terminal pots verure el contingut del sistema de fitxers:

$ lynx localhost:8000

O desde el navegador:

Però com podem xifrar la connexió?

Doncs hem de fer un "wrap" del socket que fa servir http.server:

Crea el fitxer http-server.py:

from http.server import HTTPServer, SimpleHTTPRequestHandler
from ssl import SSLContext, PROTOCOL_TLS_SERVER

context = SSLContext(PROTOCOL_TLS_SERVER)
context.load_cert_chain("cert.pem","key.pem")

server = HTTPServer(('0.0.0.0',8443), SimpleHTTPRequestHandler)

with context.wrap_socket(server.socket, server_side= True) as tls_socket:
    server.socket = tls_socket
    server.serve_forever()

Arrenca el servidor:

$ python3 http-server.py

Conecta't al servidor amb Lynx:

$ lynx https://127.0.0.1:8443

Podem veure que funciona (si acceptem el certificat autofirmat):

També pots veure que podem interactuar amb el servidor amb el client openssl:

$ openssl s_client -brief -connect 127.0.0.1:8443

Can't use SSL_get_servername
depth=0 C = ES, ST = Barcelona, O = XTEC, CN = xtec.dev
verify error:num=18:self-signed certificate
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: C = ES, ST = Barcelona, O = XTEC, CN = xtec.dev
Hash used: SHA256
Signature type: RSA-PSS
Verification error: self-signed certificate
Server Temp Key: X25519, 253 bits
GET / HTTP/1.1

HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.10.12
Date: Mon, 03 Jun 2024 22:03:50 GMT
...

PostgreSQL

Crea una base de dades PostgreSQL tal com s'explica a Postgres:

$ mkdir data
$ docker run --rm -d --name db -p 5432:5432 -v $PWD/data:/var/lib/postgresql/data -e POSTGRES_PASSWORD=password postgres:16
....

Per connectar-te a la base de dades faras servir l’adaptador postgres psycopg2 per a Python:

$ sudo apt install -y libpq-dev
$ pip install psycopg2 

Crea l'script db.py:

import psycopg2

connection = psycopg2.connect(host="127.0.0.1", port=5432,user="postgres", password="password")

connection.autocommit = True

cursor = connection.cursor()
cursor.execute('select %s as connected;', ('Connection to postgres successful',))
print(cursor.fetchone())

Pots veure que et pots connectar a la base de dades:

$ python3 db.py
('Connection to postgres successful',)

TLS es pot aplicar a qualsevol protocol client-servidor, també a la conexió entre el nostre client i la base de dades PostgreSQL.

Modifica el fitxer db.py afegint l'opció sslmode='require' al crear la connexió:

connection = psycopg2.connect(host="127.0.0.1", port=5432,user="postgres", password="password", sslmode='require')

Tornar a executar l'script:

$ python3 db.py 
Traceback (most recent call last):
  File "/home/david/site/tmp/db.py", line 3, in <module>
    connection = psycopg2.connect(host="127.0.0.1", port=5432,user="postgres", password="password", sslmode='require')
  File "/home/david/.local/lib/python3.10/site-packages/psycopg2/__init__.py", line 122, in connect
    conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
psycopg2.OperationalError: connection to server at "127.0.0.1", port 5432 failed: server does not support SSL, but SSL was required

Ara es produeix un error en l'script perqué el nostre client només vol utilitzar una connexió TSL i el servidor contesta que no pots: server does not support SSL, but SSL was required.

Hem de crear uns certificats pel servidor postgres:

$ docker exec db openssl req -new -x509 -days 365 -nodes -text -out /var/lib/postgresql/data/server.crt -keyout /var/lib/postgresql/data/server.key -subj="/CN=db.xtec.dev"
...

Ja podem parar el contenidor db i tornar a executar un altre contenidor amb l'opció ssl=on:

$ docker stop db

$ docker run --rm --name db -p 5432:5432 -v $PWD/data:/var/lib/postgresql/data -e POSTGRES_PASSWORD=password -d postgres:16 -c ssl=on

L’applicació db.py ja es pot connectar mitjançant una connexió xifrada TLS:

$ python3 db.py 
('Connection to postgres successful',)

Pots utilitzar el client openssl amb l'opció -starttls postgres per veure la connexió TLS:

$ openssl s_client -brief -starttls postgres -connect 127.0.0.1:5432

Can't use SSL_get_servername
depth=0 CN = db.xtec.dev
verify error:num=18:self-signed certificate
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN = db.xtec.dev
Hash used: SHA256
Signature type: RSA-PSS
Verification error: self-signed certificate
Server Temp Key: ECDH, prime256v1, 256 bits

mTLS

TODO. Revisar

Mutual Transport Layer Security (mTLS) és un procés que estableix una connexió TLS xifrada en la qual ambdues parts utilitzen certificats digitals X.509 per autenticar-se mútuament.

Diguem l'Alícia i el Bobfer necessita una autenticació més forta. Potser estan tractant amb transferències financeres de gran valor o enviant informació sensible. Potser hi ha una bona probabilitat que un actor maliciós pugui robar la contrasenya d'Alice i ocupar el seu lloc. Bob pot voler onecessitat per comprovar criptogràficament que l'Alícia és realment qui diu que és. En aquest cas, potser voldrienmútuament autenticar-se mútuament. És a dir, l'Alice no només verifica la identitat d'en Bob, sinó que Bob també autentica la identitat d'Alice. A través del web, això es pot realitzar mitjançant certificats digitals i TLS mutus.

Autenticació bidireccional, o mútua, com es veu quan s'utilitza mTLS, en què tant el client (Alice) com el servidor (Bob) s'autentiquen mútuament.

mTLS no és un protocol nou. L'autenticació mútua forma part de l'estàndard TLS i ha format part de l'especificació des que es va anomenar Secure Sockets Layer (SSL). Qualsevol servidor web que utilitzi TLS per assegurar el seu trànsit hauria de ser capaç d'autenticar-se mútuament. Per tal d'implementar l'autenticació mútua, el servidor ha de demanar específicament al client el seu certificat, però, la majoria dels servidors web no estan configurats per fer-ho de manera predeterminada.

La següent figura mostra una versió simplificada d'una connexió TLS 1.2, coneguda com a "encaixada de mans". La majoria de llocs web d'Internet salten els passos 4 i 5. Per als llocs i serveis que necessiten realitzar una autenticació mútua, el servidor, al pas 4, enviarà un missatge al client demanant-li que proporcioni un certificat (a més de dir-li de quines CA accepta certificats).

Passos durant una connexió de mans TLS 1.2 entre el client i el servidor quan es configura l'autenticació TLS mútua.

Entorns de malla de servei

Moltes aplicacions modernes utilitzen a arquitectura de microserveis que separa els components de l'aplicació en serveis discrets que s'executen en diversos servidors, sovint dins de contenidors. Per tal d'intercanviar dades i càlculs, aquests serveis s'han de comunicar a través de la xarxa. Compareu això amb les aplicacions monolítices tradicionals que realitzen tota la comunicació en memòria. Els entorns al núvol, amb una escala aparentment il·limitada i una automatització flexible, proporcionen un lloc ideal per desplegar aplicacions basades en microserveis i, tot i que el TLS estàndard pot xifrar la comunicació entre aquests microserveis, encara deixa oberta la possibilitat que algú manipuli qualsevol dels serveis.

L'ús d'una "malla de servei" permet als propietaris d'aplicacions governar el trànsit de servei a servei. Una malla de servei pot centralitzar la gestió dels microserveis i us permet controlar els dos extrems de la connexió, utilitzant mTLS per xifrar i autenticar les dades al cable.

La figura següent mostra les interaccions d'alt nivell de les sessions TLS autenticades mútuament en un entorn de malla de servei.

Entorns de malla de servei que utilitzen mTLS per xifrar i autenticar serveis.

Exemple

Anem a veure un exemple de mTLS d'ús del navegador web cURL (el client) per connectar-se a un servidor web Node.js (el servidor) que serveix al nom DNSlocalhost. Al fer-ho:

  • El client validarà que el servidor és de confiança per oferir contingut per al nom DNSlocalhost
  • El servidor validarà que el client és conegut, és a dir, l'autenticarà

El primer pas és crear una autoritat de certificació (CA) en la qual confien tant el client com el servidor. La CA és només una clau pública i privada amb la clau pública embolicada en un certificat X.509 autofirmat. L'ordre que fem servir per fer-ho és:

$ openssl req  -new -x509  -nodes -days 365 -subj '/CN=xtec'  -keyout ca.key -out ca.crt

Això genera dos fitxers, ca.key i ca.crt,en el Format PEM (codificació base64 de la clau privada i del certificat X.509 respectivament).

Mirant la [ocumentació de openssl req documentació, veiem que:

  • Les opcions -new i -x509 permeten la creació d'un certificat arrel CA X.509 autofirmat.
  • L'opció -nodes (Sense DES) desactiva la seguretat de la clau privada amb una contrasenya; aquesta opció és opcional.
  • L'opció -subject proporciona la identitat de l'AC; en aquest cas el Nom Comú (NC) deel meu-ca. La resta d'opcions s'explica per si mateix.

Podem donar la volta i inspeccionar el certificat mitjançant l'ordre següent:

$ openssl x509  --in ca.crt -text --noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            1a:14:b7:13:24:e4:67:f3:85:5a:4d:25:6f:71:61:ff:c0:4e:36:34
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = xtec
        Validity
            Not Before: Jun  4 07:47:07 2024 GMT
            Not After : Jun  4 07:47:07 2025 GMT
        Subject: CN = xtec
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    ...
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                C4:13:DF:E3:EF:96:4E:70:7C:F4:9A:96:C2:3C:AB:30:CA:B3:F4:31
            X509v3 Authority Key Identifier: 
                C4:13:DF:E3:EF:96:4E:70:7C:F4:9A:96:C2:3C:AB:30:CA:B3:F4:31
            X509v3 Basic Constraints: critical
                CA:TRUE
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        ...

Les opcions aquí s'expliquen per si mateixes tal com es documenta a la pàgina openssl x509.

Mirant la sortida, podem confirmar una sèrie de coses:

  • Tant el Issuercom el Subject tenen el CN = xtec; això indica que aquest certificat està autofirmat.

  • Validity indica que el certificat té una validesa d'un any.

  • El valor CA:TRUE de X509v3 Basic Constraints: critical indica que aquest certificat es pot utilitzar com a CA, és a dir, es pot utilitzar per signar certificats.

A continuació creem la clau i el certificat del servidor; començant per la clau:

$ openssl genrsa  -out server.key 2048

Les opcions aquí s'expliquen per si mateixes tal com es documenta a openssl genrsa.

Recordeu que el nostre objectiu aquí és crear un certificat de servidor per al nom DNS localhost signat per la CA.

Ara creem una sol·licitud de signatura de certificat (CSR) amb el nom comú (CN)localhost:

$ openssl req  -new -key server.key -subj '/CN=localhost'  -out server.csr

Utilitzant la CSR, la CA (realment utilitzant la clau i el certificat de la CA) crea el certificat signat:

$ openssl x509  -req -in server.csr -CA ca.crt  -CAkey ca.key  -CAcreateserial -days 365  -out server.crt

La sortida és el certificat del servidor signat, servidor.crt, en format PEM.

La majoria de les opcions són familiars (des de dalt) o s'expliquen per si mateixes, tal com es documenta a openssl x509.

L'única excepció és l'opció -CAcreateserial que gestiona un fitxer creat recentment, ca.srl, que permet que cada certificat creat per aquesta CA tingui un número de sèrie únic.

Com abans, inspeccionem el certificat mitjançant l'ordre següent:

$ openssl x509  --in server.crt -text  --noout
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            51:e1:59:44:af:4b:8b:fe:0d:a8:d7:f3:e0:4a:86:d8:a4:0f:e6:ff
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = xtec
        Validity
            Not Before: Jun  4 08:01:58 2024 GMT
            Not After : Jun  4 08:01:58 2025 GMT
        Subject: CN = localhost
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    ...
                Exponent: 65537 (0x10001)
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        ...

Mirant la sortida, podem confirmar una sèrie de coses:

  • El Issuer té el valor CN = xtec que és l'autoritat certificadora que firma aquest certificat.

  • Validity indica que el certificat té una validesa d'un any.

  • El Subject tè el valora CN = localhost; això indica que aquest certificat es pot enviar a un client per validar que el servidor és de confiança per oferir contingut per al nom DNS localhost.

A continuació hem de crear una clau i un certificat pel client.

Començem per crear la clau del client:

$ openssl genrsa -out client.key 2048

Creem el CSR amb un nom comú arbitrari de client:

$ openssl req  -new -key client.key -subj '/CN=client' -out client.csr

I finalment creem certificat del client:

$ openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial  -days 365 -out client.crt

Inspecciona aquest certificat i observa que el Serial Number és diferent del Serial Number del certificat del servidor:

$ openssl x509  --in server.crt -text  --noout | grep Serial -A 1
        Serial Number:
            51:e1:59:44:af:4b:8b:fe:0d:a8:d7:f3:e0:4a:86:d8:a4:0f:e6:ff

$ openssl x509  --in client.crt -text  --noout | grep Serial -A 1
        Serial Number:
            37:3b:f1:f8:a3:6b:d5:2c:43:92:b5:be:fa:36:b1:a6:0f:7b:0e:ff

Observació. Tant els certificats de servidor com de client són certificats X.509 v1 més simples; el certificat CA, però, és un certificat X.509 v3. Això es deu al fet que OpenSSL crea automàticament certificats autofirmats X.508 v3 (certificat CA) i no hem subministrat cap extensió v3 en signar els certificats de servidor i client (utilitzant elfitxer ext iextensions opcions).

Amb totes les nostres claus i certificats (ca, servidor i client) creats podem configurar el nostre servidor i client.

Demo

Important!. El servidor s'ha de canviar a Python

Instal·leu npm i creeu un projecte nou: sudo apt install -y npm npm init

El servidor és el bàsic Hola món exemple, proporcionat per Node.js, millorat per admetre mTLS. nano index.js

const https = require('https'); const fs = require('fs');

const hostname = 'localhost'; const port = 3000;

const options = { ca: fs.readFileSync('ca.crt'), cert: fs.readFileSync('server.crt'), key: fs.readFileSync('server.key'), rejectUnauthorized: true, requestCert: true, };

const server = https.createServer(options, (req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end('Hello World'); });

server.listen(port, hostname, () => { console.log(Server running at http://${hostname}:${port}/); });

Aquí elrequestCert,rebutjarNo autoritzat, iaixò Les opcions s'utilitzen per requerir que el navegador (client) proporcioni un certificat signat pel certificat CA per interactuar amb el servidor. Elclau icert Les opcions permeten que el servidor proporcioni el certificat de servidor signat de CA. Executeu el servidor: $ node index.js Server running at http://localhost:3000/

El client és simplement el navegador web cURL amb opcions: $ curl --cacert ca.crt --key client.key --cert client.crt https://localhost:3000 Hello World

Aquí elcacert s'utilitza perquè el client (cURL) pugui validar el certificat subministrat pel servidor. Elclau icert s'utilitzen perquè el client enviï el certificat de client signat de la CA amb la sol·licitud. De fet, observem que aquesta sol·licitud torna amb èxitHola món. Si, però, deixem fora elcacert opció, obtenim l'error: $ curl --key client.key --cert client.crt https://localhost:3000 curl: (60) SSL certificate problem: self-signed certificate in certificate chain More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the web page mentioned above.

D'altra banda, si deixem fora les opcions de clau i certificat, obtenim un error diferent: $ curl --cacert ca.crt https://localhost:3000 curl: (56) OpenSSL SSL_read: error:0A00045C:SSL routines::tlsv13 alert certificate required, errno 0