Preciso escrever um script que se conecte a vários sites em nossa intranet corporativa por HTTPS e verifique se seus certificados SSL são válidos; que não tenham expirado, que tenham sido emitidos para o endereço correto etc. Usamos nossa própria Autoridade de Certificação corporativa interna para esses sites, portanto, temos a chave pública da CA para verificar os certificados.
Por padrão, o Python apenas aceita e usa certificados SSL ao usar HTTPS, portanto, mesmo se um certificado for inválido, as bibliotecas Python, como urllib2 e Twisted, usarão o certificado com prazer.
Existe uma boa biblioteca em algum lugar que me permita conectar a um site por HTTPS e verificar seu certificado dessa forma?
Como faço para verificar um certificado em Python?
fonte
treq
outwisted.web.client.Agent
desde a versão 14.0, Twisted verifica os certificados por padrão.Respostas:
A partir da versão 2.7.9 / 3.4.3, o Python, por padrão, tenta realizar a validação do certificado.
Isso foi proposto no PEP 467, que vale a pena ler: https://www.python.org/dev/peps/pep-0476/
As mudanças afetam todos os módulos stdlib relevantes (urllib / urllib2, http, httplib).
Documentação relevante:
https://docs.python.org/2/library/httplib.html#httplib.HTTPSConnection
https://docs.python.org/3/library/http.client.html#http.client.HTTPSConnection
Observe que a nova verificação integrada é baseada no banco de dados de certificados fornecido pelo sistema . Oposto a isso, o pacote de solicitações envia seu próprio pacote de certificados. Prós e contras de ambas as abordagens são discutidos na seção de banco de dados de confiança do PEP 476 .
fonte
HTTPSConnection
classe? Eu estava usandoSSLSocket
. Como posso fazer a validação comSSLSocket
? Devo validar explicitamente usandopyopenssl
como explicado aqui ?Eu adicionei uma distribuição ao Python Package Index que disponibiliza a
match_hostname()
função dossl
pacote Python 3.2 em versões anteriores do Python.http://pypi.python.org/pypi/backports.ssl_match_hostname/
Você pode instalá-lo com:
Ou você pode torná-lo uma dependência listada no do seu projeto
setup.py
. De qualquer forma, ele pode ser usado assim: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: ...
fonte
getpeercert()
método chamado para que a saída possa ser passadamatch_hostname()
.Você pode usar Twisted para verificar os certificados. A API principal é CertificateOptions , que pode ser fornecida como
contextFactory
argumento para várias funções, como listenSSL e startTLS .Infelizmente, nem Python nem Twisted vêm com a pilha de certificados CA necessários para realmente fazer a validação HTTPS, nem a lógica de validação HTTPS. Devido a uma limitação no PyOpenSSL , você não pode fazer isso completamente correto ainda, mas graças ao fato de que quase todos os certificados incluem um assunto commonName, você pode chegar perto o suficiente.
Aqui está um exemplo ingênuo de implementação de um cliente HTTPS Twisted de verificação que ignora curingas e extensões subjectAltName e usa os certificados de autoridade de certificação presentes no pacote 'ca-certificates' na maioria das distribuições Ubuntu. Experimente com seus sites favoritos de certificados válidos e invá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()
fonte
URLPath(url).netloc
que: isso significa a parte do host da URL passada para secureGet. Em outras palavras, ele está verificando se o commonName do assunto é o mesmo que está sendo solicitado pelo chamador.https://www.google.com/
porque um dos assuntos é 'www.google.com', fazendo com que a função retorne False. Provavelmente significava retornar True (aceito) se os nomes corresponderem e False se não corresponderem?PycURL faz isso lindamente.
Abaixo está um pequeno exemplo. Ele irá lançar um
pycurl.error
if something is fish, onde você obtém uma tupla com código de erro e uma mensagem legível por 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()
Você provavelmente desejará configurar mais opções, como onde armazenar os resultados, etc. Mas não há necessidade de confundir o exemplo com itens não essenciais.
Exemplo de quais exceções podem ser levantadas:
(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'")
Alguns links que achei úteis são libcurl-docs para setopt e getinfo.
fonte
Ou simplesmente torne sua vida mais fácil usando a biblioteca de solicitações :
import requests requests.get('https://somesite.com', cert='/path/server.crt', verify=True)
Mais algumas palavras sobre seu uso.
fonte
cert
argumento é o certificado do cliente, não um certificado do servidor para verificar. Você quer usar overify
argumento.verify
argumento, exceto para ser mais explícito ou desabilitar a verificação.Aqui está um exemplo de script que demonstra a validação do 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()
fonte
CertValidatingHTTPSConnection.connect
. Consulte esta solicitação de pull para obter detalhes (e uma correção).backports.ssl_match_hostname
.M2Crypto pode fazer a validação . Você também pode usar M2Crypto com Twisted, se desejar. O cliente de desktop Chandler usa Twisted para rede e M2Crypto para SSL , incluindo validação de certificado.
Com base no comentário do Glyphs, parece que M2Crypto faz melhor verificação de certificado por padrão do que o que você pode fazer com pyOpenSSL atualmente, porque M2Crypto verifica o campo subjectAltName também.
Também fiz um blog sobre como obter os certificados com os quais o Mozilla Firefox vem em Python e utilizáveis com soluções SSL em Python.
fonte
O Jython FAZ a verificação do certificado por padrão, portanto, usando módulos de biblioteca padrão, por exemplo, httplib.HTTPSConnection, etc, o jython irá verificar os certificados e dar exceções para falhas, ou seja, identidades incompatíveis, certificados expirados, etc.
Na verdade, você tem que fazer algum trabalho extra para fazer o jython se comportar como cpython, ou seja, fazer com que o jython NÃO verifique certificados.
Eu escrevi uma postagem no blog sobre como desativar a verificação de certificado no jython, porque pode ser útil nas fases de teste, etc.
Instalando um provedor de segurança totalmente confiável em java e jython.
http://jython.xhaus.com/installing-an-all-trusting-security-provider-on-java-and-jython/
fonte
O código a seguir permite que você se beneficie de todas as verificações de validação de SSL (por exemplo, validade de data, cadeia de certificados CA ...) EXCETO uma etapa de verificação conectável, por exemplo, para verificar o nome do host ou realizar outras etapas adicionais de verificação de certificado.
from httplib import HTTPSConnection import ssl def create_custom_HTTPSConnection(host): def verify_cert(cert, host): # Write your code here # You can certainly base yourself on ssl.match_hostname # Raise ssl.CertificateError if verification fails print 'Host:', host print 'Peer cert:', cert class CustomHTTPSConnection(HTTPSConnection, object): def connect(self): super(CustomHTTPSConnection, self).connect() cert = self.sock.getpeercert() verify_cert(cert, host) context = ssl.create_default_context() context.check_hostname = False return CustomHTTPSConnection(host=host, context=context) if __name__ == '__main__': # try expired.badssl.com or self-signed.badssl.com ! conn = create_custom_HTTPSConnection('badssl.com') conn.request('GET', '/') conn.getresponse().read()
fonte
pyOpenSSL é uma interface para a biblioteca OpenSSL. Deve fornecer tudo que você precisa.
fonte
Eu estava tendo o mesmo problema, mas queria minimizar as dependências de terceiros (porque esse script único deveria ser executado por muitos usuários). Minha solução foi encerrar uma
curl
chamada e verificar se o código de saída era0
. Funcionou como um encanto.fonte