CloudTrail のクエリファイルの整合性検証のカスタム実装 - AWS CloudTrail

CloudTrail のクエリファイルの整合性検証のカスタム実装

CloudTrail では、オープンで提供されている業界標準の暗号化アルゴリズムとハッシュ関数が使用されるため、CloudTrail クエリ結果ファイルの整合性を検証するために独自のツールを作成することができます。Amazon S3 バケットにクエリ結果を保存すると、CloudTrail は署名ファイルを S3 バケットに送信します。独自の検証ソリューションを実装して、署名ファイルとクエリ結果ファイルを検証できます。署名ファイルの詳細については、「CloudTrail 署名ファイル構造」を参照してください。

このトピックでは、署名ファイルの署名方法について説明し、署名ファイルと、署名ファイルによって参照される署名ファイルを検証するソリューションの実装に必要な手順を詳しく示します。

CloudTrail 署名ファイルの署名の方法を理解する

CloudTrail 署名ファイルは RSA デジタル署名で署名されます。CloudTrail は各署名ファイルを次のように処理します。

  1. 各クエリ結果ファイルのハッシュ値を含むハッシュリストを作成します。

  2. リージョンに固有のプライベートキーを取得します。

  3. 文字列の SHA-256 ハッシュとプライベートキーを RSA 署名アルゴリズムに渡すと、そこでデジタル署名が作成されます。

  4. 署名のバイトコードを 16 進形式にエンコードします。

  5. デジタル署名を署名ファイルに入力します。

データ署名文字列の内容

データ署名文字列は、スペースで区切られた各クエリ結果ファイルのハッシュ値で構成されます。署名ファイルには、各クエリ結果ファイルの fileHashValue がリストされています。

カスタム検証を実装する手順

カスタム検証ソリューションを実装するときは、最初にダイジェストファイルを検証してから、署名ファイルと参照するクエリ結果ファイルを検証する必要があります。

署名ファイルを検証する

署名ファイルを検証するには、署名、対応するプライベートキーが署名に使用されたパブリックキー、計算したデータ署名文字列が必要です。

  1. 署名ファイルを入手してください。

  2. 本来の場所から署名ファイルが取得されたことを確認します。

  3. 署名ファイルの 16 進エンコードされた署名を取得します。

  4. パブリックキー (対応するプライベートキーが署名ファイルの署名に使用された) の 16 進エンコードされたフィンガープリントを取得します。

  5. 署名ファイルで queryCompleteTime に対応する時間範囲のパブリックキーを取得します。時間範囲には、「StartTime より早い queryCompleteTime」および「EndTime より遅い queryCompleteTime」を選択します。

  6. 取得したパブリックキーの中から、フィンガープリントが署名ファイルの publicKeyFingerprint の値と一致するパブリックキーを選択します。

  7. 各クエリ結果ファイルのハッシュ値をスペースで区切ったハッシュリストを使用して、署名ファイルの署名を検証するために使用するデータ署名文字列を再作成します。署名ファイルには、各クエリ結果ファイルの fileHashValue がリストされています。

    たとえば、署名ファイルの files 配列に次の 3 つのクエリ結果ファイルが含まれている場合、ハッシュリストは「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 に対応する時間範囲を使用してください。指定した時間範囲について 1 つ以上のパブリックキーが返されることがあります。返されたキーの有効な時間範囲が重複する可能性があります。

注記

CloudTrail では、リージョンごとに異なるプライベート/パブリックキーのペアが使用されるため、各署名ファイルはリージョン固有のプライベートキーで署名されます。したがって、特定のリージョンの署名ファイルを検証するときは、同じリージョンからパブリックキーを取得する必要があります。

AWS CLI を使用してパブリックキーを取得する

AWS CLI を使用して署名ファイルのパブリックキーを取得するには、cloudtrail list-public-keys コマンドを使用します。このコマンドの形式は次のとおりです。

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

start-time および end-time パラメータには UTC タイムスタンプを使用します。これらはオプションです。指定しない場合、現在の時刻が使用され、現在アクティブなパブリックキー (1 つまたは複数) が返されます。

レスポンス例

レスポンスは、返されるキー (1 つまたは複数) を表す JSON オブジェクトのリストです。

CloudTrail API を使用してパブリックキーを取得する

CloudTrail API を使用して署名ファイルのパブリックキーを取得するには、開始時刻と終了時刻の値を ListPublicKeys API に渡します。この ListPublicKeys API は、指定された時間範囲内の、対応するプライベートキーが署名ファイルの署名に使用されたパブリックキーを返します。API は、各パブリックキーに対応するフィンガープリントも返します。

ListPublicKeys

このセクションでは、ListPublicKeys API のリクエストパラメータとレスポンス要素について説明します。

注記

ListPublicKeys のバイナリフィールドのエンコードは変更される可能性があります。

リクエストパラメータ

名前 説明
StartTime

オプションとして、CloudTrail 署名ファイルのパブリックキーを検索する時間範囲の開始時刻を UTC で指定します。StartTime が指定されない場合、現在の時刻が使用され、現在のパブリックキーが返されます。

型: DateTime

EndTime

オプション。CloudTrail 署名ファイルのパブリックキーを検索する時間範囲の終了時刻を UTC で指定します。EndTime が指定されない場合、現在の時刻が使用されます。

型: DateTime

レスポンス要素

PublicKeyList は、次の要素を含む PublicKey オブジェクトの配列です。

名前 説明
Value

DER エンコードされたパブリックキー値 (PKCS #1 形式)。

型: Blob

ValidityStartTime

パブリックキーの有効期間の開始時刻。

型: DateTime

ValidityEndTime

パブリックキーの有効期間の終了時刻。

型: DateTime

Fingerprint

パブリックキーのフィンガープリント。フィンガープリントを使用して、署名ファイルの検証に使用する必要があるパブリックキーを特定できます。

型: 文字列

C. 検証に使用するパブリックキーを選択する

list-public-keys または ListPublicKeys によって取得されたパブリックキーの中から、そのフィンガープリントが署名ファイルの 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."); } }