سؤال

أحتاج إلى كتابة برنامج نصي يتصل بمجموعة من المواقع على إنترانت الشركة عبر HTTPS والتحقق من أن شهادات SSL الخاصة بهم صالحة؛ أنهم ليسوا من انتهاء صلاحيتهم، ويتم إصدارها للعنوان الصحيح، وما إلى ذلك. نحن نستخدم هيئة شهادة الشركات الداخلية الخاصة بنا لهذه المواقع، لذلك لدينا المفتاح العام للمرجع المصدق للتحقق من الشهادات ضد.

يقبل Python افتراضيا فقط ويستخدم شهادات SSL عند استخدام HTTPS، لذلك حتى لو كانت الشهادة غير صالحة، فإن مكتبات Python مثل Urllib2 وملتوية ستستخدم بسعادة الشهادة.

هل هناك مكتبة جيدة في مكان ما سيسمح لي بالاتصال بموقع عبر HTTPS والتحقق من شهادته بهذه الطريقة؟

كيف يمكنني التحقق من شهادة في بيثون؟

هل كانت مفيدة؟

المحلول

من الإصدار الإصدار 2.7.9 / 3.4.3 ON، بيثون بشكل افتراضي محاولات إجراء التحقق من صحة الشهادة.

تم اقتراح ذلك في PEP 467، الذي يستحق القراءة: https://www.python.org/dev/peps/peps/pep-0476/

تؤثر التغييرات على جميع وحدات Stdlib ذات الصلة (Urllib / Urllib2، HTTP، HTTPLIB).

الوثائق ذات الصلة:

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

تقوم هذه الفئة الآن بإجراء جميع الشهادات اللازمة وشيكات اسم المضيف افتراضيا. للعودة إلى SSL._CREATE_UNVERIFD_CONDEXT_CONDEXT ()، يمكن تمريرها إلى المعلمة السياق.

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

تم تغييرها في الإصدار 3.4.3: تقوم هذه الفئة الآن بإجراء جميع الشهادات اللازمة وشيكات اسم المضيف افتراضيا. للعودة إلى SSL._CREATE_UNVERIFD_CONDEXT_CONDEXT ()، يمكن تمريرها إلى المعلمة السياق.

لاحظ أن التحقق المدمج الجديد يعتمد على النظام - المقدمة قاعدة بيانات الشهادة. عارض ذلك، طلبات حزمة السفن حزمة الشهادة الخاصة بها. تتم مناقشة إيجابيات وسلبيات كلا النهجين في قاعدة بيانات الثقة قسم بيب 476.

نصائح أخرى

لقد أضفت توزيعا إلى مؤشر حزمة بيثون مما يجعل match_hostname() وظيفة من بيثون 3.2 ssl الحزمة المتاحة في الإصدارات السابقة من بيثون.

http://pypi.python.org/pypi/backportss.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:
    ...

يمكنك استخدام الملتوية للتحقق من الشهادات. واجهة برمجة التطبيقات الرئيسية هي عميل المعلمات, ، والتي يمكن توفيرها كما contextFactory حجة لموظفي مختلفة مثل listenssl. و starttls..

لسوء الحظ، لا يأتي أي ثياب ولا تلتوية مع كومة من شهادات كاليفورنيا المطلوبة في الواقع من صحة HTTPS، ولا منطق التحقق من صحة HTTPS. بسبب قيود في Pyopenssl, ، لا يمكنك القيام بذلك بشكل صحيح تماما حتى الآن، ولكن بفضل حقيقة أن جميع الشهادات تقريبا تشمل موضوعا كاميرا، يمكنك الاقتراب بما فيه الكفاية.

فيما يلي تطبيق عينة ساذج لعميل HTTPS الملتوي الذي يتجاهل أحرف البدل وملحقات Natoralamname، ويستخدم شهادات سلطة الشهادة الموجودة في حزمة "شهادات الشهادات" في معظم توزيعات Ubuntu. جربه مع مواقع الشهادة الصالحة والصلاحية المفضلة لديك :).

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 إذا كان هناك شيء مريب، حيث يمكنك الحصول على Tuple مع رمز الخطأ ورسالة قابلة للقراءة.

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

بعض الروابط التي عثرت عليها مفيدة هي مستندات libcurl-setopt و getinfo.

إليك برنامج نصي مثال يظهر التحقق من صحة الشهادة:

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 مع الملتوية إذا تحب. عميل Chandler Desktop يستخدم الملتوية للشبكات و m2crypto ل SSL, ، بما في ذلك التحقق من صحة الشهادة.

بناء على تعليق Glyphs، يبدو أن m2crypto هل تحقق شهادة أفضل افتراضيا من ما يمكنك القيام به مع pyopenssl حاليا، لأن m2crypto يتحقق حقل matayalamname أيضا.

لقد بلوق أيضا حول كيفية الحصول على الشهادات سفن موزيلا فايرفوكس مع في ثيثون وقابلة للاستخدام مع حلول بيثون SSL.

يقوم Jython بإجراء التحقق من الشهادة بشكل افتراضي، لذا باستخدام وحدات المكتبة القياسية، مثل HTTPLIB.HTTPSConnection، إلخ، سيحقق Jython شهادات وإعطاء استثناءات للإفشل، أي الهويات غير المعطلة، شارات منتهية الصلاحية، إلخ.

في الواقع، عليك القيام ببعض الأعمال الإضافية للحصول على Jython أن تتصرف مثل CpeThon، أي للحصول على Jython لعدم التحقق من certs.

لقد كتبت منشور مدونات حول كيفية تعطيل التحقق من الشهادة على Jython، لأنه قد يكون مفيدا في مراحل الاختبار، إلخ.

تثبيت مزود الأمان متعدد الثقة على Java و Jython.
http://jython.xhaus.com/installing-All-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. يجب أن توفر كل ما تحتاجه.

كنت أواجه نفس المشكلة ولكن أرغب في تقليل تبعيات الطرف الثالث (لأن هذا البرنامج النصي لمرة واحدة كان سيتم تنفيذه من قبل العديد من المستخدمين). كان الحل الخاص بي هو التفاف curl اتصل وتأكد من أن رمز الخروج كان 0. وبعد عملت مثل سحر.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top