

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 验证 CloudTrail Lake 保存的查询结果
<a name="cloudtrail-query-results-validation"></a>

要确定查询结果在 CloudTrail 传送查询结果后是修改、删除还是未更改，您可以使用 CloudTrail 查询结果完整性验证。该功能是使用业界标准算法构建的：哈希采用 SHA-256，数字签名采用带 RSA 的 SHA-256。这使得在不被发现的情况下修改、删除或伪造 CloudTrail 查询结果文件在计算上是不可行的。您可以使用命令行验证查询结果文件。

## 为什么使用它？
<a name="cloudtrail-query-results-validation-use-cases"></a>

在安全和事故调查中，经验证的查询结果文件非常重要。例如，经验证的查询结果文件可帮助您明确地断言查询结果文件未经更改。 CloudTrail 查询结果文件完整性验证过程还会让您知道查询结果文件是否已被删除或更改。

**Topics**
+ [为什么使用它？](#cloudtrail-query-results-validation-use-cases)
+ [使用验证保存的查询结果 AWS CLI](#cloudtrail-query-results-validation-cli)
+ [CloudTrail 签名文件结构](#cloudtrail-results-file-validation-sign-file-structure)
+ [CloudTrail 查询结果文件完整性验证的自定义实现](#cloudtrail-results-file-custom-validation)

## 使用验证保存的查询结果 AWS CLI
<a name="cloudtrail-query-results-validation-cli"></a>

您可以使用 [https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudtrail/verify-query-results.html](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudtrail/verify-query-results.html) 命令验证查询结果和签名文件的完整性。

### 先决条件
<a name="cloudtrail-query-results-validation-cli-prerequisites"></a>

要使用命令行验证查询结果的完整性，必须满足以下条件：
+ 您必须联机连接到 AWS。
+ 您必须使用 AWS CLI 版本 2。
+ 要在本地验证查询结果文件和签名文件，应满足以下条件：
  + 必须将查询结果文件和签名文件放入指定的文件路径。将文件路径指定为 **--local-export-path** 参数的值。
  + 不得重命名查询结果文件和签名文件。
+ 要在 S3 存储桶中验证查询结果文件和签名文件，应满足以下条件：
  + 不得重命名查询结果文件和签名文件。
  + 必须拥有包含查询结果文件和签名文件的 Amazon S3 存储桶的读取访问权限。
  + 指定的 S3 前缀必须包含查询结果文件和签名文件。将 S3 前缀指定为 **--s3-prefix** 参数的值。

### verify-query-results
<a name="cloudtrail-query-results-validation-cli-command"></a>

 **verify-query-results** 命令将每个查询结果文件的哈希值与签名文件中的 `fileHashValue` 进行比对，再验证签名文件中的 `hashSignature`，从而验证前述哈希值。

验证查询结果时，您可以使用 **--s3-bucket** 和 **--s3-prefix** 命令行选项来验证存储在 S3 存储桶中的查询结果文件和签名文件，也可以使用 **--local-export-path** 命令行选项对下载的查询结果文件和签名文件执行本地验证。

**注意**  
**verify-query-results** 命令与特定区域相关。必须指定**--region**全局选项才能验证特定项的查询结果 AWS 区域。

**verify-query-results** 命令的选项如下：

**--s3-bucket** {{<string>}}  
指定存储查询结果文件和签名文件的 S3 存储桶名称。此参数不能与 **--local-export-path** 一起使用。

**--s3-prefix** {{<string>}}  
指定包含查询结果文件和签名文件的 S3 文件夹的 S3 路径（例如 `s3/path/`）。此参数不能与 **--local-export-path** 一起使用。如果文件位于 S3 存储桶的根目录中，则无需提供此参数。

**--local-export-path** {{<string>}}  
指定包含查询结果文件和签名文件的本地目录（例如 `/local/path/to/export/file/`）。此参数不能与 **--s3-bucket** 或 **--s3-prefix** 一起使用。

#### 示例
<a name="cloudtrail-query-results-validation-cli-examples"></a>

以下示例使用 **--s3-bucket** 和 **--s3-prefix** 命令行选项来指定包含查询结果文件和签名文件的 S3 存储桶名称和前缀，从而验证查询结果。

```
aws cloudtrail verify-query-results --s3-bucket {{amzn-s3-demo-bucket}} --s3-prefix {{prefix}} --region {{region}}
```

以下示例使用 **--local-export-path** 命令行选项来指定查询结果文件和签名文件的本地路径，从而验证下载的查询结果。有关下载查询结果文件的更多信息，请参阅[下载您的 CloudTrail Lake 已保存的查询结果](view-download-cloudtrail-lake-query-results.md#cloudtrail-download-lake-query-results)。

```
aws cloudtrail verify-query-results --local-export-path {{local_file_path}} --region {{region}}
```

#### 验证结果
<a name="cloudtrail-query-results-validation-cli-command-messages"></a>

下表描述了查询结果文件和签名文件可能会出现的验证消息。


****  

| 文件类型 | 验证消息 | 说明 | 
| --- | --- | --- | 
| Sign file | Successfully validated sign and query result files | 签名文件的签名有效。其引用的查询结果文件可供检查。 | 
| Query result file |  `ValidationError: "File {{file_name}} has inconsistent hash value with hash value recorded in sign file, hash value in sign file is {{expected_hash}}, but get {{computed_hash}}`  | 验证失败，因为查询结果文件的哈希值与签名文件中的 fileHashValue 不相符。 | 
| Sign file |  `ValidationError: Invalid signature in sign file`  | 签名文件验证失败，因为签名无效。 | 

## CloudTrail 签名文件结构
<a name="cloudtrail-results-file-validation-sign-file-structure"></a>

签名文件包含保存查询结果时传送到 Amazon S3 存储桶的每个查询结果文件的名称、每个查询结果文件的哈希值以及文件的数字签名。数字签名和哈希值用于验证查询结果文件和签名文件本身的完整性。

### 签名文件位置
<a name="cloudtrail-results-file-validation-sign-file-location"></a>

签名文件将传送到遵循以下语法的 Amazon S3 存储桶位置。

```
s3://{{amzn-s3-demo-bucket}}/{{optional-prefix/}}AWSLogs/{{aws-account-ID}}/CloudTrail-Lake/Query/{{year}}/{{month}}/{{date}}/{{query-ID}}/result_sign.json
```

### 示例签名文件内容
<a name="cloudtrail-results-file-validation-sign-file-contents"></a>

以下示例签名文件包含 La CloudTrail ke 查询结果的信息。

```
{
  "version": "1.0",
  "region": "us-east-1",
  "files": [
    {
      "fileHashValue" : "de85a48b8a363033c891abd723181243620a3af3b6505f0a44db77e147e9c188",
      "fileName" : "result_1.csv.gz"
    }
  ],
  "hashAlgorithm" : "SHA-256",
  "signatureAlgorithm" : "SHA256withRSA",
  "queryCompleteTime": "2022-05-10T22:06:30Z",
  "hashSignature" : "7664652aaf1d5a17a12ba50abe6aca77c0ec76264bdf7dce71ac6d1c7781117c2a412e5820bccf473b1361306dff648feae20083ad3a27c6118172a81635829bdc7f7b795ebfabeb5259423b2fb2daa7d1d02f55791efa403dac553171e7ce5f9307d13e92eeec505da41685b4102c71ec5f1089168dacde702c8d39fed2f25e9216be5c49769b9db51037cb70a84b5712e1dffb005a74580c7fdcbb89a16b9b7674e327de4f5414701a772773a4c98eb008cca34228e294169901c735221e34cc643ead34628aabf1ba2c32e0cdf28ef403e8fe3772499ac61e21b70802dfddded9bea0ddfc3a021bf2a0b209f312ccee5a43f2b06aa35cac34638f7611e5d7",
  "publicKeyFingerprint" : "67b9fa73676d86966b449dd677850753"
}
```

### 签名文件字段描述
<a name="cloudtrail-results-file-validation-sign-file-descriptions"></a>

以下是对签名文件中每个字段的描述：

`version`  
签名文件的版本。

`region`  
用于保存查询结果的 AWS 账户的区域。

`files.fileHashValue`  
已压缩的查询结果文件内容的十六进制编码哈希值。

`files.fileName`  
查询结果文件的名称。

`hashAlgorithm`  
用于对查询结果文件进行哈希处理的哈希算法。

`signatureAlgorithm`  
用于对文件进行签名的算法。

`queryCompleteTime`  
表示何时将查询结果 CloudTrail 传送到 S3 存储桶。您可以使用此值来查找公钥。

`hashSignature`  
文件的哈希签名。

`publicKeyFingerprint`  
用于对文件进行签名的公钥的十六进制编码指纹。

## CloudTrail 查询结果文件完整性验证的自定义实现
<a name="cloudtrail-results-file-custom-validation"></a>

由于 CloudTrail 使用行业标准、公开可用的加密算法和哈希函数，因此您可以创建自己的工具来验证 CloudTrail 查询结果文件的完整性。当您将查询结果保存到 Amazon S3 存储桶时，会 CloudTrail 将签名文件发送到您的 S3 存储桶。您可以实施自己的验证解决方案以验证签名和查询结果文件。有关签名文件的更多信息，请参阅[CloudTrail 签名文件结构](#cloudtrail-results-file-validation-sign-file-structure)。

本主题介绍了签名文件的签名方式，并详述了实施验证签名文件及签名文件所引用查询结果文件的解决方案所需采取的步骤。

### 了解 CloudTrail 签名文件的签名方式
<a name="cloudtrail-results-file-custom-validation-how-cloudtrail-sign-files-are-signed"></a>

CloudTrail 签名文件使用 RSA 数字签名进行签名。对于每个签名文件，执行以下 CloudTrail操作：

1. 创建一个哈希列表，其中包含每个查询结果文件的哈希值。

1. 获取区域唯一的私钥。

1. 将此字符串的 SHA-256 哈希值和私钥传递给 RSA 签名算法（生成数字签名）。

1. 将签名的字节代码编码成十六进制格式。

1. 将数字签名放入签名文件中。

#### 数据签名字符串的内容
<a name="cloudtrail-results-file-custom-validation-data-signing-string-summary"></a>

数据签名字符串包含以空格分隔的每个查询结果文件的哈希值。签名文件列出了每个查询结果文件的 `fileHashValue`。

### 自定义验证实现步骤
<a name="cloudtrail-results-file-custom-validation-steps"></a>

实施自定义验证解决方案时，需要验证签名文件及其引用的查询结果文件。

#### 验证签名文件
<a name="cloudtrail-results-file-custom-validation-steps-sign"></a>

要验证签名文件，您需要其签名、与用于对其进行签名的私钥对应的公钥以及您计算的数据签名字符串。

1. 获取签名文件。

1. 验证是否已从签名文件的原始位置检索到签名文件。

1. 获取签名文件的十六进制编码签名。

1. 获取与用于对签名文件进行签名的私钥对应的公钥的十六进制编码指纹。

1. 检索与签名文件中的 `queryCompleteTime` 对应的时间范围的公钥。对于时间范围，请选择早于 `queryCompleteTime` 的 `StartTime` 和晚于 `queryCompleteTime` 的 `EndTime`。

1. 从检索到的公钥中，选择指纹与签名文件中的 `publicKeyFingerprint` 值匹配的公钥。

1. 使用包含以空格分隔的每个查询结果文件哈希值的哈希列表，重新创建用于验证签名文件签名的数据签名字符串。签名文件列出了每个查询结果文件的 `fileHashValue`。

   例如，如果签名文件的 `files` 数组包含以下三个查询结果文件，则哈希列表为“aaa bbb ccc”。

   ```
   “files": [ 
      { 
           "fileHashValue" : “aaa”, 
           "fileName" : "result_1.csv.gz" 
      },
      {       
           "fileHashValue" : “bbb”,       
           "fileName" : "result_2.csv.gz"      
      },
      { 
           "fileHashValue" : “ccc”,       
           "fileName" : "result_3.csv.gz" 
      }
   ],
   ```

1. 将此字符串的 SHA-256 哈希值、公钥及签名作为参数传递给 RSA 签名验证算法，以验证签名。如果结果为 true，则签名文件有效。

#### 验证查询结果文件
<a name="cloudtrail-results-file-custom-validation-steps-logs"></a>

如果签名文件有效，请验证签名文件引用的查询结果文件。要验证查询结果文件的完整性，请计算其压缩内容的 SHA-256 哈希值，并将结果与签名文件中记录的查询结果文件中的 `fileHashValue` 进行比较。如果哈希值匹配，则查询结果文件有效。

以下部分详细介绍了验证过程。

#### A. 获取签名文件
<a name="cloudtrail-results-file-custom-validation-steps-get-the-sign-file"></a>

第一步是获取签名文件并获取公钥的指纹。

1. 从 Amazon S3 存储桶中获取要验证的查询结果的签名文件。

1. 接下来，从签名文件中获取 `hashSignature` 值。

1. 在签名文件中，从 `publicKeyFingerprint` 字段中获取与用于对文件进行签名的私钥对应的公钥的指纹。

#### B. 检索用于验证签名文件的公钥
<a name="cloudtrail-results-file-custom-validation-steps-retrieve-public-key"></a>

要获取用于验证签名文件的公钥，您可以使用 AWS CLI 或 CloudTrail API。在这两种情况下，您都需要指定要验证的签名文件的时间范围（即起始时间和结束时间）。使用与签名文件中的 `queryCompleteTime` 对应的时间范围。对于您指定的时间范围，可能会返回一个或多个公钥。返回的密钥的有效时间范围可能会发生重叠。

**注意**  
由于每个区域 CloudTrail 使用不同的 private/public 密钥对，因此每个签名文件都使用其区域独有的私钥进行签名。因此，当您验证来自特定区域的签名文件时，必须从同一区域检索其公钥。

##### 使用检 AWS CLI 索公钥
<a name="cloudtrail-results-file-custom-validation-steps-retrieve-public-key-cli"></a>

要使用检索签名文件的公钥 AWS CLI，请使用`cloudtrail list-public-keys`命令。此命令采用以下格式：

 `aws cloudtrail list-public-keys [--start-time <start-time>] [--end-time <end-time>]` 

start-time 和 end-time 参数为 UTC 时间戳且是可选的。如果未指定，则使用当前时间，且返回当前有效的一个或多个公钥。

 **示例响应** 

响应是代表所返回的一个或多个密钥的 JSON 对象的列表：

##### 使用 CloudTrail API 检索公钥
<a name="cloudtrail-results-file-custom-validation-steps-retrieve-public-key-api"></a>

要使用 CloudTrail API 检索签名文件的公钥，请将开始时间和结束时间值传递给 `ListPublicKeys` API。`ListPublicKeys` API 会返回与用于在指定时间范围对文件进行签名的私钥对应的公钥。对于每个公钥，此 API 还返回相应的指纹。

##### `ListPublicKeys`
<a name="cloudtrail-results-file-custom-validation-steps-list-public-keys"></a>

本部分介绍 `ListPublicKeys` API 的请求参数和响应元素。

**注意**  
`ListPublicKeys` 的二进制字段的编码可能随时发生变化。

 **请求参数** 


****  

| Name | 说明 | 
| --- | --- | 
|  StartTime  |  （可选）以 UTC 为单位指定查找 CloudTrail 签名文件公钥的时间范围的起始时间。如果 StartTime 未指定，则使用当前时间，并返回当前的公钥。 类型： DateTime   | 
|  EndTime  |  （可选）以 UTC 为单位指定查找 CloudTrail 签名文件公钥的时间范围的结束时间。如果 EndTime 未指定，则使用当前时间。 类型： DateTime   | 

 **响应元素** 

`PublicKeyList`：`PublicKey` 对象数组，包含：


****  

|  |  | 
| --- |--- |
|  名称  |  描述  | 
|  Value  |  DER 编码的公钥值（采用 PKCS \#1 格式）。 类型：Blob   | 
|  ValidityStartTime  |  公钥有效的起始时间。 类型： DateTime   | 
|  ValidityEndTime  |  公钥有效的结束时间。 类型： DateTime   | 
|  Fingerprint  |  公钥的指纹。指纹可用于识别验证签名文件所必需的公钥。 类型：字符串   | 

#### C. 选择要用于验证的公钥
<a name="cloudtrail-results-file-custom-validation-steps-choose-public-key"></a>

从 `list-public-keys` 或 `ListPublicKeys` 检索到的公钥中，选择指纹与签名文件的 `publicKeyFingerprint` 字段中记录的指纹匹配的公钥。此即为用于验证签名文件的公钥。

#### D. 重新创建数据签名字符串
<a name="cloudtrail-results-file-custom-validation-steps-recreate-data-signing-string"></a>

现在，您已拥有签名文件的签名及关联公钥，接下来，您需要计算数据签名字符串。算出数据签名字符串后，您就有了验证签名所需的输入。

数据签名字符串包含以空格分隔的每个查询结果文件的哈希值。重新创建此字符串后，您可以验证签名文件。

#### E. 验证签名文件
<a name="cloudtrail-results-file-custom-validation-steps-validate-sign-file"></a>

将重新创建的数据签名字符串、数字签名和公钥传递给 RSA 签名验证算法。如果输出为 true，则已验证签名文件的签名，且签名文件有效。

#### F. 验证查询结果文件
<a name="cloudtrail-results-file-custom-validation-steps-validate-log-files"></a>

验证签名文件后，您可以验证其引用的查询结果文件。签名文件包含查询结果文件的 SHA-256 哈希值。如果其中一个查询结果文件在 CloudTrail 交付后被修改，则 SHA-256 哈希值将发生变化，并且签名文件的签名将不匹配。

使用以下步骤验证签名文件的 `files` 数组中列出的查询结果文件。

1. 从签名文件中的 `files.fileHashValue` 字段检索文件的原始哈希值。

1. 使用 `hashAlgorithm` 中指定的哈希算法计算压缩的查询结果文件内容的哈希值。

1. 将您为每个查询结果文件生成的哈希值与签名文件中的 `files.fileHashValue` 进行比较。如果哈希值匹配，则查询结果文件有效。

### 离线验证签名和查询结果文件
<a name="cloudtrail-results-file-custom-validation-offline"></a>

离线验证签名和查询结果文件时，您通常可以按照前述部分中介绍的流程进行。但是，您必须考虑以下有关公钥的信息。

#### 公钥
<a name="cloudtrail-results-file-custom-validation-offline-public-keys"></a>

要进行离线验证，首先必须在线获取验证给定时间范围内的查询结果文件所需的公钥（例如，通过调用 `ListPublicKeys` 实现），然后将其离线存储。每当您需要验证超出指定的初始时间范围的其他文件时，都必须重复执行这一步。

### 示例验证代码段
<a name="cloudtrail-results-file-custom-validation-sample-code"></a>

以下示例片段提供了用于验证 CloudTrail 签名和查询结果文件的基本代码。基本代码是 online/offline 不可知论的；也就是说，由你来决定是否在联机连接的情况下实现它。 AWS建议在实现中使用 [Java Cryptography Extension（JCE）](https://en.wikipedia.org/wiki/Java_Cryptography_Extension)和 [Bouncy Castle](https://www.bouncycastle.org/) 作为安全提供程序。

示例代码段：
+ 如何创建用于验证签名文件签名的数据签名字符串。
+ 如何验证签名文件的签名。
+ 如何计算查询结果文件的哈希值，并将其与签名文件中列出的 `fileHashValue` 进行比较，以验证查询结果文件的真实性。

```
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.RSAPublicKey;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.json.JSONArray;
import org.json.JSONObject;
 
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
 
 
public class SignFileValidationSampleCode {
 
    public void validateSignFile(String s3Bucket, String s3PrefixPath) throws Exception {
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
 
        // Load the sign file from S3 (using Amazon S3 Client) or from your local copy
        JSONObject signFile = loadSignFileToMemory(s3Bucket, String.format("%s/%s", s3PrefixPath, "result_sign.json"));
 
        // Using the Bouncy Castle provider as a JCE security provider - http://www.bouncycastle.org/
        Security.addProvider(new BouncyCastleProvider());
 
        List<String> hashList = new ArrayList<>();
 
        JSONArray jsonArray = signFile.getJSONArray("files");
 
        for (int i = 0; i < jsonArray.length(); i++) {
            JSONObject file = jsonArray.getJSONObject(i);
            String fileS3ObjectKey = String.format("%s/%s", s3PrefixPath, file.getString("fileName"));
 
            // Load the export file from S3 (using Amazon S3 Client) or from your local copy
            byte[] exportFileContent = loadCompressedExportFileInMemory(s3Bucket, fileS3ObjectKey);
            messageDigest.update(exportFileContent);
            byte[] exportFileHash = messageDigest.digest();
            messageDigest.reset();
            byte[] expectedHash = Hex.decodeHex(file.getString("fileHashValue"));
 
            boolean signaturesMatch = Arrays.equals(expectedHash, exportFileHash);
            if (!signaturesMatch) {
                System.err.println(String.format("Export file: %s/%s hash doesn't match.\tExpected: %s Actual: %s",
                        s3Bucket, fileS3ObjectKey,
                        Hex.encodeHexString(expectedHash), Hex.encodeHexString(exportFileHash)));
            } else {
                System.out.println(String.format("Export file: %s/%s hash match",
                        s3Bucket, fileS3ObjectKey));
            }
 
            hashList.add(file.getString("fileHashValue"));
        }
        String hashListString = hashList.stream().collect(Collectors.joining(" "));
 
        /*
            NOTE:
            To find the right public key to verify the signature, call CloudTrail ListPublicKey API to get a list
            of public keys, then match by the publicKeyFingerprint in the sign file. Also, the public key bytes
            returned from ListPublicKey API are DER encoded in PKCS#1 format:
 
            PublicKeyInfo ::= SEQUENCE {
                algorithm       AlgorithmIdentifier,
                PublicKey       BIT STRING
            }
 
            AlgorithmIdentifier ::= SEQUENCE {
                algorithm       OBJECT IDENTIFIER,
                parameters      ANY DEFINED BY algorithm OPTIONAL
            }
        */
        byte[] pkcs1PublicKeyBytes = getPublicKey(signFile.getString("queryCompleteTime"),
                signFile.getString("publicKeyFingerprint"));
        byte[] signatureContent = Hex.decodeHex(signFile.getString("hashSignature"));
 
        // Transform the PKCS#1 formatted public key to x.509 format.
        RSAPublicKey rsaPublicKey = RSAPublicKey.getInstance(pkcs1PublicKeyBytes);
        AlgorithmIdentifier rsaEncryption = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null);
        SubjectPublicKeyInfo publicKeyInfo = new SubjectPublicKeyInfo(rsaEncryption, rsaPublicKey);
 
        // Create the PublicKey object needed for the signature validation
        PublicKey publicKey = KeyFactory.getInstance("RSA", "BC")
                .generatePublic(new X509EncodedKeySpec(publicKeyInfo.getEncoded()));
 
        // Verify signature
        Signature signature = Signature.getInstance("SHA256withRSA", "BC");
        signature.initVerify(publicKey);
        signature.update(hashListString.getBytes("UTF-8"));
 
        if (signature.verify(signatureContent)) {
            System.out.println("Sign file signature is valid.");
        } else {
            System.err.println("Sign file signature failed validation.");
        }
 
        System.out.println("Sign file validation completed.");
    }
}
```