CloudTrail ログファイルの整合性検証のカスタム実装 - AWS CloudTrail

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

CloudTrail ログファイルの整合性検証のカスタム実装

CloudTrail では、オープンに利用可能な業界標準の暗号化アルゴリズムとハッシュ関数を使用するため、独自のツールを作成して CloudTrail ログファイルの整合性を検証できます。ログファイルの整合性検証が有効になっている場合、 はダイジェストファイルを Amazon S3 バケットに CloudTrail 配信します。これらのファイルを使用して、独自の検証ソリューションを実装できます。ダイジェストファイルについて詳しくは、「CloudTrail ダイジェストファイル構造」を参照してください。

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

CloudTrail ダイジェストファイルの署名方法を理解する

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

  1. 指定されたダイジェストファイルフィールド (次のセクションで説明) に基づいて、データに署名する文字列を作成します。

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

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

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

  5. デジタル署名を Amazon S3 ダイジェストファイルオブジェクトの x-amz-meta-signature メタデータプロパティに設定します。

データ署名文字列の内容

データ署名の文字列には、次の CloudTrail オブジェクトが含まれます。

  • UTC 拡張形式のダイジェストファイル終了タイムスタンプ (例: 2015-05-08T07:19:37Z)

  • 現在のダイジェストファイルの S3 パス

  • 現在のダイジェストファイルの 16 進エンコードされた SHA-256 ハッシュ

  • 以前のダイジェストファイルの 16 進エンコードされた署名

この文字列を計算するための形式と文字列の例は、このドキュメントの後半で示します。

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

カスタム検証ソリューションを実装するときは、最初にダイジェストファイルを検証してから、その後で、ダイジェストファイルが参照するログファイルを検証する必要があります。

ダイジェストファイルを検証する

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

  1. ダイジェストファイルを取得します。

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

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

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

  5. ダイジェストファイルに対応する時間範囲のパブリックキーを取得します。

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

  7. ダイジェストファイルのハッシュや他のダイジェストファイルフィールドを使用して、ダイジェストファイル署名の検証に使用されるデータ署名文字列を再作成します。

  8. 文字列の SHA-256 ハッシュ、パブリックキー、署名を、パラメータとしてRSA 署名検証アルゴリズムに渡して、署名を検証します。結果が true の場合、ダイジェストファイルは有効です。

ログファイルを検証する

ダイジェストファイルが有効であれば、そのダイジェストファイルが参照するログファイルそれぞれを検証します。

  1. ログファイルの整合性を検証するには、未圧縮の内容に対して SHA-256 ハッシュ値を計算し、その結果を、ダイジェストに 16 進数で記録されたログファイルのハッシュと比較します。ハッシュが一致する場合、ログファイルは有効です。

  2. 現在のダイジェストファイルに含まれる以前のダイジェストファイルの情報を使用して、以前のダイジェストファイルとそれに対応するログファイルを連続して検証します。

以下のセクションで、これらの手順について詳しく説明します。

A. ダイジェストファイルを取得する

最初の手順では、最新のダイジェストファイルを取得し、それを本来の場所から取得したことを確認し、デジタル署名を確認し、パブリックキーのフィンガープリントを取得します。

  1. S3 Get または AmazonS3Client クラス (例) を使用して、検証しようとする時間範囲の Amazon S3 バケットから最新のダイジェストファイルを取得します。

  2. ファイルの取得に使用した S3 バケットと S3 オブジェクトが、ダイジェストファイルそのものに記録された S3 バケットと S3 オブジェクトの場所と一致することを確認します。

  3. 次に、ダイジェストファイルのデジタル署名を、Amazon S3 のダイジェストファイルオブジェクトの x-amz-meta-signature メタデータプロパティから取得します。

  4. ダイジェストファイルで、ダイジェストファイルの署名に使用されたプライベートキーに対応するパブリックキーのフィンガープリントを digestPublicKeyFingerprint フィールドから取得します。

B. ダイジェストファイルの検証のためにパブリックキーを取得する

パブリックキーを取得してダイジェストファイルを検証するには、 AWS CLIまたは CloudTrail API を使用できます。どちらの場合も、検証しようとするダイジェストファイルの時間範囲 (開始時刻と終了時刻) を指定します。指定した時間範囲について 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 オブジェクトのリストです。

{ "publicKeyList": [ { "ValidityStartTime": "1436317441.0", "ValidityEndTime": "1438909441.0", "Value": "MIIBCgKCAQEAn11L2YZ9h7onug2ILi1MWyHiMRsTQjfWE+pHVRLk1QjfWhirG+lpOa8NrwQ/r7Ah5bNL6HepznOU9XTDSfmmnP97mqyc7z/upfZdS/AHhYcGaz7n6Wc/RRBU6VmiPCrAUojuSk6/GjvA8iOPFsYDuBtviXarvuLPlrT9kAd4Lb+rFfR5peEgBEkhlzc5HuWO7S0y+KunqxX6jQBnXGMtxmPBPP0FylgWGNdFtks/4YSKcgqwH0YDcawP9GGGDAeCIqPWIXDLG1jOjRRzWfCmD0iJUkz8vTsn4hq/5ZxRFE7UBAUiVcGbdnDdvVfhF9C3dQiDq3k7adQIziLT0cShgQIDAQAB", "Fingerprint": "8eba5db5bea9b640d1c96a77256fe7f2" }, { "ValidityStartTime": "1434589460.0", "ValidityEndTime": "1437181460.0", "Value": "MIIBCgKCAQEApfYL2FiZhpN74LNWVUzhR+VheYhwhYm8w0n5Gf6i95ylW5kBAWKVEmnAQG7BvS5g9SMqFDQx52fW7NWV44IvfJ2xGXT+wT+DgR6ZQ+6yxskQNqV5YcXj4Aa5Zz4jJfsYjDuO2MDTZNIzNvBNzaBJ+r2WIWAJ/Xq54kyF63B6WE38vKuDE7nSd1FqQuEoNBFLPInvgggYe2Ym1Refe2z71wNcJ2kY+q0h1BSHrSM8RWuJIw7MXwF9iQncg9jYzUlNJomozQzAG5wSRfbplcCYNY40xvGd/aAmO0m+Y+XFMrKwtLCwseHPvj843qVno6x4BJN9bpWnoPo9sdsbGoiK3QIDAQAB", "Fingerprint": "8933b39ddc64d26d8e14ffbf6566fee4" }, { "ValidityStartTime": "1434589370.0", "ValidityEndTime": "1437181370.0", "Value": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqlzPJbvZJ42UdcmLfPUqXYNfOs6I8lCfao/tOs8CmzPOEdtLWugB9xoIUz78qVHdKIqxbaG4jWHfJBiOSSFBM0lt8cdVo4TnRa7oG9io5pysS6DJhBBAeXsicufsiFJR+wrUNh8RSLxL4k6G1+BhLX20tJkZ/erT97tDGBujAelqseGg3vPZbTx9SMfOLN65PdLFudLP7Gat0Z9p5jw/rjpclKfo9Bfc3heeBxWGKwBBOKnFAaN9V57pOaosCvPKmHd9bg7jsQkI9Xp22IzGLsTFJZYVA3KiTAElDMu80iFXPHEq9hKNbt9e4URFam+1utKVEiLkR2disdCmPTK0VQIDAQAB", "Fingerprint": "31e8b5433410dfb61a9dc45cc65b22ff" } ] }

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 によって取得されたパブリックキーの中から、ダイジェストファイルの digestPublicKeyFingerprint フィールドに記録されているフィンガープリントと一致するフィンガープリントのパブリックキーを選択します。これはダイジェストファイルの検証に使用するパブリックキーです。

D. データ署名文字列を再作成する

ダイジェストファイルの署名と、関連付けられたパブリックキーを取得しました。次は、データ署名文字列を計算する必要があります。データ署名文字列の計算が完了すると、署名の検証に必要な入力を得られます。

データ署名文字列は次の形式になります。

Data_To_Sign_String = Digest_End_Timestamp_in_UTC_Extended_format + '\n' + Current_Digest_File_S3_Path + '\n' + Hex(Sha256(current-digest-file-content)) + '\n' + Previous_digest_signature_in_hex

例の Data_To_Sign_String を次に示します。

2015-08-12T04:01:31Z S3-bucket-name/AWSLogs/111122223333/CloudTrail-Digest/us-east-2/2015/08/12/111122223333_us-east-2_CloudTrail-Digest_us-east-2_20150812T040131Z.json.gz 4ff08d7c6ecd6eb313257e839645d20363ee3784a2328a7d76b99b53cc9bcacd 6e8540b83c3ac86a0312d971a225361d28ed0af20d70c211a2d405e32abf529a8145c2966e3bb47362383a52441545ed091fb81 d4c7c09dd152b84e79099ce7a9ec35d2b264eb92eb6e090f1e5ec5d40ec8a0729c02ff57f9e30d5343a8591638f8b794972ce15bb3063a01972 98b0aee2c1c8af74ec620261529265e83a9834ebef6054979d3e9a6767dfa6fdb4ae153436c567d6ae208f988047ccfc8e5e41f7d0121e54ed66b1b904f80fb2ce304458a2a6b91685b699434b946c52589e9438f8ebe5a0d80522b2f043b3710b87d2cda43e5c1e0db921d8d540b9ad5f6d4$31b1f4a8ef2d758424329583897339493a082bb36e782143ee5464b4e3eb4ef6

この文字列を再作成した後、ダイジェストファイルを検証できます。

E. ダイジェストファイルを検証する

再作成したデータ署名文字列の SHA-256 ハッシュ、デジタル署名、パブリックキーを、RSA 署名検証アルゴリズムに渡します。出力が true の場合、ダイジェストファイルの署名が検証され、ダイジェストファイルは有効です。

F. ログファイルを検証する

ダイジェストファイルの検証が完了したら、ダイジェストファイルが参照するログファイルを検証することができます。ダイジェストファイルにはログファイルの SHA-256 ハッシュが含まれています。 CloudTrail 配信後にいずれかのログファイルが変更された場合、SHA-256 ハッシュが変更され、ダイジェストファイルの署名が一致しません。

ログファイルの検証方法を次に示します。

  1. ダイジェストファイルの logFiles.s3Bucket フィールドと logFiles.s3Object フィールドの S3 の場所に関する情報を使用して、ログファイルの S3 Get を実行します。

  2. S3 Get の操作が成功した場合は、ダイジェストファイルの logFiles 配列にリストされているログファイルに対して次の手順を繰り返します。

    1. ダイジェストファイル内で、対応するログの logFiles.hashValue フィールドからファイルの元のハッシュを取得します。

    2. Hash the uncompressed contents of the log file with the hashing algorithm specified in logFiles.hashAlgorithm 指定されたハッシュアルゴリズムを使用して、ログファイルの未圧縮の内容をハッシュします。

    3. 生成されたハッシュ値を、ダイジェストファイルのログのハッシュ値と比較します。ハッシュが一致する場合、ログファイルは有効です。

G. その他のダイジェストファイルとログファイルを検証する

各ダイジェストファイルの次のフィールドには、以前のダイジェストファイルの場所と署名が含まれています。

  • previousDigestS3Bucket

  • previousDigestS3Object

  • previousDigestSignature

この情報を使用して、以前のダイジェストファイルに順番にアクセスし、前のセクションの手順に従って、それぞれの署名と参照先のログファイルを検証します。以前のダイジェストファイルの場合に 1 つ異なるのは、ダイジェストファイルオブジェクトの Amazon S3 メタデータプロパティからデジタル署名を取得する必要がないことです。以前のダイジェストファイルの署名は previousDigestSignature フィールドにあります。

どちらが先になるとしても、最初のダイジェストファイルに到達するか、ダイジェストファイルのチェーンが途切れるまで、さかのぼることができます。

ダイジェストファイルとログファイルをオフラインで検証する

ダイジェストファイルとログファイルをオフラインで検証するとき、通常は前のセクションで説明した手順に従います。ただし、次のことを考慮に入れる必要があります。

最新のダイジェストファイルの処理

最新 (つまり "現在") のダイジェストファイルのデジタル署名は、ダイジェストファイルオブジェクトの Amazon S3 メタデータプロパティにあります。オフラインのシナリオでは、現在のダイジェストファイルのデジタル署名を取得できません。

これに対処するには次の 2 つの方法があります。

  • 前のダイジェストファイルのデジタル署名は現在のダイジェストファイルにあるため、 next-to-last ダイジェストファイルから検証を開始します。この方法では、最新のダイジェストファイルを検証できません。

  • 準備の手順として、現在のダイジェストファイルの署名をダイジェストファイルオブジェクトのメタデータプロパティから取得し、オフラインで安全に保存します。このようにすれば、チェーン内の以前のファイルだけでなく現在のダイジェストファイルも検証できるようになります。

パスの解決

s3ObjectpreviousDigestS3Object など、ダウンロードしたダイジェストファイル内のフィールドは、ログファイルとダイジェストファイルについて Amazon S3 のオンライン上の場所を指しています。オフラインソリューションでは、ダウンロードしたログファイルとダイジェストファイルの現在の場所を指すようにこれらを再設定する方法を見つける必要があります。

パブリックキー

オフラインで検証するには、所定の時間範囲のログファイルの検証に必要なすべてのパブリックキーを最初にオンラインで取得し (たとえば、ListPublicKeys を呼び出す)、オフラインで安全に保存する必要があります。指定した最初の時間範囲外の他のファイルを検証するには、常にこの手順を繰り返す必要があります。

検証のサンプルスニペット

次のサンプルスニペットは、 CloudTrail ダイジェストファイルとログファイルを検証するためのスケルトンコードを提供します。このスケルトンコードはオンラインでもオフラインでも使用できます。つまり、実装する際に AWS とのオンライン接続を使用するかどうかはユーザーが決めることができます。推奨の実装では、Java Cryptography Extension (JCE)Bouncy Castle をセキュリティ プロバイダーとして使用しています。

サンプルスニペットには次の内容が含まれます。

  • ダイジェストファイルの署名の検証に使用されるデータ署名文字列を作成する方法。

  • ダイジェストファイルの署名を確認する方法。

  • ログファイルのハッシュを確認する方法。

  • ダイジェストファイルのチェーンを検証するためのコード構造。

import java.util.Arrays; import java.security.MessageDigest; import java.security.KeyFactory; import java.security.PublicKey; import java.security.Security; import java.security.Signature; import java.security.spec.X509EncodedKeySpec; import org.json.JSONObject; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.apache.commons.codec.binary.Hex; public class DigestFileValidator { public void validateDigestFile(String digestS3Bucket, String digestS3Object, String digestSignature) { // Using the Bouncy Castle provider as a JCE security provider - http://www.bouncycastle.org/ Security.addProvider(new BouncyCastleProvider()); // Load the digest file from S3 (using Amazon S3 Client) or from your local copy JSONObject digestFile = loadDigestFileInMemory(digestS3Bucket, digestS3Object); // Check that the digest file has been retrieved from its original location if (!digestFile.getString("digestS3Bucket").equals(digestS3Bucket) || !digestFile.getString("digestS3Object").equals(digestS3Object)) { System.err.println("Digest file has been moved from its original location."); } else { // Compute digest file hash MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); messageDigest.update(convertToByteArray(digestFile)); byte[] digestFileHash = messageDigest.digest(); messageDigest.reset(); // Compute the data to sign String dataToSign = String.format("%s%n%s/%s%n%s%n%s", digestFile.getString("digestEndTime"), digestFile.getString("digestS3Bucket"), digestFile.getString("digestS3Object"), // Constructing the S3 path of the digest file as part of the data to sign Hex.encodeHexString(digestFileHash), digestFile.getString("previousDigestSignature")); byte[] signatureContent = Hex.decodeHex(digestSignature); /* 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 digest 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 } */ pkcs1PublicKeyBytes = getPublicKey(digestFile.getString("digestPublicKeyFingerprint"))); // 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(dataToSign.getBytes("UTF-8")); if (signature.verify(signatureContent)) { System.out.println("Digest file signature is valid, validating log files…"); for (int i = 0; i < digestFile.getJSONArray("logFiles").length(); i++) { JSONObject logFileMetadata = digestFile.getJSONArray("logFiles").getJSONObject(i); // Compute log file hash byte[] logFileContent = loadUncompressedLogFileInMemory( logFileMetadata.getString("s3Bucket"), logFileMetadata.getString("s3Object") ); messageDigest.update(logFileContent); byte[] logFileHash = messageDigest.digest(); messageDigest.reset(); // Retrieve expected hash for the log file being processed byte[] expectedHash = Hex.decodeHex(logFileMetadata.getString("hashValue")); boolean signaturesMatch = Arrays.equals(expectedHash, logFileHash); if (!signaturesMatch) { System.err.println(String.format("Log file: %s/%s hash doesn't match.\tExpected: %s Actual: %s", logFileMetadata.getString("s3Bucket"), logFileMetadata.getString("s3Object"), Hex.encodeHexString(expectedHash), Hex.encodeHexString(logFileHash))); } else { System.out.println(String.format("Log file: %s/%s hash match", logFileMetadata.getString("s3Bucket"), logFileMetadata.getString("s3Object"))); } } } else { System.err.println("Digest signature failed validation."); } System.out.println("Digest file validation completed."); if (chainValidationIsEnabled()) { // This enables the digests' chain validation validateDigestFile( digestFile.getString("previousDigestS3Bucket"), digestFile.getString("previousDigestS3Object"), digestFile.getString("previousDigestSignature")); } } } }