2019-01-24 21:23:21 +00:00
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// snippet-start:[cloudtrail.java.log-and-digest-file-validation.complete]
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 ;
2024-01-16 10:41:11 -05:00
2019-01-24 21:23:21 +00:00
public class DigestFileValidator {
2024-01-16 10:41:11 -05:00
2019-01-24 21:23:21 +00:00
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 " ) ) ;
}
}
}
}
// snippet-end:[cloudtrail.java.log-and-digest-file-validation.complete]