Skip to content

Commit af496ac

Browse files
committed
Add full object checksum validation for Transfer Manager downloads
1 parent c9374f0 commit af496ac

File tree

2 files changed

+104
-0
lines changed

2 files changed

+104
-0
lines changed

src/aws-cpp-sdk-transfer/include/aws/transfer/TransferManager.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,13 @@ namespace Aws
338338
*/
339339
void SetChecksumForAlgorithm(const std::shared_ptr<PartState>& state, Aws::S3::Model::CompletedPart& part);
340340

341+
/**
342+
* Validates downloaded file checksum against expected checksum from HeadObject.
343+
* @param handle The transfer handle containing checksum and file path.
344+
* @return True if validation passes or no checksum to validate, false if validation fails.
345+
*/
346+
bool ValidateDownloadChecksum(const std::shared_ptr<TransferHandle>& handle);
347+
341348
static Aws::String DetermineFilePath(const Aws::String& directory, const Aws::String& prefix, const Aws::String& keyName);
342349

343350
Aws::Utils::ExclusiveOwnershipResourceManager<unsigned char*> m_bufferManager;

src/aws-cpp-sdk-transfer/source/transfer/TransferManager.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,21 @@ namespace Aws
938938
handle->SetContentType(getObjectOutcome.GetResult().GetContentType());
939939
handle->ChangePartToCompleted(partState, getObjectOutcome.GetResult().GetETag());
940940
getObjectOutcome.GetResult().GetBody().flush();
941+
942+
if (!ValidateDownloadChecksum(handle)) {
943+
Aws::Client::AWSError<Aws::S3::S3Errors> checksumError(
944+
Aws::S3::S3Errors::INTERNAL_FAILURE,
945+
"ChecksumMismatch",
946+
"Downloaded file checksum does not match expected checksum",
947+
false);
948+
handle->ChangePartToFailed(partState);
949+
handle->UpdateStatus(TransferStatus::FAILED);
950+
handle->SetError(checksumError);
951+
TriggerErrorCallback(handle, checksumError);
952+
TriggerTransferStatusUpdatedCallback(handle);
953+
return;
954+
}
955+
941956
handle->UpdateStatus(TransferStatus::COMPLETED);
942957
}
943958
else
@@ -1000,6 +1015,21 @@ namespace Aws
10001015
handle->SetContentType(headObjectOutcome.GetResult().GetContentType());
10011016
handle->SetMetadata(headObjectOutcome.GetResult().GetMetadata());
10021017
handle->SetEtag(headObjectOutcome.GetResult().GetETag());
1018+
1019+
if (headObjectOutcome.GetResult().GetChecksumType() == S3::Model::ChecksumType::FULL_OBJECT) {
1020+
if (!headObjectOutcome.GetResult().GetChecksumCRC32().empty()) {
1021+
handle->SetChecksum(headObjectOutcome.GetResult().GetChecksumCRC32());
1022+
} else if (!headObjectOutcome.GetResult().GetChecksumCRC32C().empty()) {
1023+
handle->SetChecksum(headObjectOutcome.GetResult().GetChecksumCRC32C());
1024+
} else if (!headObjectOutcome.GetResult().GetChecksumSHA256().empty()) {
1025+
handle->SetChecksum(headObjectOutcome.GetResult().GetChecksumSHA256());
1026+
} else if (!headObjectOutcome.GetResult().GetChecksumSHA1().empty()) {
1027+
handle->SetChecksum(headObjectOutcome.GetResult().GetChecksumSHA1());
1028+
} else if (!headObjectOutcome.GetResult().GetChecksumCRC64NVME().empty()) {
1029+
handle->SetChecksum(headObjectOutcome.GetResult().GetChecksumCRC64NVME());
1030+
}
1031+
}
1032+
10031033
/* When bucket versioning is suspended, head object will return "null" for unversioned object.
10041034
* Send following GetObject with "null" as versionId will result in 403 access denied error if your IAM role or policy
10051035
* doesn't have GetObjectVersion permission.
@@ -1240,6 +1270,22 @@ namespace Aws
12401270
if (failedParts.size() == 0 && handle->GetBytesTransferred() == handle->GetBytesTotalSize())
12411271
{
12421272
outcome.GetResult().GetBody().flush();
1273+
1274+
// Validate checksum if available
1275+
if (!ValidateDownloadChecksum(handle)) {
1276+
Aws::Client::AWSError<Aws::S3::S3Errors> checksumError(
1277+
Aws::S3::S3Errors::INTERNAL_FAILURE,
1278+
"ChecksumMismatch",
1279+
"Downloaded file checksum does not match expected checksum",
1280+
false);
1281+
handle->UpdateStatus(TransferStatus::FAILED);
1282+
handle->SetError(checksumError);
1283+
TriggerErrorCallback(handle, checksumError);
1284+
TriggerTransferStatusUpdatedCallback(handle);
1285+
partState->SetDownloadPartStream(nullptr);
1286+
return;
1287+
}
1288+
12431289
handle->UpdateStatus(TransferStatus::COMPLETED);
12441290
}
12451291
else
@@ -1594,5 +1640,56 @@ namespace Aws
15941640
partFunc->second(part, state->GetChecksum());
15951641
}
15961642
}
1643+
1644+
bool TransferManager::ValidateDownloadChecksum(const std::shared_ptr<TransferHandle>& handle) {
1645+
if (handle->GetChecksum().empty() || handle->GetTargetFilePath().empty()) {
1646+
return true;
1647+
}
1648+
1649+
std::shared_ptr<Aws::Utils::Crypto::Hash> hashCalculator;
1650+
if (m_transferConfig.checksumAlgorithm == S3::Model::ChecksumAlgorithm::CRC32) {
1651+
hashCalculator = Aws::MakeShared<Aws::Utils::Crypto::CRC32>("TransferManager");
1652+
} else if (m_transferConfig.checksumAlgorithm == S3::Model::ChecksumAlgorithm::CRC32C) {
1653+
hashCalculator = Aws::MakeShared<Aws::Utils::Crypto::CRC32C>("TransferManager");
1654+
} else if (m_transferConfig.checksumAlgorithm == S3::Model::ChecksumAlgorithm::SHA1) {
1655+
hashCalculator = Aws::MakeShared<Aws::Utils::Crypto::Sha1>("TransferManager");
1656+
} else if (m_transferConfig.checksumAlgorithm == S3::Model::ChecksumAlgorithm::SHA256) {
1657+
hashCalculator = Aws::MakeShared<Aws::Utils::Crypto::Sha256>("TransferManager");
1658+
} else {
1659+
hashCalculator = Aws::MakeShared<Aws::Utils::Crypto::CRC64>("TransferManager");
1660+
}
1661+
1662+
auto fileStream = Aws::MakeShared<Aws::FStream>("TransferManager",
1663+
handle->GetTargetFilePath().c_str(),
1664+
std::ios_base::in | std::ios_base::binary);
1665+
1666+
if (!fileStream->good()) {
1667+
AWS_LOGSTREAM_ERROR(CLASS_TAG, "Transfer handle [" << handle->GetId()
1668+
<< "] Failed to open downloaded file for checksum validation: " << handle->GetTargetFilePath());
1669+
return false;
1670+
}
1671+
1672+
unsigned char buffer[8192];
1673+
while (fileStream->read(reinterpret_cast<char*>(buffer), sizeof(buffer)) || fileStream->gcount() > 0) {
1674+
hashCalculator->Update(buffer, static_cast<size_t>(fileStream->gcount()));
1675+
}
1676+
1677+
auto hashResult = hashCalculator->GetHash();
1678+
if (!hashResult.IsSuccess()) {
1679+
AWS_LOGSTREAM_ERROR(CLASS_TAG, "Transfer handle [" << handle->GetId()
1680+
<< "] Failed to calculate checksum for downloaded file");
1681+
return false;
1682+
}
1683+
1684+
Aws::String calculatedChecksum = Aws::Utils::HashingUtils::Base64Encode(hashResult.GetResult());
1685+
if (calculatedChecksum != handle->GetChecksum()) {
1686+
AWS_LOGSTREAM_ERROR(CLASS_TAG, "Transfer handle [" << handle->GetId()
1687+
<< "] Checksum validation failed. Expected: " << handle->GetChecksum()
1688+
<< ", Calculated: " << calculatedChecksum);
1689+
return false;
1690+
}
1691+
1692+
return true;
1693+
}
15971694
}
15981695
}

0 commit comments

Comments
 (0)