문제

HTTPS를 통한 회사 인트라넷의 많은 사이트에 연결하는 스크립트를 작성하고 SSL 인증서가 유효한지 확인해야합니다. 그들이 만료되지 않았고, 올바른 주소 등을 발급 받는다. 우리는이 사이트에 대한 우리 자신의 내부 법인 인증 기관을 사용하므로 CA의 공개 키가있어 인증서를 확인합니다.

기본적으로 Python은 HTTPS를 사용할 때 SSL 인증서를 수락하고 사용하므로 인증서가 유효하지 않더라도 urllib2 및 Twisted와 같은 Python 라이브러리는 인증서를 행복하게 사용합니다.

HTTPS를 통해 사이트에 연결하고 이런 식으로 인증서를 확인할 수있는 좋은 라이브러리가 있습니까?

파이썬에서 인증서를 어떻게 확인합니까?

도움이 되었습니까?

해결책

릴리스 버전 2.7.9/3.4.3 on, Python에서 기본적으로 인증서 유효성 검사를 시도합니다.

이것은 PEP 467에서 제안되었으며, 이는 읽을 가치가 있습니다. https://www.python.org/dev/peps/pep-0476/

변경 사항은 모든 관련 stdlib 모듈 (urllib/urllib2, http, httplib)에 영향을 미칩니다.

관련 문서 :

https://docs.python.org/2/library/httplib.html#httplib.httpsconnection

이 클래스는 이제 기본적으로 필요한 모든 인증서 및 호스트 이름 확인을 수행합니다. 이전, 비 검사 SSL._Create_Unverified_Context ()로 되돌려 컨텍스트 매개 변수로 전달할 수 있습니다.

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

버전 3.4.3에서 변경 :이 클래스는 이제 기본적으로 필요한 모든 인증서 및 호스트 이름 확인을 수행합니다. 이전, 비 검사 SSL._Create_Unverified_Context ()로 되돌려 컨텍스트 매개 변수로 전달할 수 있습니다.

새로운 내장 검증은 시스템이 제공됩니다 인증서 데이터베이스. 그것에 반대하여 요청 패키지는 자체 인증서 번들을 선적합니다. 두 접근법의 장단점은 신뢰 데이터베이스 PEP 476 섹션.

다른 팁

Python 패키지 색인에 분포를 추가하여 match_hostname() 파이썬 3.2의 기능 ssl 이전 버전의 Python에서 사용할 수있는 패키지.

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

다음과 같이 설치할 수 있습니다.

pip install backports.ssl_match_hostname

또는 프로젝트에 나열된 종속성으로 만들 수 있습니다. setup.py. 어느 쪽이든, 그것은 다음과 같이 사용할 수 있습니다.

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

Twisted를 사용하여 인증서를 확인할 수 있습니다. 메인 API는입니다 인증서, 이것은로 제공 될 수 있습니다 contextFactory 다음과 같은 다양한 기능에 대한 인수 LESTENSSL 그리고 starttls.

불행히도, 파이썬이나 트위스트는 실제로 HTTPS 검증을 수행하는 데 필요한 CA 인증서 또는 HTTPS 검증 로직이 포함되어 있습니다. 때문에 pyopenssl의 제한, 당신은 아직 완전히 올바르게 할 수는 없지만 거의 모든 인증서에 주제 공통 이름이 포함되어 있기 때문에 충분히 가까워 질 수 있습니다.

다음은 와일드 카드 및 주제 연장을 무시하고 대부분의 우분투 배포판에서 'CA-Certificates'패키지에 존재하는 인증서 증명서를 사용하는 검증 된 트위스트 HTTPS 클라이언트의 순진한 샘플 구현입니다. 좋아하는 유효하고 유효하지 않은 인증 사이트로 사용해보십시오 :).

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 이것을 아름답게합니다.

아래는 짧은 예입니다. 그것은 던질 것입니다 pycurl.error 비린내 인 경우 오류 코드와 사람이 읽을 수있는 메시지가있는 튜플을 얻는 경우.

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

결과를 저장하는 위치 등과 같은 더 많은 옵션을 구성하려고하지만 필수 비 필수로 예제를 혼란스럽게 할 필요는 없습니다.

예외가 제기 될 수있는 예 :

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

내가 유용한 일부 링크는 setopt 및 getinfo의 libcurl-doc입니다.

다음은 인증서 유효성 검사를 보여주는 예제 스크립트입니다.

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

또는 단순히 사용하여 인생을 더 쉽게 만들 수 있습니다 요청 도서관:

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

사용법에 대해 몇 마디 더.

M2Crypto ~할 수 있다 검증을 수행하십시오. 당신은 또한 사용할 수 있습니다 트위스트가있는 M2Crypto 당신이 좋아한다면. 챈들러 데스크탑 클라이언트 네트워킹에는 트위스트와 SSL 용 M2Crypto를 사용합니다, 인증서 유효성 검사를 포함하여.

M2Crypto는 M2Crypto가 SubjectAltName 필드도 확인하기 때문에 M2Crypto가 현재 PyopenSSL에서 수행 할 수있는 것보다 기본적으로 더 나은 인증서 확인을하는 것처럼 보입니다.

나는 또한 방법에 대한 블로그를 작성했습니다 인증서를 받으십시오 Mozilla Firefox는 Python으로 배송되며 Python SSL 솔루션으로 사용할 수 있습니다.

Jython은 기본적으로 인증서 확인을 수행하므로 표준 라이브러리 모듈을 사용하여 httplib.httpsconnection 등을 사용하면 Jython이 인증서를 확인하고 실패, 즉 불일치 신원, 만료 된 CERT 등에 대한 예외를 제외합니다.

사실, 당신은 Jython이 Cpython처럼 행동하게하기 위해 추가 작업을 수행해야합니다. 즉 Jython이 CERT를 확인하지 않도록합니다.

Jython에서 인증서 확인을 비활성화하는 방법에 대한 블로그 게시물을 작성했습니다. 단계 등을 테스트하는 데 유용 할 수 있습니다.

Java 및 Jython에 완전히 신뢰할 수있는 보안 제공 업체 설치.
http://jython.xhaus.com/installing-an-all-trusting-security-provider-on-java-and-jython/

다음 코드를 사용하면 호스트 이름을 확인하거나 기타 추가 인증서 확인 단계를 수행하는 플러그 가능 확인 단계를 제외한 모든 SSL 유효성 검사 검사 (예 : 날짜 유효성, CA 인증 체인 ...)의 혜택을받을 수 있습니다.

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 OpenSSL 라이브러리의 인터페이스입니다. 필요한 모든 것을 제공해야합니다.

나는 같은 문제가 있었지만 타사 종속성을 최소화하고 싶었습니다 (이 일회성 스크립트는 많은 사용자가 실행해야하기 때문에). 내 해결책은 a curl 전화를 걸어 출구 코드가 있는지 확인하십시오 0. 매력처럼 일했습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top