sslerror - Validar certificados SSL con Python
urlopen error ssl certificate_verify_failed certificate verify failed(_ ssl c 645)> (10)
Necesito escribir un script que se conecte a un grupo de sitios en nuestra intranet corporativa a través de HTTPS y verifique que sus certificados SSL sean válidos; que no están caducados, que se emitieron para la dirección correcta, etc. Usamos nuestra propia Autoridad de certificación corporativa interna para estos sitios, por lo que tenemos la clave pública de la CA para verificar los certificados.
Python acepta de manera predeterminada y usa certificados SSL cuando usa HTTPS, por lo que incluso si un certificado no es válido, las bibliotecas de Python como urllib2 y Twisted simplemente usarán el certificado.
¿Hay alguna buena biblioteca en alguna parte que me permita conectarme a un sitio a través de HTTPS y verificar su certificado de esta manera?
¿Cómo verifico un certificado en Python?
Aquí hay un script de ejemplo que demuestra la validación del certificado:
import httplib
import re
import socket
import sys
import urllib2
import ssl
class InvalidCertificateException(httplib.HTTPException, urllib2.URLError):
def __init__(self, host, cert, reason):
httplib.HTTPException.__init__(self)
self.host = host
self.cert = cert
self.reason = reason
def __str__(self):
return (''Host %s returned an invalid certificate (%s) %s/n'' %
(self.host, self.reason, self.cert))
class CertValidatingHTTPSConnection(httplib.HTTPConnection):
default_port = httplib.HTTPS_PORT
def __init__(self, host, port=None, key_file=None, cert_file=None,
ca_certs=None, strict=None, **kwargs):
httplib.HTTPConnection.__init__(self, host, port, strict, **kwargs)
self.key_file = key_file
self.cert_file = cert_file
self.ca_certs = ca_certs
if self.ca_certs:
self.cert_reqs = ssl.CERT_REQUIRED
else:
self.cert_reqs = ssl.CERT_NONE
def _GetValidHostsForCert(self, cert):
if ''subjectAltName'' in cert:
return [x[1] for x in cert[''subjectAltName'']
if x[0].lower() == ''dns'']
else:
return [x[0][1] for x in cert[''subject'']
if x[0][0].lower() == ''commonname'']
def _ValidateCertificateHostname(self, cert, hostname):
hosts = self._GetValidHostsForCert(cert)
for host in hosts:
host_re = host.replace(''.'', ''/.'').replace(''*'', ''[^.]*'')
if re.search(''^%s$'' % (host_re,), hostname, re.I):
return True
return False
def connect(self):
sock = socket.create_connection((self.host, self.port))
self.sock = ssl.wrap_socket(sock, keyfile=self.key_file,
certfile=self.cert_file,
cert_reqs=self.cert_reqs,
ca_certs=self.ca_certs)
if self.cert_reqs & ssl.CERT_REQUIRED:
cert = self.sock.getpeercert()
hostname = self.host.split('':'', 0)[0]
if not self._ValidateCertificateHostname(cert, hostname):
raise InvalidCertificateException(hostname, cert,
''hostname mismatch'')
class VerifiedHTTPSHandler(urllib2.HTTPSHandler):
def __init__(self, **kwargs):
urllib2.AbstractHTTPHandler.__init__(self)
self._connection_args = kwargs
def https_open(self, req):
def http_class_wrapper(host, **kwargs):
full_kwargs = dict(self._connection_args)
full_kwargs.update(kwargs)
return CertValidatingHTTPSConnection(host, **full_kwargs)
try:
return self.do_open(http_class_wrapper, req)
except urllib2.URLError, e:
if type(e.reason) == ssl.SSLError and e.reason.args[0] == 1:
raise InvalidCertificateException(req.host, '''',
e.reason.args[1])
raise
https_request = urllib2.HTTPSHandler.do_request_
if __name__ == "__main__":
if len(sys.argv) != 3:
print "usage: python %s CA_CERT URL" % sys.argv[0]
exit(2)
handler = VerifiedHTTPSHandler(ca_certs = sys.argv[1])
opener = urllib2.build_opener(handler)
print opener.open(sys.argv[2]).read()
Desde la versión de lanzamiento 2.7.9 / 3.4.3 en adelante, Python por defecto intenta realizar la validación del certificado.
Esto ha sido propuesto en PEP 467, que vale la pena leer: https://www.python.org/dev/peps/pep-0476/
Los cambios afectan a todos los módulos stdlib relevantes (urllib / urllib2, http, httplib).
Documentación relevante:
https://docs.python.org/2/library/httplib.html#httplib.HTTPSConnection
Esta clase ahora realiza todos los certificados necesarios y las verificaciones del nombre de host de forma predeterminada. Para volver al comportamiento anterior, no verificado, ssl._create_unverified_context () se puede pasar al parámetro de contexto.
https://docs.python.org/3/library/http.client.html#http.client.HTTPSConnection
Modificado en la versión 3.4.3: esta clase ahora realiza todos los certificados necesarios y las verificaciones del nombre de host de forma predeterminada. Para volver al comportamiento anterior, no verificado, ssl._create_unverified_context () se puede pasar al parámetro de contexto.
Tenga en cuenta que la nueva verificación incorporada se basa en la base de datos de certificados proporcionada por el sistema . En oposición a eso, el paquete de solicitudes envía su propio paquete de certificados. Las ventajas y desventajas de ambos enfoques se analizan en la sección Base de datos de confianza de PEP 476 .
Estaba teniendo el mismo problema pero quería minimizar las dependencias de terceros (porque este script único debía ser ejecutado por muchos usuarios). Mi solución fue envolver una llamada curl
y asegurarme de que el código de salida fuera 0
. Trabajado como un encanto.
He agregado una distribución al Python Package Index que hace que la función match_hostname()
del paquete Python 3.2 ssl
esté disponible en versiones anteriores de Python.
http://pypi.python.org/pypi/backports.ssl_match_hostname/
Puedes instalarlo con:
pip install backports.ssl_match_hostname
O puede hacer que sea una dependencia incluida en el setup.py
su proyecto. De cualquier manera, se puede usar así:
from backports.ssl_match_hostname import match_hostname, CertificateError
...
sslsock = ssl.wrap_socket(sock, ssl_version=ssl.PROTOCOL_SSLv3,
cert_reqs=ssl.CERT_REQUIRED, ca_certs=...)
try:
match_hostname(sslsock.getpeercert(), hostname)
except CertificateError, ce:
...
Jython REALIZA la verificación del certificado de forma predeterminada, por lo que el uso de módulos de biblioteca estándar, por ejemplo, httplib.HTTPSConnection, etc., con jython verificará los certificados y dará excepciones por fallas, es decir, identidades no coincidentes, certes caducados, etc.
De hecho, tienes que hacer un trabajo extra para que jython se comporte como cpython, es decir, para que jython NO verifique los certificados.
He escrito una publicación de blog sobre cómo desactivar la comprobación de certificados en jython, porque puede ser útil en fases de prueba, etc.
Instalar un proveedor de seguridad confiable en java y jython.
http://jython.xhaus.com/installing-an-all-trusting-security-provider-on-java-and-jython/
O simplemente haz tu vida más fácil usando la biblioteca de requests :
import requests
requests.get(''https://somesite.com'', cert=''/path/server.crt'', verify=True)
Puede usar Twisted para verificar certificados. La API principal es CertificateOptions , que se puede proporcionar como el argumento contextFactory
para varias funciones como listenSSL y startTLS .
Lamentablemente, ni Python ni Twisted incluyen una pila de certificados de CA necesarios para realizar la validación de HTTPS ni la lógica de validación de HTTPS. Debido a una limitación en PyOpenSSL , no puede hacerlo completamente correctamente todavía, pero gracias al hecho de que casi todos los certificados incluyen un sujeto commonName, puede acercarse lo suficiente.
Aquí hay una implementación ingenua de muestra de un cliente Twisted HTTPS verificador que ignora comodines y extensiones subjectAltName, y usa los certificados de autoridad de certificado presentes en el paquete ''ca-certificates'' en la mayoría de las distribuciones de Ubuntu. Pruébalo con tus sitios de certificados válidos y no válidos :).
import os
import glob
from OpenSSL.SSL import Context, TLSv1_METHOD, VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT, OP_NO_SSLv2
from OpenSSL.crypto import load_certificate, FILETYPE_PEM
from twisted.python.urlpath import URLPath
from twisted.internet.ssl import ContextFactory
from twisted.internet import reactor
from twisted.web.client import getPage
certificateAuthorityMap = {}
for certFileName in glob.glob("/etc/ssl/certs/*.pem"):
# There might be some dead symlinks in there, so let''s make sure it''s real.
if os.path.exists(certFileName):
data = open(certFileName).read()
x509 = load_certificate(FILETYPE_PEM, data)
digest = x509.digest(''sha1'')
# Now, de-duplicate in case the same cert has multiple names.
certificateAuthorityMap[digest] = x509
class HTTPSVerifyingContextFactory(ContextFactory):
def __init__(self, hostname):
self.hostname = hostname
isClient = True
def getContext(self):
ctx = Context(TLSv1_METHOD)
store = ctx.get_cert_store()
for value in certificateAuthorityMap.values():
store.add_cert(value)
ctx.set_verify(VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, self.verifyHostname)
ctx.set_options(OP_NO_SSLv2)
return ctx
def verifyHostname(self, connection, x509, errno, depth, preverifyOK):
if preverifyOK:
if self.hostname != x509.get_subject().commonName:
return False
return preverifyOK
def secureGet(url):
return getPage(url, HTTPSVerifyingContextFactory(URLPath.fromString(url).netloc))
def done(result):
print ''Done!'', len(result)
secureGet("https://google.com/").addCallback(done)
reactor.run()
M2Crypto puede hacer la validación . También puede usar M2Crypto con Twisted si lo desea. El cliente de escritorio Chandler usa Twisted para redes y M2Crypto para SSL , incluida la validación de certificados.
Basado en el comentario de Glyphs, parece que M2Crypto realiza una mejor verificación de certificados de forma predeterminada que lo que puede hacer con pyOpenSSL actualmente, porque M2Crypto también comprueba el campo subjectAltName.
También he blogueado sobre cómo obtener los certificados que Mozilla Firefox incluye en Python y que se pueden usar con las soluciones Python SSL.
PycURL hace esto bellamente.
A continuación hay un breve ejemplo. pycurl.error
un pycurl.error
si hay algo sospechoso, donde se obtiene una tupla con un código de error y un mensaje legible para humanos.
import pycurl
curl = pycurl.Curl()
curl.setopt(pycurl.CAINFO, "myFineCA.crt")
curl.setopt(pycurl.SSL_VERIFYPEER, 1)
curl.setopt(pycurl.SSL_VERIFYHOST, 2)
curl.setopt(pycurl.URL, "https://internal.stuff/")
curl.perform()
Es probable que desee configurar más opciones, como dónde almacenar los resultados, etc. Pero no es necesario desordenar el ejemplo con elementos no esenciales.
Ejemplo de las excepciones que pueden surgir:
(60, ''Peer certificate cannot be authenticated with known CA certificates'')
(51, "common name ''CN=something.else.stuff,O=Example Corp,C=SE'' does not match ''internal.stuff''")
Algunos enlaces que encontré útiles son los documentos libcurl-docs para setopt y getinfo.
pyOpenSSL es una interfaz para la biblioteca OpenSSL. Debe proporcionar todo lo que necesita.