CloudTrail 로그 파일 무결성 검증에 대한 사용자 지정 구현 - AWS CloudTrail

CloudTrail 로그 파일 무결성 검증에 대한 사용자 지정 구현

CloudTrail은 업계 표준의 공개적으로 제공되는 암호화 알고리즘 및 해시 함수를 사용하므로 고유한 도구를 생성하여 CloudTrail 로그 파일의 무결성을 검증할 수 있습니다. 로그 파일 무결성 검증이 활성화되면 CloudTrail이 Amazon S3 버킷으로 다이제스트 파일을 전송합니다. 이 파일을 사용하여 고유한 검증 솔루션을 구현할 수 있습니다. 다이제스트 파일에 대한 자세한 내용은 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 class(예)를 사용하여 Amazon S3 버킷에서 검증할 시간 범위에 해당하는 가장 최근 다이제스트 파일을 가져옵니다.

  2. 파일을 검색하는 데 사용된 S3 버킷 및 S3 객체가 다이제스트 파일 자체에 기록된 S3 버킷 S3 객체 위치와 일치하는지 확인합니다.

  3. 그런 다음, Amazon S3에 있는 다이제스트 파일 객체의 x-amz-meta-signature 메타데이터 속성에서 다이제스트 파일의 디지털 서명을 가져옵니다.

  4. 다이제스트 파일의 digestPublicKeyFingerprint 필드에서 다이제스트 파일에 서명하는 데 사용된 프라이빗 키에 대한 퍼블릭 키의 지문을 가져옵니다.

B. 다이제스트 파일 검증을 위한 퍼블릭 키 검색

AWS CLI 또는 CloudTrail API를 사용하여 다이제스트 파일을 검증하기 위한 퍼블릭 키를 가져올 수 있습니다. 어느 방법을 사용하든 다이제스트 파일에 대해 검증할 시간 범위(시작 시간 및 종료 시간)를 지정합니다. 지정한 시간 범위에 대해 하나 이상의 퍼블릭 키가 반환될 수 있습니다. 반환된 키의 유효 시간 범위가 겹칠 수 있습니다.

참고

CloudTrail은 리전별로 서로 다른 프라이빗/퍼블릭 키 페어를 사용하므로 각 다이제스트 파일은 해당 리전에 고유한 프라이빗 키로 서명됩니다. 따라서 특정 리전의 다이제스트 파일을 검증할 때는 동일한 리전의 퍼블릭 키를 검색해야 합니다.

AWS CLI를 사용하여 퍼블릭 키 검색

AWS CLI를 사용하여 다이제스트 파일의 퍼블릭 키를 검색하려면 cloudtrail list-public-keys 명령을 사용합니다. 명령의 형식은 다음과 같습니다.

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

시작 시간 및 종료 시간 파라미터는 UTC 타임스탬프이며 선택 사항입니다. 지정하지 않을 경우 현재 시간이 사용되며 현재 활성 상태인 퍼블릭 키가 반환됩니다.

샘플 응답

응답은 반환된 키를 나타내는 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

PKCS #1 형식의 DER 인코딩 퍼블릭 키 값입니다.

유형: 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.s3BucketlogFiles.s3Object 필드의 S3 위치 정보를 사용하여 로그 파일의 S3 Get을 수행합니다.

  2. S3 Get 작업이 성공하면 다이제스트 파일의 logFiles 어레이에 나열된 로그 파일 전체에 대해 이 작업을 반복합니다.

    1. 다이제스트 파일에 있는 해당 로그의 logFiles.hashValue 필드에서 파일의 원래 해시를 검색합니다.

    2. logFiles.hashAlgorithm에 지정된 해시 알고리즘을 사용하여 압축되지 않은 상태의 로그 파일 내용을 해시합니다.

    3. 생성한 해시 값을 다이제스트 파일에 있는 로그에 대한 해시 값과 비교합니다. 해시가 서로 일치하면 로그 파일이 유효한 것입니다.

G. 추가 다이제스트 및 로그 파일 검증

각 다이제스트 파일에서 다음 필드는 이전 다이제스트 파일의 위치 및 서명을 제공합니다.

  • previousDigestS3Bucket

  • previousDigestS3Object

  • previousDigestSignature

이 정보를 사용하여 이전 다이제스트 파일을 순차적으로 검토합니다. 이전 단원의 단계를 사용하여 각 다이제스트 파일의 서명 및 다이제스트 파일이 참조하는 로그 파일을 검증합니다. 이전 다이제스트 파일의 경우 다이제스트 파일 객체의 Amazon S3 메타데이터 속성에서 디지털 서명을 검색할 필요가 없다는 점만 다릅니다. 이전 다이제스트 파일의 서명은 previousDigestSignature 필드에 기본 제공되어 있습니다.

시작 다이제스트 파일에 도달할 때까지 또는 다이제스트 파일 체인이 끊어질 때까지 되돌아갈 수 있습니다.

오프라인으로 다이제스트 및 로그 파일 검증

다이제스트 및 로그 파일을 오프라인으로 검증할 때 일반적으로 이전 단원에 설명된 절차를 따르면 되지만, 다음과 같은 사항을 고려해야 합니다.

가장 최근 다이제스트 파일 처리

가장 최근("현재") 다이제스트 파일의 디지털 서명은 다이제스트 파일 객체의 Amazon S3 메타데이터 속성에 있습니다. 오프라인 시나리오에서는 현재 다이제스트 파일의 디지털 서명을 사용할 수 없습니다.

이 문제를 처리하기 위한 두 가지 가능한 방법은 다음과 같습니다.

  • 이전 다이제스트 파일에 대한 디지털 서명이 현재 다이제스트 파일에 있으므로 끝에서 두 번째 다이제스트 파일부터 검증을 시작합니다. 이 방법을 사용할 경우 가장 최근 다이제스트 파일을 검증할 수 없습니다.

  • 예비 단계로, 다이제스트 파일 객체의 메타데이터 속성에서 현재 다이제스트 파일의 서명을 가져온(예: Amazon S3 getObjectMetadata API 호출을 통해) 다음, 오프라인에 안전하게 저장합니다. 이렇게 하면 체인의 이전 파일과 함께 현재 다이제스트 파일을 검증할 수 있습니다.

경로 확인

다운로드된 다이제스트 파일에 있는 필드(예: 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")); } } } }