¿Cómo puedo recuperar el certificado de igual TLS / SSL de un host remoto usando python?

Necesito escanear una lista de IP y recuperar el nombre común del certificado en esa IP (para cada IP que permita las conexiones del puerto 443). He podido hacer esto con éxito usando los módulos sockets y ssl. Funciona para todas las IP con certificados firmados válidos, pero no funciona para certificados autofirmados.

Si utilizo este método, se requiere un certificado válido verificado por mi paquete CA:

from socket import socket import ssl s = socket() c = ssl.wrap_socket(s,cert_reqs=ssl.CERT_REQUIRED, ca_certs='ca-bundle.crt') c.connect(('127.0.0.1', 443)) print c.getpeercert() 

Si cert_reqs=ssl.CERT_REQUIRED , se conecta pero no obtiene el certificado.

¿Cómo puedo recuperar el nombre común de un certificado en una IP, ya sea que se valide con el paquete ca o no?

La biblioteca ssl de python parece que solo analiza el certificado por usted si tiene una firma válida.

  """Returns a formatted version of the data in the certificate provided by the other end of the SSL channel. Return None if no certificate was provided, {} if a certificate was provided, but not validated.""" 

Aún puede obtener el certificado del servidor con la función ssl.get_server_certificate() , pero lo devuelve en formato PEM. (Alternativamente, puede llamar a c.getpeercert(True) , que devuelve el certificado en formato binario DER, ya sea que esté validado o no).

 >>> print ssl.get_server_certificate(('server.test.com', 443)) -----BEGIN CERTIFICATE----- MIID4zCCAsugAwIBA..... 

Desde aquí, usaría M2Crypto o OpenSSL para leer el certificado y obtener valores:

 # M2Crypto cert = ssl.get_server_certificate(('www.google.com', 443)) x509 = M2Crypto.X509.load_cert_string(cert) x509.get_subject().as_text() # 'C=US, ST=California, L=Mountain View, O=Google Inc, CN=www.google.com' # OpenSSL x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert) x509.get_subject().get_components() #[('C', 'US'), # ('ST', 'California'), # ('L', 'Mountain View'), # ('O', 'Google Inc'), # ('CN', 'www.google.com')] 

En Mac necesitas instalar swig y M2Crypto

En ejecución terminal:

 brew install swig 

Y entonces:

 sudo pip install m2crypto 

A continuación, puede ejecutar el código anterior:

 from socket import socket import ssl import M2Crypto import OpenSSL # M2Crypto cert = ssl.get_server_certificate(('www.google.com', 443)) x509 = M2Crypto.X509.load_cert_string(cert) print x509.get_subject().as_text() # 'C=US, ST=California, L=Mountain View, O=Google Inc, CN=www.google.com' # OpenSSL x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert) print x509.get_subject().get_components() #[('C', 'US'), # ('ST', 'California'), # ('L', 'Mountain View'), # ('O', 'Google Inc'), # ('CN', 'www.google.com')] 

Si alguien está luchando con SNI (Indicación del nombre del servidor) (mencionado por @mootmoot) consulte mi respuesta aquí https://stackoverflow.com/a/49132495/8370670 .

Recuperar y analizar con el soporte de SNI, analizar fechas y mostrar datos de extensión (como subjectAltName ):

 import ssl import socket import OpenSSL from pprint import pprint from datetime import datetime def get_certificate(host, port=443, timeout=10): context = ssl.create_default_context() conn = socket.create_connection((host, port)) sock = context.wrap_socket(conn, server_hostname=host) sock.settimeout(timeout) try: der_cert = sock.getpeercert(True) finally: sock.close() return ssl.DER_cert_to_PEM_cert(der_cert) certificate = get_certificate('example.com') x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, certificate) result = { 'subject': dict(x509.get_subject().get_components()), 'issuer': dict(x509.get_issuer().get_components()), 'serialNumber': x509.get_serial_number(), 'version': x509.get_version(), 'notBefore': datetime.strptime(x509.get_notBefore(), '%Y%m%d%H%M%SZ'), 'notAfter': datetime.strptime(x509.get_notAfter(), '%Y%m%d%H%M%SZ'), } extensions = (x509.get_extension(i) for i in range(x509.get_extension_count())) extension_data = {e.get_short_name(): str(e) for e in extensions} result.update(extension_data) pprint(result) 

Salida:

 {'authorityInfoAccess': 'OCSP - URI:http://ocsp.digicert.com\nCA Issuers - URI:http://cacerts.digicert.com/DigiCertSHA2SecureServerCA.crt\n', 'authorityKeyIdentifier': 'keyid:0F:80:61:1C:82:31:61:D5:2F:28:E7:8D:46:38:B4:2C:E1:C6:D9:E2\n', 'basicConstraints': 'CA:FALSE', 'certificatePolicies': 'Policy: 2.16.840.1.114412.1.1\n CPS: https://www.digicert.com/CPS\nPolicy: 2.23.140.1.2.2\n', 'crlDistributionPoints': '\nFull Name:\n URI:http://crl3.digicert.com/ssca-sha2-g6.crl\n\nFull Name:\n URI:http://crl4.digicert.com/ssca-sha2-g6.crl\n', 'ct_precert_scts': 'Signed Certificate Timestamp:\n Version : v1 (0x0)\n Log ID : A4:B9:09:90:B4:18:58:14:87:BB:13:A2:CC:67:70:0A:\n 3C:35:98:04:F9:1B:DF:B8:E3:77:CD:0E:C8:0D:DC:10\n Timestamp : Nov 28 21:20:12.614 2018 GMT\n Extensions: none\n Signature : ecdsa-with-SHA256\n 30:46:02:21:00:84:64:81:B7:21:1D:FA:1A:48:F5:76:\n AE:4B:E8:46:86:57:27:17:B0:7B:E9:3B:B7:4A:57:42:\n 6C:A2:84:C4:6C:02:21:00:BB:93:B5:FE:30:C4:64:E4:\n 16:4C:7C:6E:58:53:57:EE:EC:7F:AA:45:4F:BF:0E:46:\n 8E:FE:70:FD:FD:8E:42:42\nSigned Certificate Timestamp:\n Version : v1 (0x0)\n Log ID : 87:75:BF:E7:59:7C:F8:8C:43:99:5F:BD:F3:6E:FF:56:\n 8D:47:56:36:FF:4A:B5:60:C1:B4:EA:FF:5E:A0:83:0F\n Timestamp : Nov 28 21:20:12.821 2018 GMT\n Extensions: none\n Signature : ecdsa-with-SHA256\n 30:45:02:20:6F:AA:77:D2:1C:A7:94:C0:63:2D:2E:B3:\n 86:DD:41:8B:40:8A:1A:2F:7F:AE:66:C1:93:5F:73:1F:\n 48:93:50:11:02:21:00:D2:F9:9D:48:86:05:1E:A0:97:\n 44:25:0B:3C:EA:CE:FA:2B:19:7C:81:FF:27:7B:9E:DB:\n 58:B6:DC:E8:F0:4A:4E\nSigned Certificate Timestamp:\n Version : v1 (0x0)\n Log ID : 6F:53:76:AC:31:F0:31:19:D8:99:00:A4:51:15:FF:77:\n 15:1C:11:D9:02:C1:00:29:06:8D:B2:08:9A:37:D9:13\n Timestamp : Nov 28 21:20:12.956 2018 GMT\n Extensions: none\n Signature : ecdsa-with-SHA256\n 30:45:02:21:00:E4:79:FB:43:84:8E:CA:A1:E4:4F:E9:\n 03:B0:7A:BB:92:EE:F3:44:3B:8C:EC:FE:14:0D:7D:9F:\n B7:63:29:9F:2D:02:20:4D:77:5A:DC:49:01:4A:F4:68:\n 04:85:61:9F:D7:8D:20:0C:31:FA:C1:D3:F4:71:0A:5B:\n D6:56:CB:3D:2C:72:8C', 'extendedKeyUsage': 'TLS Web Server Authentication, TLS Web Client Authentication', 'issuer': {'C': 'US', 'CN': 'DigiCert SHA2 Secure Server CA', 'O': 'DigiCert Inc'}, 'keyUsage': 'Digital Signature, Key Encipherment', 'notAfter': datetime.datetime(2020, 12, 2, 12, 0), 'notBefore': datetime.datetime(2018, 11, 28, 0, 0), 'serialNumber': 21020869104500376438182461249190639870L, 'subject': {'C': 'US', 'CN': 'www.example.org', 'L': 'Los Angeles', 'O': 'Internet Corporation for Assigned Names and Numbers', 'OU': 'Technology', 'ST': 'California'}, 'subjectAltName': 'DNS:www.example.org, DNS:example.com, DNS:example.edu, DNS:example.net, DNS:example.org, DNS:www.example.com, DNS:www.example.edu, DNS:www.example.net', 'subjectKeyIdentifier': '66:98:62:02:E0:09:91:A7:D9:E3:36:FB:76:C6:B0:BF:A1:6D:A7:BE', 'version': 2}