web-dev-qa-db-fra.com

Comment puis-je récupérer le certificat homologue TLS/SSL d'un hôte distant à l'aide de python?

Je dois parcourir une liste d'adresses IP et récupérer le nom commun du certificat sur cette adresse IP (pour chaque adresse IP qui autorise les connexions au port 443). J'ai réussi à le faire avec les sockets et les modules ssl. Cela fonctionne pour toutes les IP avec des certificats valides et signés, mais pas pour les certificats auto-signés.

Si j'utilise cette méthode, un certificat valide, vérifié par mon ensemble d'autorité de certification, est nécessaire:

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 je supprime le cert_reqs=ssl.CERT_REQUIRED, il se connecte mais ne reçoit pas le certificat du tout.

Comment puis-je récupérer le nom commun d'un certificat sur une adresse IP, que celui-ci soit validé par rapport au ca-bundle ou non?

30
Dave Forgac

Il semble que la bibliothèque python ssl n’analyse le certificat que s’il possède une signature valide.

    """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."""

Vous pouvez toujours obtenir le certificat de serveur avec la fonction ssl.get_server_certificate(), mais il le renvoie au format PEM. (Vous pouvez également appeler c.getpeercert(True), qui renvoie le certificat au format binaire DER, qu'il soit validé ou non.)

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

A partir de là, j'utiliserais M2Crypto ou OpenSSL pour lire le cert et obtenir les valeurs:

# 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')]
40
JimB

Sur Mac, vous devez installer swig et M2Crypto.

Sur le terminal:

brew install swig

Et alors:

Sudo pip install m2crypto

Ensuite, vous pouvez exécuter le code ci-dessus:

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')]
4
Gil Allen

Si quelqu'un a des problèmes avec SNI (Server Name Indication) (mentionné par @mootmoot), reportez-vous à ma réponse ici https://stackoverflow.com/a/49132495/8370670 .

0
serbia99

Récupération et analyse avec le support SNI, analyse des dates et affichage des données d'extension (telles que 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)

Sortie:

{'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}
0
Raz