【问题标题】:Getting certificate chain with Python 3.3 SSL module使用 Python 3.3 SSL 模块获取证书链
【发布时间】:2013-10-09 07:40:03
【问题描述】:

我可以通过 SSL 套接字上的 getpeercert() 方法获取 Python 3.3 中 SSL 连接的标准证书信息。但是,它似乎没有像 OpenSSL 的“s_client”工具那样提供链。

有什么方法可以让我查看我的 IA 证书是否配置正确?

s_client 命令行:

openssl s_client -connect google.com:443

s_client 结果(仅前几行):

$ openssl s_client -connect google.com:443
CONNECTED(00000003)
depth=2 C = US, O = GeoTrust Inc., CN = GeoTrust Global CA
verify error:num=20:unable to get local issuer certificate
verify return:0
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=*.google.com
   i:/C=US/O=Google Inc/CN=Google Internet Authority G2
 1 s:/C=US/O=Google Inc/CN=Google Internet Authority G2
   i:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
 2 s:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
   i:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
---

Python 3.3 代码:

import socket

from ssl import SSLContext  # Modern SSL?
from ssl import HAS_SNI  # Has SNI?

from pprint import pprint

def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
                    ca_certs=None, server_hostname=None,
                    ssl_version=None):

    context = SSLContext(ssl_version)
    context.verify_mode = cert_reqs

    if ca_certs:
        try:
            context.load_verify_locations(ca_certs)
        # Py32 raises IOError
        # Py33 raises FileNotFoundError
        except Exception as e:  # Reraise as SSLError
            raise ssl.SSLError(e)

    if certfile:
        # FIXME: This block needs a test.
        context.load_cert_chain(certfile, keyfile)

    if HAS_SNI:  # Platform-specific: OpenSSL with enabled SNI
        return context.wrap_socket(sock, server_hostname=server_hostname)

    return context.wrap_socket(sock)

hostname = 'www.google.com'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((hostname, 443))

sslSocket = ssl_wrap_socket(s,
                            ssl_version=2, 
                            cert_reqs=2, 
                            ca_certs='/usr/local/lib/python3.3/dist-packages/requests/cacert.pem', 
                            server_hostname=hostname)

pprint(sslSocket.getpeercert())
s.close()

代码结果:

{'issuer': ((('countryName', 'US'),),
            (('organizationName', 'Google Inc'),),
            (('commonName', 'Google Internet Authority G2'),)),
 'notAfter': 'Sep 25 15:09:31 2014 GMT',
 'notBefore': 'Sep 25 15:09:31 2013 GMT',
 'serialNumber': '13A87ADB3E733D3B',
 'subject': ((('countryName', 'US'),),
             (('stateOrProvinceName', 'California'),),
             (('localityName', 'Mountain View'),),
             (('organizationName', 'Google Inc'),),
             (('commonName', 'www.google.com'),)),
 'subjectAltName': (('DNS', 'www.google.com'),),
 'version': 3}

【问题讨论】:

    标签: python openssl ssl-certificate python-3.3


    【解决方案1】:

    我不确定,但我认为 OpenSSL API 的那部分在 Python 的 ssl 模块中不可用。

    看来函数SSL_get_peer_cert_chain是用来访问OpenSSL中的证书链的。例如,请参阅打印您包含的输出的section of openssl s_client。另一方面,用 source of Python's ssl-module 查找 SSL_get_peer_cert_chain 不会产生匹配项。

    M2Crypto 和 pyOpenSSL 似乎都包含 get_peer_cert_chain 函数,如果您愿意查看其他(和非标准库)库。不过,我不能亲自为它们担保,因为我没有经常使用它们。

    【讨论】:

    • 我打算添加一个功能请求,但最近已经有一张为此打开的票。看我的回答。
    【解决方案2】:

    感谢 Aleksi 的贡献答案,我发现了一个已经提出这个问题的错误/功能请求:http://bugs.python.org/issue18233。尽管更改尚未最终确定,但他们确实有一个补丁可以提供:

    这是我从某个被遗忘的来源中窃取并重新组装的测试代码:

    import socket
    
    from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23
    from ssl import SSLContext  # Modern SSL?
    from ssl import HAS_SNI  # Has SNI?
    
    from pprint import pprint
    
    def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
                        ca_certs=None, server_hostname=None,
                        ssl_version=None):
        context = SSLContext(ssl_version)
        context.verify_mode = cert_reqs
    
        if ca_certs:
            try:
                context.load_verify_locations(ca_certs)
            # Py32 raises IOError
            # Py33 raises FileNotFoundError
            except Exception as e:  # Reraise as SSLError
                raise SSLError(e)
    
        if certfile:
            # FIXME: This block needs a test.
            context.load_cert_chain(certfile, keyfile)
    
        if HAS_SNI:  # Platform-specific: OpenSSL with enabled SNI
            return (context, context.wrap_socket(sock, server_hostname=server_hostname))
    
        return (context, context.wrap_socket(sock))
    
    hostname = 'www.google.com'
    print("Hostname: %s" % (hostname))
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((hostname, 443))
    
    (context, ssl_socket) = ssl_wrap_socket(s,
                                           ssl_version=2, 
                                           cert_reqs=2, 
                                           ca_certs='/usr/local/lib/python3.3/dist-packages/requests/cacert.pem', 
                                           server_hostname=hostname)
    
    pprint(ssl_socket.getpeercertchain())
    
    s.close()
    

    输出:

    Hostname: www.google.com
    ({'issuer': ((('countryName', 'US'),),
                 (('organizationName', 'Google Inc'),),
                 (('commonName', 'Google Internet Authority G2'),)),
      'notAfter': 'Sep 11 11:04:38 2014 GMT',
      'notBefore': 'Sep 11 11:04:38 2013 GMT',
      'serialNumber': '50C71E48BCC50676',
      'subject': ((('countryName', 'US'),),
                  (('stateOrProvinceName', 'California'),),
                  (('localityName', 'Mountain View'),),
                  (('organizationName', 'Google Inc'),),
                  (('commonName', 'www.google.com'),)),
      'subjectAltName': (('DNS', 'www.google.com'),),
      'version': 3},
     {'issuer': ((('countryName', 'US'),),
                 (('organizationName', 'GeoTrust Inc.'),),
                 (('commonName', 'GeoTrust Global CA'),)),
      'notAfter': 'Apr  4 15:15:55 2015 GMT',
      'notBefore': 'Apr  5 15:15:55 2013 GMT',
      'serialNumber': '023A69',
      'subject': ((('countryName', 'US'),),
                  (('organizationName', 'Google Inc'),),
                  (('commonName', 'Google Internet Authority G2'),)),
      'version': 3},
     {'issuer': ((('countryName', 'US'),),
                 (('organizationName', 'Equifax'),),
                 (('organizationalUnitName',
                   'Equifax Secure Certificate Authority'),)),
      'notAfter': 'Aug 21 04:00:00 2018 GMT',
      'notBefore': 'May 21 04:00:00 2002 GMT',
      'serialNumber': '12BBE6',
      'subject': ((('countryName', 'US'),),
                  (('organizationName', 'GeoTrust Inc.'),),
                  (('commonName', 'GeoTrust Global CA'),)),
      'version': 3},
     {'issuer': ((('countryName', 'US'),),
                 (('organizationName', 'Equifax'),),
                 (('organizationalUnitName',
                   'Equifax Secure Certificate Authority'),)),
      'notAfter': 'Aug 22 16:41:51 2018 GMT',
      'notBefore': 'Aug 22 16:41:51 1998 GMT',
      'serialNumber': '35DEF4CF',
      'subject': ((('countryName', 'US'),),
                  (('organizationName', 'Equifax'),),
                  (('organizationalUnitName',
                    'Equifax Secure Certificate Authority'),)),
      'version': 3})
    

    【讨论】:

      【解决方案3】:

      上面的答案不是开箱即用的。

      经过许多选择后,我发现这是最简单的方法,需要最少的 3rd 方库。

      pip install pyopenssl 证书

      import socket
      from OpenSSL import SSL
      import certifi
      
      hostname = 'www.google.com'
      port = 443
      
      
      context = SSL.Context(method=SSL.TLSv1_METHOD)
      context.load_verify_locations(cafile=certifi.where())
      
      conn = SSL.Connection(context, socket=socket.socket(socket.AF_INET, socket.SOCK_STREAM))
      conn.settimeout(5)
      conn.connect((hostname, port))
      conn.setblocking(1)
      conn.do_handshake()
      conn.set_tlsext_host_name(hostname.encode())
      for (idx, cert) in enumerate(conn.get_peer_cert_chain()):
          print(f'{idx} subject: {cert.get_subject()}')
          print(f'  issuer: {cert.get_issuer()})')
          print(f'  fingerprint: {cert.digest("sha1")}')
      
      conn.close()
      

      这是原始想法的链接 https://gist.github.com/brandond/f3d28734a40c49833176207b17a44786

      这是一个将我带到这里的参考How to get response SSL certificate from requests in python?

      【讨论】:

        【解决方案4】:

        这是oglops 的后续回答,因为我的服务器不支持标准方法:

        import socket
        import sys
        
        from OpenSSL import SSL
        import certifi
        
        hostname = "www.google.com"
        port = 443
        
        methods = [
            (SSL.SSLv2_METHOD,"SSL.SSLv2_METHOD"),
            (SSL.SSLv3_METHOD,"SSL.SSLv3_METHOD"),
            (SSL.SSLv23_METHOD,"SSL.SSLv23_METHOD"),
            (SSL.TLSv1_METHOD,"SSL.TLSv1_METHOD"),
            (SSL.TLSv1_1_METHOD,"SSL.TLSv1_1_METHOD"),
            (SSL.TLSv1_2_METHOD,"SSL.TLSv1_2_METHOD"),
        ]
        
        for method,method_name in methods:
            try:
                print(f"\n-- Method {method_name}")
                context = SSL.Context(method=method)
                context.load_verify_locations(cafile=certifi.where())
        
                conn = SSL.Connection(
                    context, socket=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                )
                conn.settimeout(5)
                conn.connect((hostname, port))
                conn.setblocking(1)
                conn.do_handshake()
                conn.set_tlsext_host_name(hostname.encode())
                for (idx, cert) in enumerate(conn.get_peer_cert_chain()):
                    print(f"{idx} subject: {cert.get_subject()}")
                    print(f"  issuer: {cert.get_issuer()})")
                    print(f'  fingerprint: {cert.digest("sha1")}')
        
                conn.close()
            except:
                print(f"<><> Method {method_name} failed due to {sys.exc_info()[0]}")
        

        【讨论】:

          猜你喜欢
          • 2021-12-18
          • 1970-01-01
          • 2019-07-29
          • 1970-01-01
          • 2015-09-25
          • 1970-01-01
          • 1970-01-01
          • 2010-11-08
          • 1970-01-01
          相关资源
          最近更新 更多