python - 如何使用 Python Requests 库检查 OCSP 客户端证书吊销?

标签 python python-requests client-certificates ocsp ejbca

如何使用 Python 请求库向 EJBCA OSCP 响应程序发出证书吊销状态的简单请求?

示例:

# 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)."
       )

最佳答案

基本上它包括以下步骤:

  • 检索主机名的相应证书
  • 如果证书中包含相应的条目,您可以通过 AuthorityInformationAccessOID.CA_ISSUERS 查询扩展,如果成功,它将为您提供指向颁发者证书的链接
  • 使用此链接检索颁发者证书
  • 类似地,您通过 AuthorityInformationAccessOID.OCSP 获得相应的 OCSP 服务器
  • 使用有关当前证书、issuer_cert 和 ocsp 服务器的信息,您可以提供给 OCSPRequestBuilder 以创建 OCSP 请求
  • 使用requests.get获取OCSP响应
  • 从 OCSP 响应中检索 certificate_status

要检索主机名和端口的证书,您可以使用这个很好的答案:https://stackoverflow.com/a/49132495 . Python 中的 OCSP 处理记录在此处:https://cryptography.io/en/latest/x509/ocsp.html .

代码

如果将以上几点转换成一个独立的示例,它看起来像这样:

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:良好证书

像下面这样的带有良好证书的测试调用

status = get_cert_status_for_host('software7.com', 443)
print('software7.com:', status, '\n')

结果如下:

   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:

status = get_cert_status_for_host('revoked.badssl.com', 443)
print('revoked.badssl.com:', status, '\n')

这给出了输出:

   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 检索

证书关系的典型场景如下所示:

Certificate Relationship

服务器在 TLS 握手期间提供服务器证书,通常还有一个或多个中间证书。 “通常”一词是有意使用的:某些服务器配置为不提供中间证书。然后浏览器使用 AIA 抓取来构建认证链。

证书颁发机构信息访问扩展中最多可以存在两个条目:用于下载颁发者证书的条目和指向 OCSP 服务器的链接。

这些条目也可能丢失,但是检查 100 个最流行服务器的证书的简短测试脚本表明,这些条目通常包含在公共(public)证书颁发机构颁发的证书中。

CA Issuers 条目也可能丢失,但是当有关 OCSP 服务器的信息可用时,可以对其进行测试,例如使用自签名证书的 OpenSSL:

Missing CA Issuers Entry

在这种情况下,您必须从 TLS 握手中的链中确定颁发者证书,它是紧跟在链中服务器证书之后的证书,另请参见上图。

只是为了完整起见:还有另一种情况有时会发生,尤其是与自签名证书结合使用时:如果没有使用中间证书,则必须使用相应的根证书(例如在本地信任库中可用)作为颁发者证书。

关于python - 如何使用 Python Requests 库检查 OCSP 客户端证书吊销?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64436317/

相关文章:

python - 从列中提取部分值

azure - 在 Azure 上,带有子状态 72 的 HTTP 403 意味着什么以及如何修复它?

python - python中event.wait()函数的返回值是什么

python - 为什么 numpy 数组有 96 字节的开销?

python-3.x - Eventbrite 搜索 API 的 406 错误/响应

python - 如何从 BeautifulSoup ( Python ) 中的表中获取第一个子表行

从终端使用 SOCKS5 时 Python 的请求为 "Missing dependencies for SOCKS support"

ssl - Firefox 访问 Tomcat 站点时出现 SSL_ERROR_RX_UNKNOWN_ALERT 错误

java - WildFly 9 启用客户端证书认证

python - 将一列的数据帧与具有一系列列的另一个数据帧匹配并提取列标题 - Python