如何使用 Python 请求库检查 OCSP 客户端证书吊销? [英] How to check OCSP client certificate revocation using Python Requests library?
问题描述
如何使用 Python 请求库向 EJBCA OSCP 响应程序发出简单的证书吊销状态请求?
How do I make a simple request for certificate revocation status to an EJBCA OSCP Responder using the Python requests library?
示例:
# Determine if certificate has been revoked
ocsp_url = req_cert.extensions[2].value[0].access_location.value
ocsp_headers = {"whatGoes: here?"}
ocsp_body = {"What goes here?"}
ocsp_response = requests.get(ocsp_url, ocsp_headers, ocsp_body)
if (ocsp_response == 'revoked'):
return func.HttpResponse(
"Certificate is not valid (Revoked)."
)
推荐答案
基本上包括以下几个步骤:
Basically it involves the following steps:
- 检索主机名的相应证书
- 如果证书中包含相应的条目,您可以通过 AuthorityInformationAccessOID.CA_ISSUERS 查询扩展名,如果成功,它将为您提供指向颁发者证书的链接
- 使用此链接检索颁发者证书
- 同样,您可以通过 AuthorityInformationAccessOID.OCSP 获得相应的 OCSP 服务器
- 有了有关当前证书、issuer_cert 和 ocsp 服务器的这些信息,您就可以向 OCSPRequestBuilder 提供信息以创建 OCSP 请求
- 使用
requests.get
获取 OCSP 响应 - 从 OCSP 响应中检索
certificate_status
- retrieve the corresponding cert for a hostname
- if a corresponding entry is contained in the certificate, you can query the extensions via AuthorityInformationAccessOID.CA_ISSUERS, which will provide you with a link to the issuer certificate if successful
- retrieve the issuer cert with this link
- similarly you get via AuthorityInformationAccessOID.OCSP the corresponding OCSP server
- with this information about the current cert, the issuer_cert and the ocsp server you can feed OCSPRequestBuilder to create an OCSP request
- use
requests.get
to get the OCSP response - from the OCSP response retrieve the
certificate_status
要检索主机名和端口的证书,您可以使用这个很好的答案:https://stackoverflow.com/a/49132495.Python 中的 OCSP 处理记录在此处:https://cryptography.io/en/latest/x509/ocsp.html.
To retrieve a cert for a hostname and port, you can use this fine answer: https://stackoverflow.com/a/49132495. The OCSP handling in Python is documented here: https://cryptography.io/en/latest/x509/ocsp.html.
代码
如果你把上面的点转换成一个自包含的例子,它看起来是这样的:
If you convert the above points into a self-contained example, it looks something like this:
import base64
import ssl
import requests
from urllib.parse import urljoin
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.x509 import ocsp
from cryptography.x509.ocsp import OCSPResponseStatus
from cryptography.x509.oid import ExtensionOID, AuthorityInformationAccessOID
def get_cert_for_hostname(hostname, port):
conn = ssl.create_connection((hostname, port))
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
sock = context.wrap_socket(conn, server_hostname=hostname)
certDER = sock.getpeercert(True)
certPEM = ssl.DER_cert_to_PEM_cert(certDER)
return x509.load_pem_x509_certificate(certPEM.encode('ascii'), default_backend())
def get_issuer(cert):
aia = cert.extensions.get_extension_for_oid(ExtensionOID.AUTHORITY_INFORMATION_ACCESS).value
issuers = [ia for ia in aia if ia.access_method == AuthorityInformationAccessOID.CA_ISSUERS]
if not issuers:
raise Exception(f'no issuers entry in AIA')
return issuers[0].access_location.value
def get_ocsp_server(cert):
aia = cert.extensions.get_extension_for_oid(ExtensionOID.AUTHORITY_INFORMATION_ACCESS).value
ocsps = [ia for ia in aia if ia.access_method == AuthorityInformationAccessOID.OCSP]
if not ocsps:
raise Exception(f'no ocsp server entry in AIA')
return ocsps[0].access_location.value
def get_issuer_cert(ca_issuer):
issuer_response = requests.get(ca_issuer)
if issuer_response.ok:
issuerDER = issuer_response.content
issuerPEM = ssl.DER_cert_to_PEM_cert(issuerDER)
return x509.load_pem_x509_certificate(issuerPEM.encode('ascii'), default_backend())
raise Exception(f'fetching issuer cert failed with response status: {issuer_response.status_code}')
def get_oscp_request(ocsp_server, cert, issuer_cert):
builder = ocsp.OCSPRequestBuilder()
builder = builder.add_certificate(cert, issuer_cert, SHA256())
req = builder.build()
req_path = base64.b64encode(req.public_bytes(serialization.Encoding.DER))
return urljoin(ocsp_server + '/', req_path.decode('ascii'))
def get_ocsp_cert_status(ocsp_server, cert, issuer_cert):
ocsp_resp = requests.get(get_oscp_request(ocsp_server, cert, issuer_cert))
if ocsp_resp.ok:
ocsp_decoded = ocsp.load_der_ocsp_response(ocsp_resp.content)
if ocsp_decoded.response_status == OCSPResponseStatus.SUCCESSFUL:
return ocsp_decoded.certificate_status
else:
raise Exception(f'decoding ocsp response failed: {ocsp_decoded.response_status}')
raise Exception(f'fetching ocsp cert status failed with response status: {ocsp_resp.status_code}')
def get_cert_status_for_host(hostname, port):
print(' hostname:', hostname, "port:", port)
cert = get_cert_for_hostname(hostname, port)
ca_issuer = get_issuer(cert)
print(' issuer ->', ca_issuer)
issuer_cert = get_issuer_cert(ca_issuer)
ocsp_server = get_ocsp_server(cert)
print(' ocsp_server ->', ocsp_server)
return get_ocsp_cert_status(ocsp_server, cert, issuer_cert)
测试 1:良好的证书
具有良好证书的如下测试调用
A test call like the following with a good certificate
status = get_cert_status_for_host('software7.com', 443)
print('software7.com:', status, '
')
产生以下输出:
hostname: software7.com port: 443
issuer -> http://cacerts.digicert.com/EncryptionEverywhereDVTLSCA-G1.crt
ocsp_server -> http://ocsp.digicert.com
software7.com: OCSPCertStatus.GOOD
测试 2:吊销证书
当然,您还必须使用已撤销的证书进行反测试.这里revoked.badssl.com是首选:
Of course you also have to do a counter test with a revoked cert. Here revoked.badssl.com is the first choice:
status = get_cert_status_for_host('revoked.badssl.com', 443)
print('revoked.badssl.com:', status, '
')
这作为输出:
hostname: revoked.badssl.com port: 443
issuer -> http://cacerts.digicert.com/DigiCertSHA2SecureServerCA.crt
ocsp_server -> http://ocsp.digicert.com
revoked.badssl.com: OCSPCertStatus.REVOKED
AIA 检索颁发者证书
证书关系的典型场景如下所示:
A typical scenario for a certificate relationship looks as follows:
服务器在 TLS 握手期间提供服务器证书,通常还会提供一个或多个中间证书.通常"一词是有意使用的:某些服务器配置为不提供中间证书.然后浏览器使用 AIA 获取来构建认证链.
The server provides the server certificate and usually one or more intermediate certificates during the TLS handshake. The word 'usually' is used intentionally: some servers are configured not to deliver intermediate certificates. The browsers then use AIA fetching to build the certification chain.
证书颁发机构信息访问扩展中最多可以有两个条目:用于下载颁发者证书的条目和指向 OCSP 服务器的链接.
Up to two entries can be present in the Certificate Authority Information Access extension: The entry for downloading the issuer certificate and the link to the OCSP server.
这些条目也可能丢失,但检查 100 个最流行服务器的证书的简短测试脚本表明,这些条目通常包含在公共证书颁发机构颁发的证书中.
These entries may also be missing, but a short test script that checks the certs of the 100 most popular servers shows that these entries are usually included in certificates issued by public certification authorities.
CA 颁发者条目也可能丢失,但虽然有关 OCSP 服务器的信息可用,但可以对其进行测试,例如使用自签名证书的 OpenSSL:
The CA Issuers entry may also be missing, but while the information about an OCSP server is available, it can be tested e.g. with OpenSSL using a self-signed certificate:
在这种情况下,您必须从 TLS 握手中的链中确定颁发者证书,它是链中服务器证书之后直接出现的证书,另见上图.
In this case you would have to determine the issuer certificate from the chain in the TLS handshake, it is the certificate that comes directly after the server certificate in the chain, see also the figure above.
只是为了完整起见:还有一种情况有时会发生,尤其是与自签名证书结合使用时:如果不使用中间证书,则必须使用相应的根证书(例如在本地信任库中可用)作为颁发者证书.
Just for the sake of completeness: There is another case that can sometimes occur especially in conjunction with self-signed certificates: If no intermediate certificates are used, the corresponding root certificate (e.g. available in the local trust store) must be used as issuer certificate.
这篇关于如何使用 Python 请求库检查 OCSP 客户端证书吊销?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!