

# 证书吊销
<a name="certificate-revocation"></a>

当由于泄露、策略更改或关系终止而需要吊销证书时，您需要一种机制来在 mTLS 握手期间拒绝这些证书。CloudFront 提供了两种原生的证书吊销方法，您可以将它们组合起来进行分层控制。
+ **OCSP（在线证书状态协议）**：CloudFront 实时查询证书颁发机构的 OCSP 响应者，以检查客户端证书是否已被吊销。在信任存储上启用 OCSP，CloudFront 将在 TLS 握手期间自动处理验证。OCSP 结果也显示在连接函数中，这使您能够通过编程访问吊销状态以进行自定义决策。
+ **CloudFront Functions 和 KeyValueStore**：您在 CloudFront KeyValueStore 中维护一份已吊销证书序列号的列表。连接函数在 TLS 握手期间查询 KeyValueStore，并允许或拒绝连接。这使您能够完全控制吊销数据、更新时间和自定义逻辑，例如宽限期或基于 IP 的异常。


**比较：OCSP 与具有 KeyValueStore 的 CloudFront Functions**  

|  | OCSP | CloudFront Functions \+ KeyValueStore | 
| --- | --- | --- | 
| 数据来源 | 证书颁发机构的 OCSP 响应者 | 您管理吊销列表 | 
| 更新机制 | 对 CA 进行实时查询 | 您向 KeyValueStore 推送更新 | 
| 自定义逻辑 | 可通过连接函数提供 | 内置在函数代码中 | 
| 外部依赖项 | 需要 CA OCSP 响应者可用性 | 无外部依赖项 | 
| 适用于 | 维护 OCSP 响应者的 CA；实时 CA 授权状态 | 自行管理的吊销；自定义策略；不支持 OCSP 的 CA | 

而是可以同时使用这两种方法。启用 OCSP 以进行 CA 授权吊销检查，然后使用连接函数在 OCSP 结果之上叠加其它逻辑，例如，支持在宽限期内从可信 IP 范围吊销的证书。

## OCSP（在线证书状态协议）
<a name="ocsp-revocation"></a>

OCSP 是一种实时协议，可直接向证书颁发机构（CA）检查证书的吊销状态。在证书签名过程中，CA 会在证书中嵌入 OCSP 响应者 URL。当客户在 mTLS 握手期间出示证书时，CloudFront 向嵌入式响应者 URL 发送 OCSP 请求，并根据响应采取行动：继续实施有效的证书，终止已吊销的证书。

CloudFront 根据各自的 OCSP 响应者 URL 验证整个证书链（叶证书和最多三个中间证书）。OCSP 验证不包括信任存储中的根证书。

### 启用 OCSP
<a name="enable-ocsp"></a>

在信任存储上启用 OCSP。启用后，CloudFront 自动对任何在其授权信息访问（AIA）扩展中包含 OCSP 响应者 URL 的客户端证书执行 OCSP 验证。启用 OCSP 后，整个客户端证书链必须有一个 OCSP URL。如果客户端证书链中的任何证书都不包含 OCSP URL，则 CloudFront 将无法建立连接。

CloudFront 在边缘缓存 OCSP 响应，以减少往返时间并防止 OCSP 响应者停机。OCSP 响应缓存达约 30 分钟，更新后的吊销状态可能需要长达 30 分钟才能反映出来。

### OCSP 导致连接函数
<a name="ocsp-results-connection-functions"></a>

在同一分配上配置连接函数时，CloudFront 在 OCSP 验证完成后调用该函数。连接对象包含叶证书和中间证书的 OCSP 状态：

```
{
  "clientCertificate": {
    "certificates": {
      "leaf": {
        "subject": "CN=client.example.com, O=Example Org",
        "issuer": "CN=Intermediate CA, O=Example Org",
        "serialNumber": "00:a7:30:9e:73:7b:3e:63:bd:b7:c0:7e:bf:d5:c9:86",
        "validity": {
          "notBefore": "2024-01-01T00:00:00Z",
          "notAfter": "2025-01-01T00:00:00Z"
        },
        "sha256Fingerprint": "AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90",
        "ocspEndpoint": "http://ocsp.example.org"
      },
      "intermediates": [
        {
          "subject": "CN=Intermediate CA, O=Example Org",
          "issuer": "CN=Root CA, O=Example Org",
          "serialNumber": "00:a7:30:9e:73:7b:3e:63:bd:b7:c0:7e:bf:d5:c9:86",
          "validity": {
            "notBefore": "2020-01-01T00:00:00Z",
            "notAfter": "2030-01-01T00:00:00Z"
          },
          "sha256Fingerprint": "12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF",
          "ocspEndpoint": "http://ocsp.example.org"
        }
      ]
    },
    "revocationStatus": {
      "chainValidity": "Valid",           // "Valid" | "Invalid" | "Unknown"
      "certificates": {
        "leaf": {
          "method": "OCSP",               // "OCSP"
          "status": "Good",               // "Good" | "Revoked" | "Unknown" | "Error"
          "serialNumber": "00:a7:30:9e:73:7b:3e:63:bd:b7:c0:7e:bf:d5:c9:86"
        },
        "intermediates": [
          {
            "method": "OCSP",             // "OCSP"
            "status": "Error",            // "Good" | "Revoked" | "Unknown" | "Error"
            "errorType": "InternalError", // "InternalError" | "OCSP response verification failed: {Type}"
            "serialNumber": "00:a7:30:9e:73:7b:3e:63:bd:b7:c0:7e:bf:d5:c9:86"
          }
        ]
      }
    }
  },
  "clientIp":"127.0.0.1",
  "endpoint":"d123.cloudfront.net",
  "distributionId":"E1NXS4MQZH501R",
  "connectionId":"xdzQ6lJUDUt8b7OuqOD8lmzOC9HcMaXPmhH5ZdzLCZpKxqzfCPpR4A=="
}
```

`chainValidity` 字段可以为 `Valid`、`Invalid` 或 `Unknown`。单个证书 `status` 值可以为 `Good`、`Revoked`、`Unknown` 或 `Error`。当状态为 `Error` 时，`errorType` 字段包含 `InternalError` 或 `OCSP response verification failed: {Type}`。

连接函数可以覆盖 OCSP 结果，例如，支持来自可信 IP 范围的已吊销证书，或者根据其它业务逻辑拒绝 OCSP 报告为“良好”的证书。

**注意**  
仅当状态为 `Error` 时，`errorType` 字段才会出现。

### 示例：可信 IP 异常的自定义 OCSP 处理
<a name="ocsp-example"></a>

```
function connectionHandler(connection) {
    var revocationStatus = connection.clientCertificate.revocationStatus;
    var trustedIP = (connection.clientIp === "[IP_ADDRESS]");

    if (revocationStatus.chainValidity === "Invalid") {
        if (trustedIP) {
            connection.allow();
        } else {
            connection.deny();
        }
    } else if (revocationStatus.certificates.leaf.status === "Error") {
        console.log(revocationStatus.certificates.leaf.errorType);
        connection.deny();
    } else {
        connection.allow();
    }
}
```

### OCSP 失败行为
<a name="ocsp-failure-behavior"></a>

当 CloudFront 无法确定证书的吊销状态时（由于 OCSP 响应者无法访问、返回错误或返回“未知”），CloudFront 默认情况下将拒绝连接。要实现软失败行为，请使用连接函数。如果启用 OCSP 且客户端证书中没有 OCSP URL，则不会执行连接函数。OCSP 结果可在连接对象中提供，当 OCSP 状态未确定时，您的函数可以允许连接：

```
async function connectionHandler(connection) {
    var revocationStatus = connection.clientCertificate.revocationStatus;

    if (revocationStatus.certificates.leaf.status === "Error" ||
        revocationStatus.certificates.leaf.status === "Unknown") {
        // OCSP responder unreachable — allow connection (soft-fail)
        connection.logCustomData(`OCSP_SOFT_FAIL:${revocationStatus.certificates.leaf.errorType}`);
        return connection.allow();
    }

    if (revocationStatus.chainValidity === "Invalid") {
        return connection.deny();
    }

    return connection.allow();
}
```

## 使用 CloudFront Functions 和 KeyValueStore 吊销证书
<a name="kvs-revocation"></a>

您可以将 CloudFront 连接函数与 KeyValueStore 结合使用来实施证书吊销检查，而无需任何外部依赖项。您在 KeyValueStore 中维护一个吊销的证书序列号列表，而连接函数在 TLS 握手期间根据此列表检查每个客户端证书。

证书吊销流程如下所述：

1. 将已吊销证书的序列号存储在 CloudFront KeyValueStore 中。

1. 当客户端提供证书时，系统将调用您的连接函数。

1. 该函数会根据 KeyValueStore 中的数据核对证书序列号。

1. 如果在存储中找到了序列号，则证书将被吊销。

1. 您的函数拒绝已吊销证书的连接。

此方法可在 CloudFront 的全球边缘网络中提供近乎实时的吊销检查。

要实施此方法，您需要：
+ 已配置查看器 mTLS 的分配
+ 包含已吊销证书序列号的 KeyValueStore
+ 一个连接函数，用于查询 KeyValueStore 以检查证书状态

当客户端连接时，CloudFront 会根据信任存储验证证书，然后运行您的连接函数。您的函数会根据 KeyValueStore 检查证书序列号，并允许或拒绝连接。

### 第 1 步：为已吊销证书创建 KeyValueStore
<a name="step1-create-kvs"></a>

以 JSON 格式准备已吊销证书的序列号：

```
{
  "data": [
    { "key": "ABC123DEF456", "value": "" },
    { "key": "789XYZ012GHI", "value": "" }
  ]
}
```

将此 JSON 文件上传到 S3 存储桶，然后创建 KeyValueStore：

```
aws s3 cp revoked-serials.json s3://your-bucket-name/revoked-serials.json

aws cloudfront create-key-value-store \
  --name revoked-serials-kvs \
  --import-source '{
    "SourceType": "S3",
    "SourceARN": "arn:aws:s3:::your-bucket-name/revoked-serials.json"
  }'
```

等待 KeyValueStore 完成预调配。通过以下方式查看状态：

```
aws cloudfront get-key-value-store --name "revoked-serials-kvs"
```

### 第 2 步：创建吊销连接函数
<a name="step2-create-revocation-function"></a>

创建一个连接函数，该函数会根据 KeyValueStore 中的数据检查证书序列号：

```
aws cloudfront create-connection-function \
  --name "revocation-control" \
  --connection-function-config file://connection-function-config.json \
  --connection-function-code file://connection-function-code.txt
```

配置文件用于指定 KeyValueStore 关联：

```
{
  "Runtime": "cloudfront-js-2.0",
  "Comment": "A function that implements revocation control via KVS",
  "KeyValueStoreAssociations": {
    "Quantity": 1,
    "Items": [
      {
        "KeyValueStoreArn": "arn:aws:cloudfront::account-id:key-value-store/kvs-id"
      }
    ]
  }
}
```

示例连接函数代码：

```
import cf from 'cloudfront';

async function connectionHandler(connection) {
    const kvsHandle = cf.kvs();

    // Get client serial number from client certificate
    const clientSerialNumber =
        connection.clientCertificate.certificates.leaf.serialNumber;

    // Check KVS to see if serial number exists as a key
    // Remove : from the clientSerialNumber if KVS entries dont have it
    const serialNumberExistsInKvs =
        await kvsHandle.exists(clientSerialNumber.replaceAll(":", ""));

    // Deny connection if serial number exists in KVS
    if (serialNumberExistsInKvs) {
        console.log("Connection denied — certificate revoked");
        connection.logCustomData("Connection denied — certificate revoked");
        return connection.deny();
    }

    // Allow connections that don't exist in KVS
    console.log("Connection allowed");
    return connection.allow();
}
```

### 第 3 步：测试吊销函数
<a name="step3-test-revocation-function"></a>

使用 CloudFront 控制台，通过示例证书测试您的连接函数。在控制台中导航到连接函数，然后使用“测试”选项卡。
+ 将 PEM 格式的示例证书粘贴到测试界面中。
+ （可选）指定用于测试基于 IP 的逻辑的客户端 IP 地址。
+ 选择**测试函数**以查看执行结果。
+ 查看执行日志以验证函数逻辑。

使用有效证书和已吊销证书进行测试，确保您的函数可以正确处理这两种场景。

### 第 4 步：将函数关联到分配
<a name="step4-associate-function"></a>

在发布连接函数后，将其与已启用 mTLS 的分配相关联，以激活证书吊销检查。导航到您的分配设置，滚动到“查看器双向身份验证（mTLS）”部分，选择您的连接函数，然后保存更改。

## 高级吊销策略
<a name="advanced-revocation-strategies"></a>

### 将 OCSP 与连接函数逻辑相结合
<a name="combine-ocsp-kvs"></a>

您可以启用 OCSP 来进行 CA 授权吊销检查，并在顶部叠加连接函数以实现自定义策略。连接函数接收 OCSP 结果并可以应用其它逻辑：
+ **宽限期**：在证书轮换期间，允许在规定的时间内从内部网络吊销的证书。
+ **紧急访问**：即使 OCSP 报告已吊销状态，也允许来自特定 IP 的连接。
+ **自定义拒绝逻辑**：根据您自己在 KeyValueStore 中的吊销数据屏蔽 OCSP 报告为“良好”的证书。

**Example OCSP \+ KVS 组合吊销**  

```
import cf from 'cloudfront';

async function connectionHandler(connection) {
    var kvsHandle = cf.kvs();
    var revocationStatus = connection.clientCertificate.revocationStatus;
    var serialNumber = connection.clientCertificate.certificates.leaf.serialNumber;

    // Check your own revocation list first (immediate revocation, no cache delay)
    var inKvs = await kvsHandle.exists(serialNumber.replaceAll(":", ""));
    if (inKvs) {
        connection.logCustomData("KVS_REVOKED:" + serialNumber);
        return connection.deny();
    }

    // Then check OCSP result
    if (revocationStatus.chainValidity === 'Valid' && revocationStatus.certificates.leaf.status === "Revoked") {
        // OCSP says revoked — allow grace period from trusted IPs
        if (connection.clientIp.startsWith("10.0.")) {
            connection.logCustomData("GRACE_PERIOD:" + serialNumber + ":" + connection.clientIp);
            return connection.allow();
        }
        connection.logCustomData("OCSP_REVOKED:" + serialNumber);
        return connection.deny();
    }

    connection.allow();
}
```

此模式可让您通过 KVS（无缓存延迟）立即吊销，以及通过 OCSP 进行 CA 授权吊销，中间有自定义异常处理。