[英]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.