本文介绍了无法使用OpenSSL的ssl.SSLContext()在Python客户端中接收对等证书的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是Windows用户.我使用Python 3.6.5,并且导入了此版本的OpenSSL OpenSSL 1.0.2k.

I am a Windows user. I use Python 3.6.5 and I import this version of OpenSSL OpenSSL 1.0.2k.

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

I need to write a script for a python TLS client that I can customize in terms of the supported TLS versions and ciphersuites and other configurations. The client should be able to make connections with self-signed certificates. Therefore, I believe I should use: ssl.SSLContext() to create my context and not ssl.create_default_context().

但是,使用以下脚本,我将永远无法获得对等方的证书.请用代码提供明确的答案,否则我将尝试许多解决方案,并且毫无希望地看了以前的帖子.

However, with the following script, I can never get the peer's certificate. Please, provide clear answers with code as otherwise I tried many solutions and looked at previous posts with no hope.

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_default_context()时,则可以正确接收该证书.但是,我需要能够接收自签名证书(即未验证的证书),这就是为什么我必须使用ssl.SSLContext()的原因.

The problem is that I do not receive the peer's certificate when I use ssl.SSLContext() but when I use ssl.create_default_context() it is received correctly. However, I need to be able to receive self-signed certificates (i.e. unverified certificates) that's why I have to use ssl.SSLContext().

感谢您发布解决方案.但是,即使没有经过验证(自签名),我也需要解析该证书.我信任此证书,并且需要其信息.我查看了几篇文章,包括这一篇.我做了这些步骤:

Thanks for the solution posted. But I need to parse the certificate even if it is not verified (self-signed). I trust this certificate and I need its info. I looked at several posts including this one. I did these steps:

  1. 我获取了服务器证书的.pem内容.
  2. 我导航至:C:\Python36\Lib\site-packages\certifi
  3. 我打开了放置在目录中的cacert.pem(第2步)
  4. 我添加了服务器的cert .pem内容,其内容以-----BEGIN CERTIFICATE-----开头,以-----END CERTIFICATE-----
  5. 结尾
  1. I took the .pem content of my server's certificate.
  2. I navigated to: C:\Python36\Lib\site-packages\certifi
  3. I opened cacert.pem which is placed in the directory (step 2)
  4. I added my server's cert .pem content which starts with: -----BEGIN CERTIFICATE----- and ends with -----END CERTIFICATE-----

我收到此错误:

ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:833)

推荐答案

经过多次尝试,有的失败,有的部分成功,我找到了一种可行的方法(不过,没有使用自签名证书对其进行测试) .另外,我清除了先前尝试中的所有内容.

After a number of attempts, some failed, some partial successful, I found a way that should work (didn't test it with self signed certificates, though). Also, I wiped out everything from the previous attempts.

有2个必要步骤:

  1. 使用 [Python 3.Docs ]:(ssl. get_server_certificate ( addr,ssl_version = PROTOCOL_TLS,ca_certs = None ),将其作为 PEM 编码返回字符串(例如:我们的-精美印刷):

  1. Get the server certificate using [Python 3.Docs]: (ssl.get_server_certificate(addr, ssl_version=PROTOCOL_TLS, ca_certs=None), which returns it as a PEM encoded string (e.g.: ours - pretty printed):

'-----BEGIN CERTIFICATE-----'
'MIIIPjCCByagAwIBAgIICG/ofYt2G48wDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE`
'BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl'

...

'L2KuOvWZ40sTVCJdWPUMtT9VP7VHfLNTFft/IhR+bUPkr33xjOa0Idq6cL89oufn'
'-----END CERTIFICATE-----'

  • 使用( !!! undocumented !!! )对证书进行解码ssl._ssl._test_decode_cert(出现在 Python 3 / Python 2 )

  • Decode the certificate using (!!!undocumented!!!) ssl._ssl._test_decode_cert (present in Python 3 / Python 2)

    • #1. 中的证书保存在临时文件中(显然,在 #2. 之前)
    • 完成后删除该文件

    我想强调 [Python 3 [文档]:SSLSocket.getpeercert( binary_form = False ) ,其中包含很多信息(我上次错过了).
    我还通过查看SSLSocket.getpeercert实现("$ {PYTHON_SRC_DIR}/Modules/_ssl.c" )来了解ssl._ssl._test_decode_cert.

    I would like to emphasize [Python 3.Docs]: SSLSocket.getpeercert(binary_form=False), which contains lots of info (that I missed the last time(s)).
    Also, I found out about ssl._ssl._test_decode_cert, by looking at SSLSocket.getpeercert implementation ("${PYTHON_SRC_DIR}/Modules/_ssl.c").

    code00.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:])
    

    注释:

    • _get_tmp_cert_file_name :生成用于存储证书的临时文件名(与脚本位于同一目录中)
    • _decode_cert :将证书保存在文件中,然后解码文件并返回生成的 dict
    • get_srv_cert_0 :获取证书表格服务器,然后对其进行解码
    • get_srv_cert_1 :与 get_srv_cert_0 相同,但手动"
      • 它的优点是控制 SSL 上下文的创建/操作(我认为这是问题的要点)
      • _get_tmp_cert_file_name: generates the temporary file name (located in the same dir as the script) that will store the certificate
      • _decode_cert: saves the certificate in the file, then decodes the file and returns the resulting dict
      • get_srv_cert_0: gets the certificate form server, then decodes it
      • get_srv_cert_1: same thing that get_srv_cert_0 does, but "manually"
        • Its advantage is controlling the SSL context creation / manipulation (which I think was the main point of the question)
        • 使用上述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]:如何我使用python解码SSL证书? (@CristiFati的回答)仅用于解码部分.

        这篇关于无法使用OpenSSL的ssl.SSLContext()在Python客户端中接收对等证书的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

  • 08-24 15:11
    查看更多