2024/07/13
Tabla de contenidos
Mientras configuraba el servidor para mi pet adoption platform, tuve que configurar TLS y utilicé certbot para obtener un certificado de Let’s Encrypt. Aunque esto fue fácil de hacer, una cosa “molesta” de los certificados de Let’s Encrypt es que solo son válidos durante 90 días y, con la forma en que había configurado mi servidor, no encontré ninguna alternativa para renovarlos automáticamente. Por lo tanto, tuve que iniciar sesión en el servidor y realizar el proceso manualmente cada 90 días.
Pensé para mis adentros que sería estupendo que el certificado durara más de 90 días y me pregunté cuál podría ser la duración máxima de un certificado y qué tan largos pueden ser sus campos. En este blog intentaré explorar, probar y aprender más sobre los certificados TLS.
Es importante saber primero que los certificados tienen un formato definido por el estándar X.509 (certificados de clave pública), razón por la cual, al leer sobre certificados, a veces te los encontrarás denominados “certificados X.509”. Sin embargo, según mi entendimiento, X.509 solo define la estructura de los certificados de clave pública, mientras que RFC 5280 define tanto su estructura como su uso en Internet para TLS. Leí y seguí lo que establece el RFC 5280.
Como puedes ver en la imagen anterior, un certificado TLS tiene varios campos. Algunos describen su validez y el algoritmo utilizado para la clave pública, pero también hay otros campos que son simplemente “informativos”, como el nombre del emisor, que se puede personalizar al crear un certificado. También hay otras pestañas como “ISRG Root X1” y “R11”, que corresponden a los certificados raíz e intermedios. “ISRG Root X1” emitió un certificado llamado “R11” y, utilizando “R11”, este emitió un certificado para mi sitio web “0x00.cl”. Esto es una cadena de confianza: si tu navegador o cliente puede confiar en el certificado raíz, como “ISRG Root X1”, entonces también debería confiar en los certificados emitidos por él. Navegadores como Chrome y Firefox utilizan la Common CA Database (ccadb) para incluir las autoridades de certificación (CA) en sus navegadores.
Puedes crear tu propio certificado raíz y ser tu propia CA, PERO, como ese certificado no estará incluido en ningún software, la mayoría, si no todos, mostrarán un error o, al menos, una advertencia indicando que el certificado no es de confianza. badssl es un sitio web que muestra ejemplos de certificados buenos y malos.
Lo primero que debemos hacer es crear un certificado; este será el certificado raíz.
Ya había creado certificados anteriormente utilizando openssl (OpenSSL 3.2.1) y, cuando buscas cómo crearlos, los sitios web suelen utilizar openssl, ya que es una herramienta de línea de comandos fácil de usar y preinstalada en la mayoría de las distribuciones de Linux. Primero, tienes que crear una clave:
$ openssl genrsa -out myCA.key 2048
Entonces, ¿cuál es el argumento 2048? Es el tamaño de la clave en bits, específicamente porque el subcomando genrsa genera una clave privada RSA. Este subcomando es un «atajo» para generar una clave privada utilizando el subcomando genpkey. El equivalente sería:
$ openssl genpkey -algorithm RSA -out myCA.key -pkeyopt bits:2048
Una vez que tengas una clave, puedes crear el certificado utilizando el subcomando req:
$ openssl req -x509 -new -noenc -key myCA.key -sha256 -days 3650 -out myCA.pem
Esto crea un certificado que durará 10 años (3650 días). Los dos archivos finales:
$ ls -l
-rw-------. 1 tomas tomas 1704 Jul 3 12:00 myCA.key
-rw-r--r--. 1 tomas tomas 1237 Jul 3 12:00 myCA.pem
Dado que crear una clave es el primer paso para crear un certificado, me pregunté: ¿y si establecía el número de bits en un valor más alto, en lugar de usar 2048, usar 65536?
$ openssl genrsa -out myCA.key 65536
Warning: It is not recommended to use more than 16384 bit for RSA keys.
Your key size is 65536! Larger key size may behave not as expected.
Dejé que se ejecutara durante unos 30 minutos, pero decidí detenerlo, ya que tardaba demasiado y no parecía que fuera a terminar pronto. En comparación, 2048 bits tardan unos 250 ms; además, el aviso que emitía OpenSSL no era nada prometedor, así que me decidí por 16384 (tardó 30 segundos).
$ ls -l
-rw-------. 1 tomas tomas 12632 Jul 3 12:00 myCA.key
Bueno, eso supone unos 12 kB para la clave, pero como mencioné antes, el subcomando genrsa genera específicamente una clave RSA. ¿Y qué pasa con otros algoritmos para crear una clave? ¿Producirían una clave más grande? Así que primero comprobé qué opciones estaban disponibles (RSA, RSA-PSS, EC, X25519, X448, ED25519 y ED448) y las probé, pero RSA fue el único que conseguí que produjera una clave más grande en bytes. También cambié el algoritmo de hash de SHA256 a SHA3-512, ya que debería producir más bits.
De nuevo, creé el certificado utilizando esta nueva clave privada y obtuve:
$ ls -l
-rw-------. 1 tomas tomas 12632 Jul 3 12:00 myCA.key
-rw-r--r--. 1 tomas tomas 6091 Jul 3 12:00 myCA.pem
Perfecto, ahora el certificado pasó de 1,2 kB a 6 kB solo porque la clave privada se creó con más bits (16384).
Los certificados tienen varios campos que proporcionan información no solo sobre el certificado, sino también sobre el emisor y el sujeto del mismo. La sección 4.1.2.4 de la RFC 5280 define los atributos que DEBEN estar presentes en el campo del emisor.
Hasta ahora, cuando creamos los certificados, todos esos campos se habían dejado con sus valores predeterminados, por lo que esta es otra oportunidad para hacer el certificado más grande añadiendo cadenas largas.
$ openssl req -x509 -new -noenc -key myCA.key -sha3-512 -days 3650 -out myCA.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CC
State or Province Name (full name) []:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Error making certificate request
800275933F7F0000:error:06800097:asn1 encoding routines:ASN1_mbstring_ncopy:string too long:crypto/asn1/a_mbstr.c:106:maxsize=128
Bien, no puedo simplemente añadir cadenas superlargas; en este caso, hay un límite para el «nombre del estado o provincia», cuyo máximo es de 128 caracteres… ¿pero qué pasa con los demás campos? Pues, lamentablemente para nosotros, existen límites superiores definidos en el Apéndice A.1 de la RFC 5280 (que OpenSSL define en crypto/asn1/tbl_standard.h).
| Atributo | Límite superior |
|---|---|
| Nombre del país (alfanumérico) | 2 |
| Nombre de la organización | 64 |
| Nombre de la unidad organizativa | 64 |
| Nombre del estado o provincia | 128 |
| Nombre común | 64 |
| Nombre de la localidad | 128 |
Es posible utilizar la bandera -subj para pasar estos valores en lugar de hacerlo de forma interactiva, como cuando lo creé por primera vez.
$ openssl req -x509 -new -noenc \
-key myCA.key \
-sha3-512 \
-days 3650 \
-out myCA.pem \
-subj "/C=XX/ST=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/L=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/O=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/OU=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/CN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/emailAddress=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
$ ls -l
-rw-------. 1 tomas tomas 12632 Jul 3 12:00 myCA.key
-rw-r--r--. 1 tomas tomas 7550 Jul 3 12:00 myCA.pem
Bueno, logré aumentarlo de 6 kB a unos 7,5 kB, no es mucho. Intentaré «hacer trampa» y ver si puedo repetir algunos atributos; voy a usar el mismo comando que antes, pero la cadena dentro de -subj se copiará y pegará varias veces. No la incluiré aquí porque sería demasiado larga e innecesaria, pero ya entiendes la idea.
$ ls -l
-rw-------. 1 tomas tomas 12632 Jul 3 12:00 myCA.key
-rw-r--r--. 1 tomas tomas 249845 Jul 3 12:00 myCA.pem
¡Wow! Casi 250 kB para un certificado. Ahora que conocemos el «secreto» para hacerlo tan grande como queramos, podemos pasar al período de validez del certificado.
Cuando creé un certificado por primera vez usando openssl, se podía definir en días cuánto tiempo debía durar el certificado. Por defecto, el campo notBefore se establece en la fecha actual y, a partir de ella, se calcula la fecha de notAfter según los días indicados.
Quería probar cuál era el valor máximo para notAfter. Así que lo primero que intenté fue establecer el número de días en un valor muy grande.
$ openssl req -x509 -new -noenc \
-key myCA.key \
-sha3-512 \
-days 36500000000 \
-out myCA.pem
req: Value "36500000000" outside integer range
req: Use -help for summary.
Bueno, supongo que crear un certificado que sea válido durante 100 millones de años es demasiado. Usaré un número más pequeño, como 3650000 (10000 años).
$ openssl req -x509 -new -nodes \
-key myCA.key \
-sha3-512 \
-days 3650000 \
-out myCA.pem
¡Parece que funcionó! Revisaré la información del certificado.
$ openssl x509 -noout -text -in myCA.pem
Could not open file or uri for loading certificate from myCA.pem: No such file or directory
$ ls -l myCA.pem
ls: cannot access 'myCA.pem': No such file or directory
Mmm… openssl se queja de que el número es demasiado grande y, cuando es lo suficientemente pequeño para openssl, no crea el certificado sin emitir ningún error o advertencia. Lo probé y me aseguré de que no fuera un problema del comando usando un número menor de días, y en ese caso sí creó el certificado, por lo que el problema era el número de días.
Cuando leí la sección 4.1.2.5 de la RFC 5280, vi que un certificado acepta dos formatos de fecha: «YYMMDDHHMMSSZ» y «YYYYMMDDHHMMSSZ». En la RFC 5280 se indica implícitamente que las fechas pueden ir de 1950 a 9999, así que esos serán nuestros límites.
Ahora que técnicamente puedo establecer notAfter en 9999 ajustando la cantidad correcta de días, ¿qué pasa con notBefore? ¿Es posible establecer un valor en el pasado para el certificado? ¿Lo permite openssl?
Los comandos openssl req o openssl x509 no permiten establecer fechas explícitamente para los certificados, pero openssl ca sí permite firmar certificados y personalizar los campos de fecha de inicio y fin. Después de intentar durante un tiempo generar uno, la única forma que encontré de usar ese comando fue que ya se debe tener un certificado de CA con una Solicitud de Firma de Certificado (CSR).
Primero tengo que crear una CSR:
$ openssl x509 -in myCA.pem -signkey myCA.key -x509toreq -out myCA.csr
Así puedo crear un certificado personalizado:
$ openssl ca -startdate '19500101000000Z' -enddate '99991231235959Z' -in myCA.csr -keyfile myCA.key -cert myCA.pem -out myCA.crt
Using configuration from /etc/pki/tls/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number:
01:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
Validity
Not Before: Jan 1 00:00:00 1950 GMT
Not After : Dec 31 23:59:59 9999 GMT
Subject:
countryName = XX
stateOrProvinceName = Mandatory
organizationName = Default Company Ltd
commonName = 0x00.cl
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Subject Key Identifier:
7E:F9:D5:33:0E:B8:64:DF:19:94:AB:59:23:A0:0A:55:D0:40:C3:B0
X509v3 Authority Key Identifier:
7E:F9:D5:33:0E:B8:64:DF:19:94:AB:59:23:A0:0A:55:D0:40:C3:B0
Certificate is to be certified until Dec 31 23:59:59 9999 GMT (2912992 days)
Sign the certificate? [y/n]:y
Genial, los certificados tienen las fechas mínima y máxima, pero hay algo que me preocupa: con este comando estoy creando un certificado nuevo, pero el certificado de la CA sigue teniendo las fechas antiguas establecidas. Podría seguir intentándolo con openssl para crear dicho certificado y quizás autofirmar la CA, pero ya se estaba volviendo demasiado complicado para mí. No solo tenía que averiguar la forma correcta de usar las opciones y banderas del comando, sino que, al usarlo para establecer fechas personalizadas, también tenía que configurar openssl.conf con directorios y archivos. Así que pasé a Python y creé los certificados programáticamente usando el paquete Cryptography.
Este es el repositorio con el código: https://gitlab.com/0x00cl/tlscertlimits
Fue mucho más fácil. Así es como creé una clave privada y un certificado para la CA.
|
|
Fue mucho más sencillo, especialmente para los atributos en los que simplemente se pueden «multiplicar» los caracteres en lugar de tener que escribirlos todos. Examinemos el certificado que acabamos de crear.
$ openssl x509 -noout -text -in certs/myCA.pem
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
0f:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:f0
Signature Algorithm: RSA-SHA3-512
Issuer: C=WW, ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW, L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW, O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW, CN=0x00.cl CA root
Validity
Not Before: Jan 1 00:00:00 1950 GMT
Not After : Dec 31 23:59:59 9999 GMT
Subject: C=WW, ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW, L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW, O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW, CN=0x00.cl CA root
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (16384 bit)
Modulus:
00:ce:5e:d0:f9:e1:76:71:b2:13:20:29:2b:a6:35:
0a:39:77:56:a7:06:39:5a:82:d6:92:72:bb:9c:9a:
41:84:32:b5:31:01:67:c0:01:f6:ad:bd:d2:72:89:
# ... cut 134 lines
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints: critical
CA:TRUE
X509v3 Key Usage: critical
Digital Signature, Certificate Sign, CRL Sign
X509v3 Subject Key Identifier:
9F:CC:F1:B4:36:98:8B:8C:24:CB:0A:A2:F5:50:5C:5A:3A:AE:64:14
Signature Algorithm: RSA-SHA3-512
Signature Value:
b4:5a:57:72:68:59:85:a4:da:85:6f:f5:e8:db:44:01:a2:08:
21:de:ac:55:1b:c3:cb:51:4e:61:fd:2b:e5:93:78:45:62:b2:
af:3e:47:4b:fe:71:15:bb:37:c1:80:fb:a7:4d:49:d1:f2:85:
# ... cut 111 lines
Nota: El «Modulus» del certificado tiene 137 líneas de largo y el «Signature Value» tiene 114 líneas; eran demasiado largos y no eran relevantes, así que los recorté.
Perfecto, ya tenemos los valores mínimo y máximo para la fecha de un certificado válido.
También creé un certificado intermedio y, finalmente, uno para un sitio web. Como se hizo programáticamente, es tan simple como multiplicar campos y listas.
|
|
Los archivos resultantes:
$ ls -l myCA*
-rw-r--r--. 1 tomas tomas 12632 Jul 03 12:00 myCAinter.key
-rw-r--r--. 1 tomas tomas 7034 Jul 03 12:00 myCAinter.pem
-rw-r--r--. 1 tomas tomas 12632 Jul 03 12:00 myCA.key
-rw-r--r--. 1 tomas tomas 6977 Jul 03 12:00 myCA.pem
-rw-r--r--. 1 tomas tomas 12632 Jul 03 12:00 myCAweb.key
-rw-r--r--. 1 tomas tomas 1067089731 Jul 03 12:00 myCAweb.pem
Genial, terminamos con un archivo de 1 GB. Aunque debo decir que generar una lista con 4000 atributos x509 en Python acabó consumiendo mucha memoria RAM. Primero intenté con 40000, pero el programa se colgó por falta de memoria; con 4000 apenas funcionó y terminó usando casi todos los 16 GB de RAM de mi ordenador.
Ahora que hemos creado un certificado grande, necesitamos servirlo y ver cómo se comporta un navegador web. Decidí usar Caddy (v2.8.4) como servidor web, simplemente porque nunca lo había probado antes y quería experimentarlo. Aquí está el Caddyfile:
|
|
Descargué el binario y lo ejecuté así:
$ ./caddy run -c Caddyfile
Caddy tardó unos 20 segundos en iniciarse, pero funcionó sin emitir ningún error.
Primero intenté usar un navegador web (Firefox), pero cuando intenté cargar el sitio web (https://localhost:8443), seguía obteniendo el error PR_END_OF_FILE_ERROR.
Los registros de Caddy no muestran un ERROR, pero sí un registro DEBUG en el que aparece un mensaje de error de handshake TLS proveniente de crypto/cryptobyte/builder.go, aunque no estoy seguro de por qué ocurre el error ni por qué se clasifica como DEBUG y no como ERROR; quizás el error esté en el cliente.
2024/07/03 12:00:00.000 DEBUG http.stdlib http: TLS handshake error from 127.0.0.1:42146: cryptobyte: pending child length 788004684 exceeds 3-byte length prefix
También probé con curl (8.6.0), pero tampoco funcionó.
$ curl -kv https://localhost:8443
* Host localhost:8443 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:8443...
* Connected to localhost (::1) port 8443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to localhost:8443
* Closing connection
curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to localhost:8443
Al principio pensé que quizás no había creado los certificados correctamente, pero como sabía que estaba intentando cargar un certificado de 1 GB (cuando, en el mejor de los casos, suelen ser de unos pocos kB), probé con uno más pequeño y funcionó. Volví a intentar cargar el certificado grande, pero obtuve el mismo error. Quizás exista un límite en el tamaño máximo de un certificado. Intenté reducir la cantidad de campos para disminuir el tamaño del certificado a unos pocos MB, pero tampoco funcionó, así que fui reduciendo gradualmente el tamaño del certificado hasta que obtuve un nuevo error.
El tamaño del nuevo certificado era de 1,1 MB. Usando Firefox, obtuve el error SSL_ERROR_RX_MALFORMED_HANDSHAKE.
Además, el «error» en Caddy también era diferente, al igual que con curl.
2024/07/11 15:50:29.281 DEBUG http.stdlib http: TLS handshake error from 127.0.0.1:47612: remote error: tls: error decoding message
$ curl -vk https://localhost:8443
* Host localhost:8443 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:8443...
* Connected to localhost (::1) port 8443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (OUT), TLS alert, illegal parameter (559):
* OpenSSL/3.2.1: error:0A000098:SSL routines::excessive message size
* Closing connection
curl: (35) OpenSSL/3.2.1: error:0A000098:SSL routines::excessive message size
Al menos, ahora curl muestra un mensaje real para el error, que es «tamaño de mensaje excesivo». Parece que a curl no le gustan los certificados «grandes» y que existe un límite. Dado que curl sabía que el certificado era demasiado grande, debe haber un límite codificado o configurado, así que comencé a examinar el código fuente en git y encontré en las páginas de manual que el tamaño máximo de la cadena de certificados es de 100 kB. Por lo que pude entender del código fuente, esto se define en ssl/statem/statem_local.h (102400 bytes = 100 kB).
Así que intenté hacerlo tan grande como OpenSSL permitía (100 kB), reduciendo la cantidad de campos repetidos a 221, y funcionó:
$ curl -vk https://localhost:8443
* Host localhost:8443 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:8443...
* Connected to localhost (::1) port 8443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / x25519 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
* subject: C=WW; ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; CN=0x00.cl Web; C=WW; ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; CN=0x00.cl Web; C=WW; ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; CN=0x00.cl Web; C=WW; ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; CN=0x00.cl Web; C=WW; ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; CN=0x00.cl Web; C=WW; ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW
* start date: Jan 1 00:00:00 1950 GMT
* expire date: Dec 31 23:59:59 9999 GMT
* issuer: C=WW; ST=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; L=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; O=WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW; CN=0x00.cl CA Intermediate
* SSL certificate verify result: self-signed certificate in certificate chain (19), continuing anyway.
* Certificate level 0: Public key type RSA (16384/272 Bits/secBits), signed using RSA-SHA3-512
* Certificate level 1: Public key type RSA (16384/272 Bits/secBits), signed using RSA-SHA3-512
* Certificate level 2: Public key type RSA (16384/272 Bits/secBits), signed using RSA-SHA3-512
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://localhost:8443/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: localhost:8443]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.6.0]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: localhost:8443
> User-Agent: curl/8.6.0
> Accept: */*
>
< HTTP/2 200
< alt-svc: h3=":8443"; ma=2592000
< content-type: text/plain; charset=utf-8
< server: Caddy
< content-length: 13
< date: Fri, 03 Jul 2024 12:00:00 GMT
<
* Connection #0 to host localhost left intact
Hello, World!
Al usar el parámetro --trace-ascii - en curl, podemos obtener más información sobre el intercambio de datos.
$ curl -k --trace-ascii - https://localhost:8443
...
== Info: TLSv1.3 (IN), TLS handshake, Certificate (11):
<= Recv SSL data, 102404 bytes (0x19004)
...
Recibió 102404 bytes para el certificado, lo que está a solo 4 bytes del límite encontrado en el código fuente de OpenSSL. No estoy seguro de cómo calculan exactamente esos bytes ni curl ni OpenSSL, pero incluso añadir un solo carácter (que debería ser 1 byte) hace que curl (y OpenSSL) devuelva un error.
Algo interesante que descubrí fue que intenté usar Firefox para hacer una solicitud, pero me devolvió un mensaje de error diferente a los anteriores: SEC_ERROR_BAD_DER, aunque con curl sí funcionaba. Fue entonces cuando empecé a investigar un poco más y descubrí que Firefox utiliza nss. Aunque no pude encontrar dónde establecen el límite del tamaño del certificado, parece que limita el tamaño de las extensiones (secciones) del certificado. Por ejemplo, en el campo «subject» del certificado, pude multiplicar sus valores 221 veces con curl antes de que empezara a devolver errores, mientras que Firefox solo lo permitió 111 veces. Sin embargo, el campo «SubjectAlternativeName» pude «extenderlo» al menos 1000 veces y Firefox seguía funcionando, a pesar de que ya había alcanzado el límite de tamaño para los campos del subject.
Explorar los certificados TLS fue, sin duda, divertido. Aprendí bastante sobre ellos, especialmente porque tuve que leer varias veces la RFC 5280. Pero también me entristece un poco que, aunque técnicamente un certificado de 1 GB podría funcionar, al final esté limitado por los clientes, como tu navegador web o herramientas de línea de comandos como curl. Esperaba poder hacer algo más «locura», como usar fechas negativas o una fecha más allá del año 9999. Por ejemplo, OpenSSL sí permitía establecer una fecha anterior a 1950, como 0000, pero eso no cumpliría con el estándar RFC 5280, y el paquete de Python cryptography sí impone un límite de 1950.
En la introducción de este blog me preguntaba:
Y espero que también te haya sido fácil de entender y que hayas aprendido algo sobre los certificados TLS con este blog. :)