如何使用 pkijs 在 TypeScript 中解码和验证 OCSP 响应以进行证书验证

How to Decode and Verify OCSP Response in TypeScript with pkijs for Certificate Validation

提问人:Namachi 提问时间:9/27/2023 最后编辑:Namachi 更新时间:9/27/2023 访问量:156

问:

目标

我们的目标是确定证书是否有效或是否已被吊销。 为了实现这一点,我们使用 pkijs 包发出 OCSP 请求以简化该过程,发送与有效证书相关的详细信息,例如“hashAlgorithm”、“issuerNameHash”、“issuerKeyHash”等(请参阅下面的代码)。

问题

然而,一旦我们成功获得OCSP响应,其中一部分似乎被编码,这使得解释变得具有挑战性。 唯一可见的元素是证书类型,但我们无法从此响应中提取其他可读信息。

我们尝试了什么

我们调用我们的函数,该函数将使用以下参数(临时硬编码,用于测试目的)来确定证书是有效还是已吊销:

await checkCertificateWithOcsp({
    hashAlgorithm: 'SHA256',
    issuerKeyHash: '7870177724f6234dccf87a8a43c84551533f831257519f90b12bb8eecae0',
    issuerNameHash: 'cbe609c06ec9bd944a5d8cf94aee2979d4396fe00f68c6d215e233766514a1',
    responderURL: 'https://7kravoouwj.execute-api.eu-west-1.amazonaws.com/test/OCSP-Responder',
    serialNumber: '2',
});
import * as asn1js from 'asn1js';

import { AlgorithmIdentifier, CertID, Extension, OCSPRequest, OCSPResponse, Request } from 'pkijs';
import Axios from 'axios';

public static async checkCertificateWithOcsp(ocspRequest: OCSPRequestData) {
    // Convert hexadecimal strings into bytes (Uint8Array).
    const issuerNameHashBytes = new Uint8Array(ocspRequest.issuerNameHash.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
    const issuerKeyHashBytes = new Uint8Array(ocspRequest.issuerKeyHash.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
    const serialNumberBytes = new Uint8Array(ocspRequest.serialNumber.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));

    // 1. Create OCSP Request with PKI.js
    const request = new OCSPRequest();
    request.tbsRequest.requestList[0] = new Request();
    request.tbsRequest.requestExtensions = [
      new Extension({
        extnID: "1.3.6.1.5.5.7.48.1.2",
        critical: false,
        extnValue: new asn1js.OctetString().toBER(),
      })
    ];
    request.tbsRequest.requestList[0].reqCert = new CertID({
      hashAlgorithm: new AlgorithmIdentifier({ algorithmId: "1.3.14.3.2.26" }),
      issuerNameHash: new asn1js.OctetString({ valueHex: issuerNameHashBytes }),
      issuerKeyHash: new asn1js.OctetString({ valueHex: issuerKeyHashBytes }),
      serialNumber: new asn1js.Integer({ valueHex: serialNumberBytes }),
    });
    // 2. Encode OCSP request
    const encodedOcspReq = request.toSchema(true).toBER(false);

    // 3. OCSP API Call with Axios
    const response: any = await Axios.post<ArrayBuffer>(ocspRequest.responderURL, encodedOcspReq,
      {
        headers: {
          'Content-Type': 'application/ocsp-request',
        },
      },
    );

    // 4. Convert response to ASN1
    const ocspResponseBuffer = Buffer.from(ocspResponse.data);
    const rawOcspResponseBuffer = new Uint8Array(ocspResponseBuffer.buffer);
    const asn1 = asn1js.fromBER(rawOcspResponseBuffer.buffer);

    // 5. Error occurred in PKI.JS OCSPResponse Class
    const decodedOcspResponse = new OCSPResponse({ schema: asn1.result });
}

上述代码中的步骤

  1. 首先,我们在PKI.js和ASN1.js包的帮助下填充请求。
  2. 在将 OCSP 请求发送到 OCSP API 调用之前对其进行编码。
  3. 我们成功接收到带有编码数据的响应
  4. 将编码的 OCSP 响应数据转换为 ASN1
  5. 尝试解码和访问信息以获取证书状态

错误描述

在步骤 5 中,PKI 端发生错误,并显示以下错误消息:对象的架构未根据 OCSPResponse 的输入数据进行验证

由于证书的安全性根本不是我们的专业领域,尽管我们进行了研究,但我们无法确定错误可能在哪里以及如何纠正它。

提前感谢您的帮助。

编辑#1

以下是 OCSP 服务发送的值:reponse.data

0�{
��t0�p  +0�a0�]0�ȡK0I10U    V2GRootCA10U
Smartlab10
    �&���,dV2G10    UDE20230927144339Z0h0f0O0   +�� �nɽ�J]��J�)y�9o�h���3ve�xpw$�#M��z�C�EQS?�WQ���+�����20230927144339Z0*�H�=���2���S�`���̥��0���oN6N8�'��2ř��=O�l,�>>jA���<~�`f}�%�2���S�`���̥��0���oN6N8�'��2ř��=O�l,�>>jA���<~�`f

我们不知道它是什么类型的编码......

节点 .js 打字稿 安全 证书 OCSP

评论


答:

-1赞 Aliif 9/27/2023 #1

您遇到的错误消息“对象的架构未针对 OCSPResponse 的输入数据进行验证”,这表明 OCSP 响应的预期架构与收到的实际数据不匹配。这通常意味着接收到的数据不符合预期的 ASN.1 架构。

下面是一些步骤,可帮助您排查问题并可能解决此问题:

  1. 验证响应内容类型:确保从服务器接收的 OCSP 响应的内容类型与预期的内容类型匹配。在代码中,在发出 OCSP 请求时将“Content-Type”标头设置为“application/ocsp-request”。确保服务器使用正确的“Content-Type”标头进行响应,对于 OCSP 响应,该标头应为“application/ocsp-response”。

    例:

    headers: {
      'Content-Type': 'application/ocsp-response',
    },
    
  2. 检查OCSP响应编码:OCSP 响应采用 DER 编码。确保您收到的响应是 DER 编码的,并且未损坏。如果它不是 DER 编码的,则可能需要以不同的方式处理解码。

    例:

    const ocspResponseBuffer = Buffer.from(response.data, 'binary');
    
  3. 确保响应数据完整:验证您是否从服务器收到完整的 OCSP 响应。它应包括所有必要的组件,例如响应状态、responseBytes 和响应数据。不完整的响应可能会导致架构验证问题。

  4. 检查可能的网络错误:确保在传输过程中没有导致数据损坏的网络相关问题。您可以添加错误处理来检查网络错误,例如超时或连接问题。

    例:

    try {
      const response = await Axios.post<ArrayBuffer>(ocspRequest.responderURL, encodedOcspReq, {
        headers: {
          'Content-Type': 'application/ocsp-request',
        },
      });
      // Handle the response here
    } catch (error) {
      console.error('Network error:', error);
      // Handle the error gracefully
    }
    
  5. 验证ASN.1架构:确保用于解码OCSP响应的ASN.1架构与从服务器接收的响应的结构匹配。您可以参考 PKI.js 文档来确认预期的结构。

  6. 日志记录和调试:在代码的各个阶段添加日志记录语句,以检查数据并确定架构和数据之间可能发生不匹配的位置。这有助于查明问题所在。

通过系统地检查这些点并调试代码,您应该能够识别错误的来源并进行必要的更正,以成功解码 OCSP 响应并确定证书状态。

评论

0赞 Namachi 9/28/2023
非常感谢您的回复。在调试来自 OCSP 调用的响应时,标头中的 确实是 .但是,我无法识别 中使用的编码类型。(请参阅主要帖子中的编辑 #1 部分)。要继续验证OCSP响应结构,第一步是成功解码它,但我们一直无法这样做。使用 Try/Catch 时没有网络问题。Content-Typeapplication/ocsp-responseresponse.data
0赞 Namachi 12/5/2023 #2

我需要在 Axios 方法中指定 responseType 是 arrayBuffer,如下所示:

const response: any = await Axios.post<ArrayBuffer>(ocspRequest.responderURL, encodedOcspReq,
  {
    headers: {
      'Content-Type': 'application/ocsp-request',
    },
    responseType: 'arraybuffer',
  },
);