代码之家  ›  专栏  ›  技术社区  ›  None

无法在使用OpenSSL的ssl的Python客户端中接收对等证书。SSLContext()

  •  1
  • None  · 技术社区  · 6 年前

    我是Windows用户。我使用 Python 3.6.5 我导入了这个版本的OpenSSL OpenSSL 1.0.2k

    我需要为python TLS客户端编写一个脚本,可以根据支持的TLS版本、密码套件和其他配置进行自定义。客户端应该能够使用自签名证书进行连接。因此,我认为我应该使用: ssl.SSLContext() 创建上下文,而不是 ssl.create_default_context()

    然而,使用下面的脚本,我永远无法获得对等方的证书。请用代码提供清晰的答案,否则我尝试了许多解决方案,并毫无希望地查看了以前的帖子。

    context = ssl.SSLContext() # ssl.create_default_context() 
    #context.verify_mode = ssl.CERT_NONE
    #context.check_hostname = True
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    domain="google.com"
    ssl_sock = context.wrap_socket(s, server_hostname=domain)
    ssl_sock.connect((domain, 443))
    
    print("====== peer's certificate ======")
    try:
        cert = ssl_sock.getpeercert()
        print ("issued to:", dict(itertools.chain(*cert["subject"]))["commonName"])
        print ("issued by:", dict(itertools.chain(*cert["issuer"]))["commonName"])
        print("issuance date:", cert["notBefore"])
        print("expairy date: ", cert["notAfter"])
        if (cert == None):
            print("no certificate")
    
    except Exception as e:
        print("Error:",e)
    ssl_sock.close()
    

    问题是我在使用时没有收到对等方的证书 ssl。SSLContext() 但当我使用 ssl。create\u default\u context() 接收正确。但是,我需要能够接收自签名证书(即未验证的证书),这就是为什么我必须使用 ssl。SSLContext()

    感谢发布的解决方案。但我需要解析证书,即使它没有经过验证(自签名)。我信任此证书,我需要它的信息。我查看了几个帖子,包括 this one .我做了以下步骤:

    1. 我接受了 .pem 我的服务器证书的内容。
    2. 我导航到: C:\Python36\Lib\site-packages\certifi
    3. 我打开了 cacert.pem 放置在目录中(步骤2)
    4. 我添加了服务器的证书。pem内容以以下内容开头: -----BEGIN CERTIFICATE----- 并以结束 -----END CERTIFICATE-----

    我收到以下错误:

    ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:833)
    
    3 回复  |  直到 6 年前
        1
  •  1
  •   CristiFati    5 年前

    经过多次尝试,有些失败,有些部分成功,我找到了一种应该可以工作的方法(但没有使用自签名证书进行测试)。此外,我还清除了之前尝试的所有内容。

    有两个必要步骤:

    1. 使用获取服务器证书 [Python 3.Docs]: (ssl. get_server_certificate ( addr, ssl_version=PROTOCOL_TLS, ca_certs=None ) ,它以 PEM公司 编码字符串(例如:我们的- 印刷精美 ):

      '-----BEGIN CERTIFICATE-----'
      'MIIIPjCCByagAwIBAgIICG/ofYt2G48wDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE`
      'BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl'
      
      ...
      
      'L2KuOvWZ40sTVCJdWPUMtT9VP7VHfLNTFft/IhR+bUPkr33xjOa0Idq6cL89oufn'
      '-----END CERTIFICATE-----'
      
    2. 使用解码证书( !!!未记录!!! ) ssl._ssl._test_decode_cert (存在于 python 3. / python 2. )

    3. 由于以下事实: ssl_ssl_测试\u解码\u证书 只能从文件中读取证书,需要另外两个步骤:
      • 从保存证书 #1。 在临时文件中(之前 #2。 ,显然)
      • 完成后删除该文件

    我想 强调 [Python 3.Docs]: SSLSocket.getpeercert( binary_form=False ) ,其中包含大量信息(我上次错过了这些信息)。
    还有,我发现 ssl_ssl_测试\u解码\u证书 ,通过查看 SSLSocket.getpeercert 实施( “${PYTHON\u SRC\u DIR}/Modules/\u ssl.c” )。

    代码00。py公司 :

    #!/usr/bin/env python3
    
    import sys
    import os
    import socket
    import ssl
    import itertools
    
    
    def _get_tmp_cert_file_name(host, port):
        return os.path.join(os.path.dirname(os.path.abspath(__file__)), "_".join(("cert", host, str(port), str(os.getpid()), ".crt")))
    
    
    def _decode_cert(cert_pem, tmp_cert_file_name):
        #print(tmp_cert_file_name)
        with open(tmp_cert_file_name, "w") as fout:
            fout.write(cert_pem)
        try:
            return ssl._ssl._test_decode_cert(tmp_cert_file_name)
        except Exception as e:
            print("Error decoding certificate:", e)
            return dict()
        finally:
            os.unlink(tmp_cert_file_name)
    
    
    def get_srv_cert_0(host, port=443):
        try:
            cert_pem = ssl.get_server_certificate((host, port))
        except Exception as e:
            print("Error getting certificate:", e)
            return dict()
        tmp_cert_file_name = _get_tmp_cert_file_name(host, port)
        return _decode_cert(cert_pem, tmp_cert_file_name)
    
    
    def get_srv_cert_1(host, port=443):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        context = ssl.SSLContext()
        ssl_sock = context.wrap_socket(sock, server_hostname=host)
        try:
            ssl_sock.connect((host, port))
        except Exception as e:
            print("Error connecting:\n", e)
            return dict()
        try:
            cert_der = ssl_sock.getpeercert(True)  # NOTE THE ARGUMENT!!!
        except Exception as e:
            print("Error getting cert:\n", e)
            return dict()
        tmp_cert_file_name = _get_tmp_cert_file_name(host, port)
        return _decode_cert(ssl.DER_cert_to_PEM_cert(cert_der), tmp_cert_file_name)
    
    
    def main(argv):
        domain = "google.com"
        if argv:
            print("Using custom method")
            get_srv_cert_func = get_srv_cert_1
        else:
            print("Using regular method")
            get_srv_cert_func = get_srv_cert_0
    
        cert = get_srv_cert_func(domain)
        print("====== peer's certificate ======")
        try:
            print("Issued To:", dict(itertools.chain(*cert["subject"]))["commonName"])
            print("Issued By:", dict(itertools.chain(*cert["issuer"]))["commonName"])
            print("Valid From:", cert["notBefore"])
            print("Valid To:", cert["notAfter"])
            if (cert == None):
                print("no certificate")
        except Exception as e:
            print("Error getting certificate:", e)
    
    
    if __name__ == "__main__":
        print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
        main(sys.argv[1:])
    

    注释 :

    • _获取\u tmp\u cert\u file\u名称 :生成将存储证书的临时文件名(与脚本位于同一目录中)
    • _解码\u证书 :将证书保存在文件中,然后解码文件并返回结果 字典
    • 获取\u srv\u证书\u 0 :获取证书表单服务器,然后对其进行解码
    • 获取\u srv\u证书\u 1 :与 获取\u srv\u证书\u 0 是,但“手动”
      • 其优势在于控制 SSL 上下文创建/操作(我认为是 主要观点 问题的答案)
    • 主要的 :
      • 使用上述2个方法之一获取服务器证书(基于是否传递给脚本的参数)
      • 打印证书数据(代码中有一些小的更正)

    输出 :

    (py35x64_test) e:\Work\Dev\StackOverflow\q050055935>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code00.py
    Python 3.5.4 (v3.5.4:3f56838, Aug  8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32
    
    Using regular method
    ====== peer's certificate ======
    Issued To: *.google.com
    Issued By: Google Internet Authority G2
    Valid From: Apr 10 18:58:05 2018 GMT
    Valid To: Jul  3 18:33:00 2018 GMT
    
    (py35x64_test) e:\Work\Dev\StackOverflow\q050055935>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code00.py 1
    Python 3.5.4 (v3.5.4:3f56838, Aug  8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32
    
    Using custom method
    ====== peer's certificate ======
    Issued To: *.google.com
    Issued By: Google Internet Authority G2
    Valid From: Apr 10 18:55:13 2018 GMT
    Valid To: Jul  3 18:33:00 2018 GMT
    

    检查 [SO]: How can I decode a SSL certificate using python? (@CristiFati's answer) 仅用于解码部分。

        2
  •  0
  •   SpellsOfTruth    4 年前

    下面是一个9行代码片段,它从任何url获取证书数据。

    import ssl
    import socket
    
    def getcertmeta(url="",port=443):
        hostname = socket.gethostbyaddr(url)[0]
        context = ssl.create_default_context()
        with socket.create_connection((hostname, port)) as sock:
            with context.wrap_socket(sock, server_hostname=hostname) as ssock:
                return ssock.context.get_ca_certs()
    

    锡箔纸注释:这张证书有点可疑。它们是各种问题的根源,但科技公司却被它们所困扰。它们没有提供任何安全好处,每个证书颁发机构都遭到了黑客攻击(LetsEncrypt iirc除外)。这是无可辩驳的,可以在idk 5分钟内在线查找和找到。还有其他一些事情正在发生,但它们太复杂了,不必要复杂,很难弄清楚是什么。Imo他们与intel ME一起被用于将服务器数据后门到中心位置(如wileaks v7揭露的黑客行为),我手机上的证书包括3-4个政府,日本政府或中国政府直截了当地说,它有一批intel代理公司,如“星光科技”,这在Imo中都非常可疑。

    EDIT2:上面的代码段是错误的,它只获取证书颁发机构证书,而不是实际的url,下面是一个13行代码段,用于获取实际url的证书。

    import socket,ssl
    from contextlib import contextmanager
    @contextmanager
    def fetch_certificate(url="", port=443, timeout=0.5):
        cxt = ssl.create_default_context()
        sslctxsock = cxt.wrap_socket(socket.socket(), server_hostname=hostname)
        sslctxsock.settimeout(timeout)
        sslctxsock.connect((url,port))
        cert = sslctxsock.getpeercert()
        try:
            yield cert
        finally:
            sslctxsock.close()
    
    

    使用方法如下:

    with fetch_certificate(url=url) as cert:
       print(cert)
    
        3
  •  0
  •   nijave    3 年前

    看起来Python不会在以下情况下解析/存储客户端的证书 ssl.CERT_NONE 设置,这是的默认值 SSLContext

    import socket
    import ssl
    
    dest = ("www.google.com", 443)
    
    sock = socket.socket(socket.AF_INET)
    ctx = ssl.SSLContext()
    
    # You will either need to add the self-signed certs to the OS cert store (varies by OS)
    # See https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_verify_locations for loading non-defaults
    ctx.set_default_verify_paths()
    
    ctx.verify_mode = ssl.CERT_REQUIRED
    ssock = ctx.wrap_socket(sock, server_hostname=dest[0])
    result = ssock.connect(dest)
    print(ssock.getpeercert())
    

    看见 https://github.com/python/cpython/blob/main/Modules/_ssl.c#L1823 按位执行 & 因此,当验证模式为CERT\u NONE时( 0 )然后跳过代码块。证书解析/填充工作需要CERT\u OPTIONAL=1或CERT\u REQUIRED=2(否则它只设置一个空dict)

    对于自签名证书,您应该将其导入操作系统证书颁发机构存储或使用以下方法 SSLContext.load_verify_locations 加载自签名证书(因为它是自签名的,所以可以将服务器/叶证书加载为CA证书)

    如果您选择OS存储路径,则可能只需使用默认上下文即可。我认为在Windows上有一个每个用户的CA存储(虽然不是积极的)。我认为Linux上不存在这样的东西,所以您需要在系统范围内添加它。不确定macOS或其他操作系统