8

I can get the standard certificate information for an SSL connection in Python 3.3 via the getpeercert() method on the SSL socket. However, it doesn't seem to provide the chain like OpenSSL's "s_client" tool does.

Is there some way I can get this so that I can see if my IA certificate was configured properly?

s_client command-line:

openssl s_client -connect google.com:443

s_client result (just the first few lines):

$ 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 code:

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()

Code result:

{'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}
4

4 回答 4

9

感谢 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})
于 2013-10-11T01:35:13.553 回答
4

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

在经历了许多选择之后,我发现这是最简单的方法,它需要最少的 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

这是一个将我带到这里的参考如何从 python 中的请求中获取响应 SSL 证书?

于 2019-10-05T07:49:26.190 回答
2

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

似乎该函数SSL_get_peer_cert_chain用于访问 OpenSSL 中的证书链。例如,请参阅打印您包含的输出的部分。openssl s_client另一方面,对 Python 的 ssl-module 的来源进行greppingSSL_get_peer_cert_chain不会产生匹配项。

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

于 2013-10-04T07:30:49.103 回答
0

这是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-04-22T11:50:17.620 回答