在 Python 中获取或构建 PEM 证书链
Get or build PEM certificate chain in Python
是否可以使用带 Python 的 ssl 以 PEM 格式获取整个证书链?我可以得到具体的:
import ssl
addr = '192.0.2.1'
cert_str = ssl.get_server_certificate((addr, 443))
这给我类似的东西:
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
但我想要:
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
我很确定这是可能的,因为我可以从我的网络浏览器下载它。有什么想法吗?
(我已经检查过 Getting certificate chain with Python 3.3 SSL module 但我不确定这是我想要的...)
编辑: 我在 Patrick Mevzek 回答后尝试了什么:
from OpenSSL import SSL
import socket
dst = ('192.0.2.1', 443)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.set_alpn_protos([b'http/1.1'])
if sock.connect_ex(dst) == 0:
connection = SSL.Connection(ctx, sock)
cert_str = connection.get_peer_cert_chain()
但是 cert_str 是 None。我认为这是因为我在使用 OpenSSL 时遗漏了一些东西。
如果您使用 Python 中的 OpenSSL
库,您可以在连接对象上应用 get_peer_cert_chain
,它将为您提供由服务器,所以如果需要,所有中间证书的最终证书。
参见https://pyopenssl.org/en/stable/api/ssl.html#connection-objects:
get_peer_cert_chain()
Retrieve the other side’s certificate (if any)
Returns: A list of X509 instances giving the peer’s certificate chain, or None if it does not have one.
这是一个粗略的例子(没有任何错误处理):
from OpenSSL import SSL
import socket
dst = ('www.google.com', 443)
ctx = SSL.Context(SSL.SSLv23_METHOD)
s = socket.create_connection(dst)
s = SSL.Connection(ctx, s)
s.set_connect_state()
s.set_tlsext_host_name(dst[0])
s.sendall('HEAD / HTTP/1.0\n\n')
s.recv(16)
certs = s.get_peer_cert_chain()
for pos, cert in enumerate(certs):
print "Certificate #" + str(pos)
for component in cert.get_subject().get_components():
print "Subject %s: %s" % (component)
print "notBefore:" + cert.get_notBefore()
print "notAfter:" + cert.get_notAfter()
print "version:" + str(cert.get_version())
print "sigAlg:" + cert.get_signature_algorithm()
print "digest:" + cert.digest('sha256')
给出:
Certificate #0
Subject C: US
Subject ST: California
Subject L: Mountain View
Subject O: Google LLC
Subject CN: www.google.com
notBefore:20180612133452Z
notAfter:20180821121300Z
version:2
sigAlg:sha256WithRSAEncryption
digest:06:C5:12:EB:3C:B1:7F:AB:18:E0:D5:22:E4:25:12:A7:30:AA:27:16:0B:3A:99:CB:3D:11:CF:12:EF:95:2E:41
Certificate #1
Subject C: US
Subject O: Google Trust Services
Subject CN: Google Internet Authority G3
notBefore:20170615000042Z
notAfter:20211215000042Z
version:2
sigAlg:sha256WithRSAEncryption
digest:BE:0C:CD:54:D4:CE:CD:A1:BD:5E:5D:9E:CC:85:A0:4C:2C:1F:93:A5:22:0D:77:FD:E8:8F:E9:AD:08:1F:64:1B
您已了解证书的完整详细内容,请参阅 https://pyopenssl.org/en/stable/api/crypto.html#x509-objects 了解可用信息。
然后你有 to_cryptography()
可以通过以下方式获得它的 PEM 版本:cert.to_cryptography().public_bytes(serialization.Encoding.PEM)
但还要考虑到:
- 使用回调会更好,见
set_info_callback()
方法,这样在适当的时候诊断运行(甚至输入SSL.SSL_CB_HANDSHAKE_DONE
)
- 我观察到您需要交换一些流量 (send/recv) 才能调用
get_peer_cert_chain()
(如果您在我的示例中将其移动到 sendall()
之前,它 returns None
)
从你问题中的 link 来看,当你只使用 ssl
而不是 OpenSSL
时,你似乎与 getpeercertchain()
等效;然而,这似乎仍然被记录为一个错误,有一些可用的补丁,可能不会发布。
事实上 https://docs.python.org/3.8/library/ssl.html 的最新文档没有列出 getpeercertchain()
.
有同样的问题,但很难找到一个解决方案,该解决方案将 PEM 格式的证书链作为字符串提供给 requests.session()。此功能应该 return 完全符合您的要求。
from OpenSSL import SSL, crypto
import socket
def getPEMFile():
dst = ('www.google.com', 443)
ctx = SSL.Context(SSL.SSLv23_METHOD)
s = socket.create_connection(dst)
s = SSL.Connection(ctx, s)
s.set_connect_state()
s.set_tlsext_host_name(str.encode(dst[0]))
s.sendall(str.encode('HEAD / HTTP/1.0\n\n'))
peerCertChain = s.get_peer_cert_chain()
pemFile = ''
for cert in peerCertChain:
pemFile += crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode("utf-8")
return pemFile
是否可以使用带 Python 的 ssl 以 PEM 格式获取整个证书链?我可以得到具体的:
import ssl
addr = '192.0.2.1'
cert_str = ssl.get_server_certificate((addr, 443))
这给我类似的东西:
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
但我想要:
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
我很确定这是可能的,因为我可以从我的网络浏览器下载它。有什么想法吗?
(我已经检查过 Getting certificate chain with Python 3.3 SSL module 但我不确定这是我想要的...)
编辑: 我在 Patrick Mevzek 回答后尝试了什么:
from OpenSSL import SSL
import socket
dst = ('192.0.2.1', 443)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.set_alpn_protos([b'http/1.1'])
if sock.connect_ex(dst) == 0:
connection = SSL.Connection(ctx, sock)
cert_str = connection.get_peer_cert_chain()
但是 cert_str 是 None。我认为这是因为我在使用 OpenSSL 时遗漏了一些东西。
如果您使用 Python 中的 OpenSSL
库,您可以在连接对象上应用 get_peer_cert_chain
,它将为您提供由服务器,所以如果需要,所有中间证书的最终证书。
参见https://pyopenssl.org/en/stable/api/ssl.html#connection-objects:
get_peer_cert_chain()
Retrieve the other side’s certificate (if any)
Returns: A list of X509 instances giving the peer’s certificate chain, or None if it does not have one.
这是一个粗略的例子(没有任何错误处理):
from OpenSSL import SSL
import socket
dst = ('www.google.com', 443)
ctx = SSL.Context(SSL.SSLv23_METHOD)
s = socket.create_connection(dst)
s = SSL.Connection(ctx, s)
s.set_connect_state()
s.set_tlsext_host_name(dst[0])
s.sendall('HEAD / HTTP/1.0\n\n')
s.recv(16)
certs = s.get_peer_cert_chain()
for pos, cert in enumerate(certs):
print "Certificate #" + str(pos)
for component in cert.get_subject().get_components():
print "Subject %s: %s" % (component)
print "notBefore:" + cert.get_notBefore()
print "notAfter:" + cert.get_notAfter()
print "version:" + str(cert.get_version())
print "sigAlg:" + cert.get_signature_algorithm()
print "digest:" + cert.digest('sha256')
给出:
Certificate #0
Subject C: US
Subject ST: California
Subject L: Mountain View
Subject O: Google LLC
Subject CN: www.google.com
notBefore:20180612133452Z
notAfter:20180821121300Z
version:2
sigAlg:sha256WithRSAEncryption
digest:06:C5:12:EB:3C:B1:7F:AB:18:E0:D5:22:E4:25:12:A7:30:AA:27:16:0B:3A:99:CB:3D:11:CF:12:EF:95:2E:41
Certificate #1
Subject C: US
Subject O: Google Trust Services
Subject CN: Google Internet Authority G3
notBefore:20170615000042Z
notAfter:20211215000042Z
version:2
sigAlg:sha256WithRSAEncryption
digest:BE:0C:CD:54:D4:CE:CD:A1:BD:5E:5D:9E:CC:85:A0:4C:2C:1F:93:A5:22:0D:77:FD:E8:8F:E9:AD:08:1F:64:1B
您已了解证书的完整详细内容,请参阅 https://pyopenssl.org/en/stable/api/crypto.html#x509-objects 了解可用信息。
然后你有 to_cryptography()
可以通过以下方式获得它的 PEM 版本:cert.to_cryptography().public_bytes(serialization.Encoding.PEM)
但还要考虑到:
- 使用回调会更好,见
set_info_callback()
方法,这样在适当的时候诊断运行(甚至输入SSL.SSL_CB_HANDSHAKE_DONE
) - 我观察到您需要交换一些流量 (send/recv) 才能调用
get_peer_cert_chain()
(如果您在我的示例中将其移动到sendall()
之前,它 returnsNone
)
从你问题中的 link 来看,当你只使用 ssl
而不是 OpenSSL
时,你似乎与 getpeercertchain()
等效;然而,这似乎仍然被记录为一个错误,有一些可用的补丁,可能不会发布。
事实上 https://docs.python.org/3.8/library/ssl.html 的最新文档没有列出 getpeercertchain()
.
有同样的问题,但很难找到一个解决方案,该解决方案将 PEM 格式的证书链作为字符串提供给 requests.session()。此功能应该 return 完全符合您的要求。
from OpenSSL import SSL, crypto
import socket
def getPEMFile():
dst = ('www.google.com', 443)
ctx = SSL.Context(SSL.SSLv23_METHOD)
s = socket.create_connection(dst)
s = SSL.Connection(ctx, s)
s.set_connect_state()
s.set_tlsext_host_name(str.encode(dst[0]))
s.sendall(str.encode('HEAD / HTTP/1.0\n\n'))
peerCertChain = s.get_peer_cert_chain()
pemFile = ''
for cert in peerCertChain:
pemFile += crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode("utf-8")
return pemFile