urlerror sslerror requests pythonhttpsverify failed error certificate_verify_failed python https ssl-certificate verification

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/



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.