Python으로 SSL 인증서 유효성 검사
HTTPS를 통해 회사 인트라넷의 여러 사이트에 연결하고 SSL 인증서가 유효한지 확인하는 스크립트를 작성해야합니다. 만료되지 않았는지, 올바른 주소로 발급되었는지 등. 이러한 사이트에 대해 자체 내부 기업 인증 기관을 사용하므로 인증서를 확인할 CA의 공개 키가 있습니다.
Python은 기본적으로 HTTPS를 사용할 때 SSL 인증서를 수락하고 사용하므로 인증서가 유효하지 않더라도 urllib2 및 Twisted와 같은 Python 라이브러리는 인증서를 기꺼이 사용합니다.
HTTPS를 통해 사이트에 연결하고 이런 방식으로 인증서를 확인할 수있는 좋은 라이브러리가 있습니까?
Python에서 인증서를 어떻게 확인합니까?
릴리스 버전 2.7.9 / 3.4.3부터 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 의 신뢰 데이터베이스 섹션 에서 설명합니다 .
match_hostname()
Python 3.2 ssl
패키지 의 함수를 이전 버전의 Python에서 사용할 수 있도록 Python Package Index에 배포를 추가했습니다 .
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는 CertificateOptions 이며 listenSSL 및 startTLScontextFactory
와 같은 다양한 함수에 대한 인수로 제공 될 수 있습니다 .
불행히도 Python이나 Twisted는 실제로 HTTPS 유효성 검사를 수행하는 데 필요한 CA 인증서 더미와 HTTPS 유효성 검사 논리를 제공하지 않습니다. PyOpenSSL의 제한 으로 인해 아직 완전히 올바르게 수행 할 수는 없지만 거의 모든 인증서에 주제 commonName이 포함되어 있기 때문에 충분히 가까워 질 수 있습니다.
다음은 와일드 카드 및 subjectAltName 확장을 무시하고 대부분의 Ubuntu 배포판에서 'ca-certificates'패키지에있는 인증 기관 인증서를 사용하는 검증 Twisted 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-docs입니다.
- http://curl.haxx.se/libcurl/c/curl_easy_setopt.html
- http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html
다음은 인증서 유효성 검사를 보여주는 예제 스크립트입니다.
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 는 유효성 검사를 할 수 있습니다 . 원한다면 Twisted와 함께 M2Crypto를 사용할 수도 있습니다 . Chandler 데스크톱 클라이언트 는 인증서 유효성 검사를 포함하여 네트워킹에 Twisted를 사용하고 SSL에 M2Crypto를 사용합니다 .
Glyphs 주석에 따르면 M2Crypto는 subjectAltName 필드도 확인하기 때문에 M2Crypto가 현재 pyOpenSSL로 할 수있는 것보다 기본적으로 더 나은 인증서 확인을 수행하는 것처럼 보입니다.
I've also blogged on how to get the certificates Mozilla Firefox ships with in Python and usable with Python SSL solutions.
Jython DOES carry out certificate verification by default, so using standard library modules, e.g. httplib.HTTPSConnection, etc, with jython will verify certificates and give exceptions for failures, i.e. mismatched identities, expired certs, etc.
In fact, you have to do some extra work to get jython to behave like cpython, i.e. to get jython to NOT verify certs.
I have written a blog post on how to disable certificate checking on jython, because it can be useful in testing phases, etc.
Installing an all-trusting security provider on java and jython.
http://jython.xhaus.com/installing-an-all-trusting-security-provider-on-java-and-jython/
The following code allows you to benefit from all SSL validation checks (e.g. date validity, CA certificate chain ...) EXCEPT a pluggable verification step e.g. to verify the hostname or do other additional certificate verification steps.
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 is an interface to the OpenSSL library. It should provide everything you need.
I was having the same problem but wanted to minimize 3rd party dependencies (because this one-off script was to be executed by many users). My solution was to wrap a curl
call and make sure that the exit code was 0
. Worked like a charm.
참고URL : https://stackoverflow.com/questions/1087227/validate-ssl-certificates-with-python
'development' 카테고리의 다른 글
현재 파비콘을 지원하는 모든 브라우저에서 파비콘을 표시하는 가장 좋은 방법은 무엇입니까? (0) | 2020.10.05 |
---|---|
SqlBulkCopy에 권장되는 배치 크기는 얼마입니까? (0) | 2020.10.05 |
Android에서 TouchDelegate를 사용하여보기의 클릭 대상 크기를 늘리는 방법에 대한 예가 있습니까? (0) | 2020.10.05 |
네이티브 글로벌 스타일 반응 (0) | 2020.10.04 |
Swift에서 문자열을 날짜로 변환 (0) | 2020.10.04 |