diff --git a/Extract-NTHashes.ps1 b/Extract-NTHashes.ps1 index 0c24c93..333a3b9 100644 --- a/Extract-NTHashes.ps1 +++ b/Extract-NTHashes.ps1 @@ -93,6 +93,23 @@ function Invoke-S3GetToFile([string]$endpointUrl, [string]$bucket, [string]$key, } finally { if ($req) { $req.Dispose() }; $client.Dispose() } } +function Invoke-S3DeleteFile([string]$endpointUrl, [string]$bucket, [string]$key, [string]$region, [string]$ak, [string]$sk, [bool]$forcePathStyle) { + $uri = BuildS3Uri -endpointUrl $endpointUrl -bucket $bucket -key $key -forcePathStyle $forcePathStyle + $payloadHash = (Get-HashHex (Get-Bytes '')) + Add-Type -AssemblyName System.Net.Http -ErrorAction SilentlyContinue + $client = [System.Net.Http.HttpClient]::new() + try { + $req = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::Delete, $uri) + $hdrs = BuildAuthHeaders -method 'DELETE' -uri $uri -region $region -accessKey $ak -secretKey $sk -payloadHash $payloadHash + $req.Headers.TryAddWithoutValidation('x-amz-date', $hdrs['x-amz-date']) | Out-Null + $req.Headers.TryAddWithoutValidation('Authorization', $hdrs['Authorization']) | Out-Null + $req.Headers.TryAddWithoutValidation('x-amz-content-sha256', $hdrs['x-amz-content-sha256']) | Out-Null + $resp = $client.SendAsync($req).Result + # S3 DELETE is idempotent and returns 204 even if the key never existed; anything else is a real failure. + if (-not $resp.IsSuccessStatusCode) { throw "S3 DELETE failed: $([int]$resp.StatusCode) $($resp.ReasonPhrase)" } + } finally { if ($req) { $req.Dispose() }; $client.Dispose() } +} + function Protect-FileWithAES { param ( [Parameter(Mandatory = $true)] @@ -355,6 +372,25 @@ try { if ($tempDownloadPath -and (Test-Path $tempDownloadPath)) { Remove-Item -Path $tempDownloadPath -Force -ErrorAction SilentlyContinue } + + # A checksum mismatch means the blob already sitting in remote storage under $blobName + # is corrupt/incomplete. Leaving it live under its normal name would let a downstream + # consumer silently fetch bad data, so remove it rather than only warning locally. + try { + if ($storageProvider -ieq 'S3') { + if ($usedAwsTools -and $s3Client) { + $delReq = New-Object Amazon.S3.Model.DeleteObjectRequest -Property @{ BucketName = $s3BucketName; Key = $blobName } + $null = $s3Client.DeleteObject($delReq) + } else { + Invoke-S3DeleteFile -endpointUrl $s3EndpointUrl -bucket $s3BucketName -key $blobName -region $s3Region -ak $s3AccessKeyId -sk $s3SecretAccessKey -forcePathStyle:$s3ForcePathStyle + } + } else { + Remove-AzStorageBlob -Blob $blobName -Container $containerName -Context $storageContext -Force -ErrorAction Stop + } + Write-Warning "Removed the mismatched blob '$blobName' from remote storage." + } catch { + Write-Warning "Could not remove the mismatched blob '$blobName' from remote storage - remove it manually: $($_.Exception.Message)" + } } } finally { # Always delete plaintext hashes and compressed archive regardless of outcome.