ssl.get_server_certificate para sitios con SNI (Indicación del nombre del servidor)

Estoy tratando de obtener el certificado de servidor de los subdominios de badssl.com (por ejemplo, https://expired.badssl.com ).

import ssl ssl.get_server_certificate(('expired.badssl.com', 443)) 

Pero al examinar el certificado generado anteriormente veo que el certificado tiene

Identidad: badssl-fallback-unknown-subdomain-or-no-sni

lo que significa que SNI está fallando. ¿Cómo puedo obtener el certificado de servidor de diferentes subdominios de badssl.com? (Estoy usando python 2.7.12)

Encontré la respuesta.

 import ssl hostname = "expired.badssl.com" port = 443 conn = ssl.create_connection((hostname, port)) context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) sock = context.wrap_socket(conn, server_hostname=hostname) certificate = ssl.DER_cert_to_PEM_cert(sock.getpeercert(True)) 

La búsqueda de “Python ssl.get_server_certificate SNI” me llevó fácilmente a esta respuesta. Aunque la respuesta del OP es correcta, me gustaría brindar un poco más de información para futuras referencias.

Con algunos [nombre de host] s la llamada a continuación con Python 3.7:

 ssl.get_server_certificate(("example.com", 443) 

Se quejará con un rastreo que termina con:

 ssl.SSLError: [SSL: TLSV1_ALERT_INTERNAL_ERROR] tlsv1 alert internal error (_ssl.c:1045) 

Hacer una investigación adicional, haciendo uso de la utilidad openssl s_client , permite descubrir que esos mismos [nombre de host] que hicieron que el get_server_certificate fallara, también hace el siguiente comando:

 openssl s_client -showcerts -connect example.com:443 

fallar con este error:

 SSL23_GET_SERVER_HELLO:tlsv1 alert internal error:s23_clnt.c:802 

Tenga en cuenta que el mensaje de error es similar al que devuelve el código de Python.

El uso del interruptor -servername hizo el truco:

 openssl s_client -showcerts -connect example.com:443 -servername example.com 

lo que lleva a la conclusión de que el nombre de host investigado se refiere a un servidor seguro que hace uso de SNI (una buena explicación de lo que eso significa se encuentra en el artículo de Wikipedia de SNI ).

Entonces, volviendo a Python y observando el método get_server_certificate , examinando la fuente del módulo ssl ( aquí para su conveniencia), puede descubrir que la función incluye esta llamada:

 context.wrap_socket(sock) 

sin el argumento clave de server_hostname=hostname , lo que por supuesto debería significar que no se puede usar get_server_certificate para consultar un servidor SNI. Se requiere un poco más de esfuerzo:

 hostname = "example.com" port = 443 context = ssl.create_default_context() with socket.create_connection((hostname, port)) as sock: with context.wrap_socket(sock, server_hostname=hostname) as sslsock: der_cert = sslsock.getpeercert(True) # from binary DER format to PEM pem_cert = ssl.DER_cert_to_PEM_cert(der_cert) print(pem_cert)