Pergunta

Eu preciso escrever um script que se conecta a um monte de sites na nossa intranet corporativa mais de HTTPS e verifica que os seus certificados SSL são válidos; que eles não são expirado, que são emitidos para o endereço correto, etc. Nós usamos nossa própria Autoridade de Certificação corporativa interna para esses sites, por isso temos a chave pública da CA para verificar os certificados contra.

Python por padrão apenas aceita e usa certificados SSL quando utiliza HTTPS, assim mesmo se um certificado é inválido, Python bibliotecas como urllib2 e torcida só vai alegremente utilizar o certificado.

Existe um lugar bom biblioteca que vai deixar-me conectar a um site através de HTTPS e verificar seu certificado desta forma?

Como posso verificar um certificado em Python?

Foi útil?

Solução

A partir lançamento da versão 2.7.9 / 3.4.3 em diante, Python por padrão tentativas para executar a validação do certificado.

Esta foi proposto em 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

Esta classe agora executa todas as necessárias certificado e nome de host cheques por padrão. Para reverter para a ssl._create_unverified_context anterior, não verificado, o comportamento () pode ser passado para o parâmetro de contexto.

https://docs.python.org/3/library/http .client.html # http.client.HTTPSConnection

alterado na versão 3.4.3: Esta classe agora executa todas as necessárias certificado e nome de host cheques por padrão. Para reverter para a ssl._create_unverified_context anterior, não verificado, o comportamento () pode ser passado para o parâmetro de contexto.

Note que o novo built-in verificação é baseada no banco de dados certificado fornecido pelo sistema. Contrapondo-se a isso, os navios pacote seu próprio pacote certificado. Prós e contras de ambas as abordagens são discutidas na banco de dados Confiança do PEP 476 .

Outras dicas

Eu adicionei uma distribuição para o Índice pacote Python que faz a função match_hostname() do pacote 3.2 ssl Python disponível nas versões anteriores do Python.

http://pypi.python.org/pypi/backports.ssl_match_hostname/

Você pode instalá-lo com:

pip install backports.ssl_match_hostname

Ou você pode torná-lo uma dependência listados na setup.py do seu projeto. De qualquer forma, ele pode ser usado como este:

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

Você pode usar torcida para verificar certificados. A principal API é CertificateOptions , que pode ser fornecida como o argumento contextFactory para várias funções tais como listenSSL e StartTLS .

Infelizmente, nem Python nem torcida vem com uma pilha de certificados de CA necessários para realmente fazer HTTPS validação, nem o HTTPS lógica de validação. Devido a uma limitação no pyOpenSSL , você não pode fazê-lo completamente corretamente ainda, mas graças ao fato de que quase todos os certificados incluem um commonName assunto, você pode chegar perto o suficiente.

Aqui está um exemplo de implementação ingênua de um cliente torcida HTTPS verificar que ignora wildcards e extensões subjectAltName, e usa os certificados de certificado de autoridade presente no pacote 'ca-certificates' na maioria das distribuições Ubuntu. Experimente-o com os seus locais de certificados válidos e inválidos favoritos:.)

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()

pycurl faz isso muito bem.

Abaixo está um pequeno exemplo. Ele vai jogar um pycurl.error se algo é suspeito, onde você começa uma tupla com código de erro e uma mensagem legível.

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 vai querer configurar mais opções, como o local para armazenar os resultados, etc. Mas não há necessidade de encher o exemplo com os não-essenciais.

Exemplo do que 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 eu encontrei útil são as libcurl-docs para setopt e getinfo.

Aqui está um script de exemplo que demonstra a validação de certificados:

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()

Ou simplesmente facilitar a sua vida usando a pedidos biblioteca:

import requests
requests.get('https://somesite.com', cert='/path/server.crt', verify=True)
mais

Algumas palavras sobre a sua utilização.

M2Crypto pode fazer o validação. Você também pode usar M2Crypto com torcida se você gosta. O cliente de desktop Chandler usa trançado para a rede e M2Crypto para SSL , incluindo a validação do certificado.

Com base no comentário Glyphs parece que M2Crypto faz melhor verificação do certificado por padrão do que o que você pode fazer com pyOpenSSL atualmente, porque M2Crypto campo cheques subjectAltName também.

Eu também tenho um blog sobre como obter os certificados navios Mozilla Firefox com em Python e utilizável com soluções Python SSL.

Jython faz transportar a verificação do certificado por padrão, portanto, usando módulos de biblioteca padrão, por exemplo, httplib.HTTPSConnection, etc, com jython irá verificar certificados e dar exceções para falhas, ou seja identidades incompatíveis, expirou certificados, etc.

Na verdade, você tem que fazer algum trabalho extra para obter jython a se comportar como CPython, ou seja, para obter jython para não verificar certificados.

Eu escrevi um post sobre como desativar certificado de verificação de jython, porque ele pode ser útil em fases de teste, etc.

A instalação de um provedor de segurança all-confiando em Java e Jython.
http: //jython.xhaus. com / instalação-um-todo-confiando-security-provedor-on-java-and-jython /

O código a seguir permite-lhe beneficiar de todas as verificações de validação SSL (por exemplo, data de validade, cadeia de certificados CA ...), exceto uma etapa de verificação pluggable por exemplo para verificar o nome do host ou fazer outras etapas de verificação certificado adicional.

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()

pyOpenSSL é uma interface para a biblioteca OpenSSL. Ele deve fornecer tudo que você precisa.

Eu estava tendo o mesmo problema, mas queria minimizar as dependências do 3o partido (porque este one-off de script seria executado por muitos usuários). Minha solução foi envolver uma chamada curl e certifique-se de que o código de saída foi 0. Trabalhou como um encanto.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top