使用OpenSSL的ssl.SSLContext()

时间:2018-04-27 05:42:33

标签: python sockets ssl network-programming x509certificate

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

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

  1. 我获取了服务器证书的.pem内容。
  2. 我导航到:C:\Python36\Lib\site-packages\certifi
  3. 我打开cacert.pem,它放在目录(步骤2)
  4. 我添加了我的服务器证书.pem内容,该内容以:-----BEGIN CERTIFICATE-----开头,以-----END CERTIFICATE-----
  5. 结尾

    我收到此错误:

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

3 个答案:

答案 0 :(得分:1)

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

有两个必要的步骤:

  1. 使用[Python 3]: (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._test_decode_cert只能从文件中读取证书,因此需要执行以下两个步骤:
    • 将证书从 #1。 保存在临时文件中(显然在 #2。 之前)
    • 完成后删除该文件
  4. 我想强调[Python 3]: SSLSocket.getpeercert(binary_form=False) ,其中包含大量信息(我错过了最后一次)。
    另外,通过查看ssl._ssl._test_decode_cert实施(" $ {PYTHON_SRC_DIR} /Modules/_ssl.c" ,我发现了SSLSocket.getpeercert #!/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:]) )。

    code.py

    (py35x64_test) e:\Work\Dev\StackOverflow\q050055935>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code.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" code.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
    

    备注

    • _get_tmp_cert_file_name :生成将存储证书的临时文件名(与脚本位于同一目录中)
    • _decode_cert :将证书保存在文件中,然后对文件进行解码并返回生成的 dict
    • get_srv_cert_0 :获取证书表单服务器,然后对其进行解码
    • get_srv_cert_1 :与 get_srv_cert_0 相同,但"手动"
      • 它的优点是控制 SSL 上下文创建/操作(我认为是问题的要点
      • 使用上述两种方法之一获取服务器证书(基于正在/不传递给脚本的参数)
      • 打印证书数据(您的代码有一些小的更正)

    <强>输出

      function initMap() {
        var map = new google.maps.Map(document.getElementById('map'), {
          center: {lat: 44.5452, lng: -78.5389},
          zoom: 9
        });
    
        var bounds = {
          north: 44.599,
          south: 44.490,
          east: -78.443,
          west: -78.649
        };
    
        // Define a rectangle and set its editable property to true.
        var rectangle = new google.maps.Rectangle({
          bounds: bounds,
          editable: true
        });
        rectangle.setMap(map);
      }
    </script>
    

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

答案 1 :(得分:0)

这是一个9行代码段,可从任何网址中获取证书数据。

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分钟内在线查找和发现,还有其他事情发生,但是它们是如此的复杂和不必要地复杂,以至于很难弄清楚是什么。我将它们与intel ME一起用于将服务器数据后门到中央位置(例如wileaks v7揭示的黑客攻击),我手机上的证书包括3-4个政府,只是直截了当地说日本政府或中国政府,并且一堆像“星光技术”之类的英特尔代理公司,都是非常可疑的imo。

EDIT2:上面的代码段是错误的,它仅获取证书颁发机构certs而不是实际的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)

答案 2 :(得分:0)

当设置 ssl.CERT_NONESSLContext 的默认设置)时,Python 似乎不会解析/存储客户端的证书

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_NONE (0) 时,将跳过代码块。证书解析/填充需要 CERT_OPTIONAL=1 或 CERT_REQUIRED=2(否则它只会设置一个空字典)

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

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