繁体   English   中英

使用 Python 3.3 SSL 模块获取证书链

[英]Getting certificate chain with Python 3.3 SSL module

我可以通过 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}

感谢 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})

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

在经历了很多选择之后,我发现这是最简单的方法,它需要最少的 3rd 方库。

pip 安装 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?

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

似乎函数SSL_get_peer_cert_chain用于访问 OpenSSL 中的证书链。 例如,请参阅openssl s_client打印您包含的输出的部分 另一方面,为SSL_get_peer_cert_chain Python 的 ssl 模块源代码SSL_get_peer_cert_chain产生匹配项。

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

这是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]}")

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM