驗證 CloudTrail 湖泊儲存的查詢結果 - AWS CloudTrail

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

驗證 CloudTrail 湖泊儲存的查詢結果

若要判斷查詢結果在 CloudTrail 傳遞查詢結果後是否已修改、刪除或未變更查詢結果,您可以使用 CloudTrail 查詢結果完整性驗證。此功能以產業標準演算法建置:SHA-256 適用於進行雜湊,而含 RSA 的 SHA-256 適用於進行數位簽署。這使得在計算上不可行修改,刪除或偽造 CloudTrail 查詢結果文件而不進行檢測。您可以使用命令列來驗證查詢結果檔案。

為什麼使用它?

驗證過的查詢結果檔案對於安全和鑑識調查十分重要。例如,驗證過的查詢結果檔案可讓您肯定地宣告查詢結果檔案本身並未變更。 CloudTrail 查詢結果檔案完整性驗證程序也會讓您知道查詢結果檔案是否已刪除或變更。

驗證已儲存的查詢結果 AWS CLI

您可以使用 aws cloudtrail verify-query-results 命令驗證查詢結果檔案和簽署檔案的完整性。

必要條件

若要使用命令列驗證查詢結果完整性,必須符合下列條件:

  • 您必須具有的線上連線 AWS。

  • 您必須使用 AWS CLI 版本 2。

  • 若要在本機驗證查詢結果檔案和簽署檔案,請套用下列條件:

    • 您必須將查詢結果檔案和簽署檔案放置在指定的檔案路徑。指定檔案路徑作為 --local-export-path 參數的值。

    • 您不得重新命名查詢結果檔案和簽署檔案。

  • 若要在 S3 儲存貯體中驗證查詢結果檔案和簽署檔案,請套用下列條件:

    • 您不得重新命名查詢結果檔案和簽署檔案。

    • 您必須具有包含查詢結果檔案和簽署檔案之 Amazon S3 儲存貯體的讀取權限。

    • 指定的 S3 字首必須包含查詢結果檔案和簽署檔案。指定 S3 字首作為 --s3-prefix 參數的值。

verify-query-results

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 搭配使用。

範例

下列範例使用 --s3-bucket--s3-prefix 命令列選項驗證查詢結果,以指定包含查詢結果檔案和簽署檔案的 S3 儲存貯體名稱和字首。

aws cloudtrail verify-query-results --s3-bucket DOC-EXAMPLE-BUCKET --s3-prefix prefix --region region

下列範例使用 --local-export-path 命令列選項驗證已下載查詢結果,以指定查詢結果檔案和簽署檔案的本機路徑。如需有關下載查詢結果檔案的詳細資訊,請參閱 下載您的 CloudTrail Lake 儲存的查詢結果

aws cloudtrail verify-query-results --local-export-path local_file_path --region region

驗證結果

下表說明查詢結果檔案和簽署檔案的可能驗證訊息。

檔案類型 驗證訊息 描述
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 簽署檔案結構

簽署檔案包含您儲存查詢結果時傳送至您 Amazon S3 儲存貯體的每個查詢結果檔案名稱、每個查詢結果檔案的雜湊值,以及檔案的數位簽章。數位簽章和雜湊是用於驗證查詢結果檔案和簽署檔案本身的完整性。

簽署檔案位置

簽署檔案會傳送到遵循此語法的 Amazon S3 儲存貯體位置。

s3://DOC-EXAMPLE-BUCKET/optional-prefix/AWSLogs/aws-account-ID/CloudTrail-Lake/Query/year/month/date/query-ID/result_sign.json

範例簽署檔案內容

下列範例符號檔案包含 CloudTrail Lake 查詢結果的資訊。

{ "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" }

簽署檔案欄位說明

以下是簽署檔案中每個欄位的說明:

version

簽署檔案的版本。

region

用於儲存查詢結果之 AWS 帳戶的「區域」。

files.fileHashValue

壓縮查詢結果檔案內容的十六進位編碼雜湊值。

files.fileName

查詢結果檔案的名稱。

hashAlgorithm

用於對查詢結果檔案進行雜湊的雜湊演算法。

signatureAlgorithm

用於簽署檔案的演算法。

queryCompleteTime

指出何時將查詢結果 CloudTrail 傳遞至 S3 儲存貯體。您可以使用此值來尋找公有金鑰。

hashSignature

檔案的雜湊簽章。

publicKeyFingerprint

用於簽署檔案之公有金鑰的十六進位編碼指紋。

CloudTrail 查詢結果檔案完整性驗證的自訂實作

由於 CloudTrail 使用業界標準、公開可用的加密演算法和雜湊函數,因此您可以建立自己的工具來驗證 CloudTrail 查詢結果檔案的完整性。將查詢結果儲存到 Amazon S3 儲存貯體時,會將簽署檔 CloudTrail 交付到 S3 儲存貯體。您可以實作自己的驗證解決方案,以驗證簽章和查詢結果檔案。如需簽署檔案的詳細資訊,請參閱 CloudTrail 簽署檔案結構

本主題說明簽署檔案的簽署方式,並接著詳細說明您必須執行的步驟,以實作解決方案來驗證簽署檔案及其所參考的查詢結果檔案。

瞭解 CloudTrail 簽署檔案的簽署方式

CloudTrail 簽署檔案會使用 RSA 數位簽章簽署。針對每個簽署檔案, CloudTrail執行下列動作:

  1. 建立雜湊清單,內含每個查詢結果檔案的雜湊值。

  2. 取得區域唯一的私有金鑰。

  3. 將字串和私有金鑰的 SHA-256 雜湊傳遞到 RSA 簽署演算法,以產生數位簽章。

  4. 將簽章的位元組碼編碼為十六進位格式。

  5. 將數位簽章放入簽署檔案中。

資料簽署字串內容

資料簽署字串是由每個查詢結果檔案的雜湊值 (以空格分隔) 所組成。簽署檔案會列出每個查詢結果檔案的 fileHashValue

自訂驗證實作步驟

實作自訂驗證解決方案時,您需要先驗證簽署檔案,再驗證其所參考的查詢結果檔案。

驗證簽署檔案

若要驗證簽署檔案,您需要其簽章、其私有金鑰已用來簽署的公有金鑰,以及用來運算的資料簽署字串。

  1. 取得簽署檔案。

  2. 確認已從其原始位置擷取簽署檔案。

  3. 取得簽署檔案的十六進位編碼簽章。

  4. 取得其私有金鑰已用來簽署簽署檔案之公有金鑰的十六進位編碼指紋。

  5. 擷取簽署檔案中 queryCompleteTime 對應時間範圍的公有金鑰。針對時間範圍,請選擇早於 queryCompleteTimeStartTime 和晚於 queryCompleteTimeEndTime

  6. 從所擷取的公有金鑰,選擇其指紋符合簽署檔案中 publicKeyFingerprint 值的公有金鑰。

  7. 使用包含每個查詢結果檔案雜湊值 (以空格分隔) 的雜湊清單,重新建立用於驗證簽署檔案簽章的資料簽署字串。簽署檔案會列出每個查詢結果檔案的 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"
 } ],
  8. 傳遞字串、公有金鑰和簽章的 SHA-256 雜湊做為 RSA 簽章驗證演算法的參數,來驗證簽章。如果結果為 true,則簽署檔案有效。

驗證查詢結果檔案

如果簽署檔案有效,請驗證簽署檔案所參考的查詢結果檔案。若要驗證查詢結果檔案的完整性,請在其壓縮內容上運算 SHA-256 雜湊值,並將結果與簽署檔案中所記錄查詢結果檔案的 fileHashValue 進行比較。如果雜湊相符,則查詢結果檔案有效。

下列各節將詳細說明驗證程序。

A. 取得簽署檔案

第一步是取得簽署檔案和公有金鑰的指紋。

  1. 針對您要驗證的查詢結果從 Amazon S3 儲存貯體取得簽署檔案。

  2. 接下來,從簽署檔案中取得 hashSignature 值。

  3. 在簽署檔案中,從 publicKeyFingerprint 欄位取得其私有金鑰已用來簽署檔案之公有金鑰的指紋。

B. 擷取用於驗證簽署檔案的公有金鑰

若要取得驗證簽署檔案的公開金鑰,您可以使用 AWS CLI 或 CloudTrail API。在這兩種情況下,您會為要驗證的簽署檔案指定時間範圍 (即開始時間和結束時間)。使用符號檔案中 queryCompleteTime 對應的時間範圍。您指定的時間範圍內可能會傳回一或多個公有金鑰。傳回的金鑰可能會有重疊的有效時間範圍。

注意

由於每個區域 CloudTrail 使用不同的私鑰/公鑰對,因此每個簽署文件都使用其區域唯一的私鑰進行簽名。因此,當您驗證來自特定區域的簽署檔案時,您必須從同一個區域擷取其公有金鑰。

使用擷 AWS CLI 取公開金鑰

若要使用擷取簽署檔案的公開金鑰 AWS CLI,請使用cloudtrail list-public-keys指令。此命令的格式如下:

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

開始時間和結束時間參數是 UTC 時間戳記,而且是選用的。如果未指定,則會使用目前的時間,並傳回一或多個目前作用中的公有金鑰。

回應範例

回應會是代表所傳回之一或多個金鑰的 JSON 物件清單:

使用 CloudTrail API 擷取公開金鑰

若要使用 CloudTrail API 擷取簽署檔案的公開金鑰,請將開始時間和結束時間值傳遞至 ListPublicKeys API。ListPublicKeys API 會傳回其私有金鑰已在指定時間範圍內用來簽署檔案的公有金鑰。針對每個公有金鑰,API 也會傳回對應的指紋。

ListPublicKeys

本節說明 ListPublicKeys API 的請求參數和回應元素。

注意

ListPublicKeys 的二進位欄位編碼可能會有所變更。

請求參數

名稱 描述
StartTime

選擇性地指定時間範圍的開始時間範圍,以查詢 CloudTrail 簽署檔案的公開金鑰。如果 StartTime 未指定,則使用當前時間,並返回當前的公鑰。

類型: DateTime

EndTime

選擇性地指定時間範圍的結束時間範圍,以查詢 CloudTrail 簽署檔案的公開金鑰。如果 EndTime 未指定,則使用目前時間。

類型: DateTime

回應元素

PublicKeyListPublicKey 物件陣列,其中包含:

名稱 描述
Value

PKCS #1 格式的 DER 編碼公有金鑰值。

類型:Blob

ValidityStartTime

公有金鑰的有效開始時間。

類型: DateTime

ValidityEndTime

公有金鑰的有效結束時間。

類型: DateTime

Fingerprint

公有金鑰的指紋。該指紋可用來識別驗證簽署檔案所需使用的公有金鑰。

類型:字串

C. 選擇要用於驗證的公有金鑰

list-public-keysListPublicKeys 所擷取的公有金鑰,選擇其指紋符合簽署檔案 publicKeyFingerprint 欄位中所記錄之指紋的公有金鑰。這是您將用來驗證簽署檔案的公有金鑰。

D. 重新建立資料簽署字串

現在您已擁有簽署檔案的簽章及相關聯的公有金鑰,您需要計算資料簽署字串。計算資料簽署字串之後,您將擁有驗證簽章所需的輸入。

資料簽署字串是由每個查詢結果檔案的雜湊值 (以空格分隔) 所組成。重新建立此字串之後,您可以驗證簽署檔案。

E. 驗證簽署檔案

將重新建立後的資料簽署字串、數位簽章和公有金鑰傳遞到 RSA 簽章驗證演算法。如果輸出為 true,則簽署檔案的簽章已經過驗證且簽署檔案有效。

F. 驗證查詢結果檔案

驗證簽署檔案之後,您可以驗證其所參考的查詢結果檔案。簽署檔案包含查詢結果檔案的 SHA-256 雜湊。如果其中一個查詢結果檔案在 CloudTrail 傳送之後被修改,SHA-256 雜湊將會變更,且簽署檔案的簽章將不符。

使用以下程序來驗證簽署檔案的 files 陣列中列出的查詢結果檔案。

  1. 從簽署檔案中的 files.fileHashValue 欄位擷取檔案的原始雜湊。

  2. 使用 hashAlgorithm 中指定的雜湊演算法,將查詢結果檔案的壓縮內容進行雜湊。

  3. 將您為每個查詢結果檔案產生的雜湊值與簽署檔案中的 files.fileHashValue 進行比較。如果雜湊相符,則查詢結果檔案有效。

離線驗證簽章和查詢結果檔案

離線驗證簽署和查詢結果檔案時,您通常可以遵循先前章節中所述的程序。不過,您必須考慮以下有關公有金鑰的資訊。

公有金鑰

若要離線驗證,您必須先在線上取得在指定時間範圍內驗證查詢結果檔案所需的公有金鑰 (例如藉由呼叫 ListPublicKeys),然後存放在離線位置。每當您想要在指定的初始時間範圍外驗證其他檔案時,都必須重複此步驟。

範例驗證程式碼片段

下列範例程式碼片段提供用於驗證 CloudTrail 符號和查詢結果檔案的基礎架構程式碼。此骨架程式碼線上/離線皆可使用;也就是說,您可以決定是否要線上連線到 AWS來實作它。建議的實作使用 Java Cryptography Extension (JCE)Bouncy Castle 做為安全供應商。

此範例程式碼片段說明:

  • 如何建立用來驗證簽署檔案簽章的資料簽署字串。

  • 如何驗證簽署檔案的簽章。

  • 如何計算查詢結果檔案的雜湊值,並將其與簽署檔案中列出的 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."); } }