Obtención de la cadena de certificados con el módulo SSL de Python 3.3

Puedo obtener la información del certificado estándar para una conexión SSL en Python 3.3 a través del método getpeercert () en el socket SSL. Sin embargo, parece que no proporciona la cadena como OpenSSL, la herramienta “s_client”.

¿Hay alguna forma de obtener esto para poder ver si mi certificado IA se configuró correctamente?

s_client línea de comandos:

openssl s_client -connect google.com:443 

Resultado de s_client (solo las primeras lineas):

 $ openssl s_client -connect google.com:443 CONNECTED(00000003) depth=2 C = US, O = GeoTrust Inc., CN = GeoTrust Global CA verify error:num=20:unable to get local issuer certificate verify return:0 --- Certificate chain 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=*.google.com i:/C=US/O=Google Inc/CN=Google Internet Authority G2 1 s:/C=US/O=Google Inc/CN=Google Internet Authority G2 i:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA 2 s:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA i:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority --- 

Código de Python 3.3:

 import socket from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 from ssl import SSLContext # Modern SSL? from ssl import HAS_SNI # Has SNI? from pprint import pprint def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, ca_certs=None, server_hostname=None, ssl_version=None): context = SSLContext(ssl_version) context.verify_mode = cert_reqs if ca_certs: try: context.load_verify_locations(ca_certs) # Py32 raises IOError # Py33 raises FileNotFoundError except Exception as e: # Reraise as SSLError raise SSLError(e) if certfile: # FIXME: This block needs a test. context.load_cert_chain(certfile, keyfile) if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI return context.wrap_socket(sock, server_hostname=server_hostname) return context.wrap_socket(sock) hostname = 'www.google.com' s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((hostname, 443)) sslSocket = ssl_wrap_socket(s, ssl_version=2, cert_reqs=2, ca_certs='/usr/local/lib/python3.3/dist-packages/requests/cacert.pem', server_hostname=hostname) pprint(sslSocket.getpeercert()) s.close() 

Resultado del código:

 {'issuer': ((('countryName', 'US'),), (('organizationName', 'Google Inc'),), (('commonName', 'Google Internet Authority G2'),)), 'notAfter': 'Sep 25 15:09:31 2014 GMT', 'notBefore': 'Sep 25 15:09:31 2013 GMT', 'serialNumber': '13A87ADB3E733D3B', 'subject': ((('countryName', 'US'),), (('stateOrProvinceName', 'California'),), (('localityName', 'Mountain View'),), (('organizationName', 'Google Inc'),), (('commonName', 'www.google.com'),)), 'subjectAltName': (('DNS', 'www.google.com'),), 'version': 3} 

Gracias a la respuesta que aportó Aleksi, encontré una solicitud de error / función que ya solicitaba esto: http://bugs.python.org/issue18233 . Aunque los cambios aún no se han finalizado, sí tienen un parche que lo hace disponible:

Este es el código de prueba que he robado de alguna fuente olvidada y reensamblado:

 import socket from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 from ssl import SSLContext # Modern SSL? from ssl import HAS_SNI # Has SNI? from pprint import pprint def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, ca_certs=None, server_hostname=None, ssl_version=None): context = SSLContext(ssl_version) context.verify_mode = cert_reqs if ca_certs: try: context.load_verify_locations(ca_certs) # Py32 raises IOError # Py33 raises FileNotFoundError except Exception as e: # Reraise as SSLError raise SSLError(e) if certfile: # FIXME: This block needs a test. context.load_cert_chain(certfile, keyfile) if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI return (context, context.wrap_socket(sock, server_hostname=server_hostname)) return (context, context.wrap_socket(sock)) hostname = 'www.google.com' print("Hostname: %s" % (hostname)) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((hostname, 443)) (context, ssl_socket) = ssl_wrap_socket(s, ssl_version=2, cert_reqs=2, ca_certs='/usr/local/lib/python3.3/dist-packages/requests/cacert.pem', server_hostname=hostname) pprint(ssl_socket.getpeercertchain()) s.close() 

Salida:

 Hostname: www.google.com ({'issuer': ((('countryName', 'US'),), (('organizationName', 'Google Inc'),), (('commonName', 'Google Internet Authority G2'),)), 'notAfter': 'Sep 11 11:04:38 2014 GMT', 'notBefore': 'Sep 11 11:04:38 2013 GMT', 'serialNumber': '50C71E48BCC50676', 'subject': ((('countryName', 'US'),), (('stateOrProvinceName', 'California'),), (('localityName', 'Mountain View'),), (('organizationName', 'Google Inc'),), (('commonName', 'www.google.com'),)), 'subjectAltName': (('DNS', 'www.google.com'),), 'version': 3}, {'issuer': ((('countryName', 'US'),), (('organizationName', 'GeoTrust Inc.'),), (('commonName', 'GeoTrust Global CA'),)), 'notAfter': 'Apr 4 15:15:55 2015 GMT', 'notBefore': 'Apr 5 15:15:55 2013 GMT', 'serialNumber': '023A69', 'subject': ((('countryName', 'US'),), (('organizationName', 'Google Inc'),), (('commonName', 'Google Internet Authority G2'),)), 'version': 3}, {'issuer': ((('countryName', 'US'),), (('organizationName', 'Equifax'),), (('organizationalUnitName', 'Equifax Secure Certificate Authority'),)), 'notAfter': 'Aug 21 04:00:00 2018 GMT', 'notBefore': 'May 21 04:00:00 2002 GMT', 'serialNumber': '12BBE6', 'subject': ((('countryName', 'US'),), (('organizationName', 'GeoTrust Inc.'),), (('commonName', 'GeoTrust Global CA'),)), 'version': 3}, {'issuer': ((('countryName', 'US'),), (('organizationName', 'Equifax'),), (('organizationalUnitName', 'Equifax Secure Certificate Authority'),)), 'notAfter': 'Aug 22 16:41:51 2018 GMT', 'notBefore': 'Aug 22 16:41:51 1998 GMT', 'serialNumber': '35DEF4CF', 'subject': ((('countryName', 'US'),), (('organizationName', 'Equifax'),), (('organizationalUnitName', 'Equifax Secure Certificate Authority'),)), 'version': 3}) 

No estoy seguro, pero creo que parte de la API de OpenSSL simplemente no está disponible en el módulo ssl de Python.

Parece que la función SSL_get_peer_cert_chain se usa para acceder a la cadena de certificados en OpenSSL. Consulte, por ejemplo, la sección de openssl s_client que imprime la salida que incluyó. Por otro lado, grepping la fuente del módulo ssl de Python para SSL_get_peer_cert_chain no produce coincidencias.

M2Crypto y pyOpenSSL parecen incluir una función get_peer_cert_chain , si estás dispuesto a ver otras bibliotecas (y no stdlib). Sin embargo, no puedo responder por ellos personalmente, ya que no los he usado mucho.