100 lines
3.5 KiB
C#
100 lines
3.5 KiB
C#
namespace StellaOps.AuditPack.Services;
|
|
|
|
public sealed partial class AuditBundleReader
|
|
{
|
|
/// <summary>
|
|
/// Reads and verifies an audit bundle.
|
|
/// </summary>
|
|
public async Task<AuditBundleReadResult> ReadAsync(
|
|
AuditBundleReadRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(request.BundlePath);
|
|
|
|
if (!File.Exists(request.BundlePath))
|
|
{
|
|
return AuditBundleReadResult.Failed("Bundle file not found");
|
|
}
|
|
|
|
var tempDir = CreateTempDir("audit-read");
|
|
|
|
try
|
|
{
|
|
await ExtractBundleAsync(request.BundlePath, tempDir, cancellationToken).ConfigureAwait(false);
|
|
|
|
var manifestLoad = await LoadManifestAsync(tempDir, cancellationToken).ConfigureAwait(false);
|
|
if (!manifestLoad.Success || manifestLoad.Manifest is null || manifestLoad.ManifestBytes is null)
|
|
{
|
|
return AuditBundleReadResult.Failed(manifestLoad.Error ?? "Failed to parse manifest");
|
|
}
|
|
|
|
var result = new AuditBundleReadResult
|
|
{
|
|
Success = true,
|
|
Manifest = manifestLoad.Manifest,
|
|
BundleDigest = await ComputeFileDigestAsync(request.BundlePath, cancellationToken).ConfigureAwait(false),
|
|
ExtractedPath = request.ExtractToPath is not null ? null : tempDir
|
|
};
|
|
|
|
var signature = await ApplySignatureVerificationAsync(
|
|
result,
|
|
request,
|
|
manifestLoad.ManifestBytes,
|
|
tempDir,
|
|
cancellationToken)
|
|
.ConfigureAwait(false);
|
|
if (signature.Stop)
|
|
{
|
|
return signature.Result;
|
|
}
|
|
result = signature.Result;
|
|
var merkle = await ApplyMerkleVerificationAsync(
|
|
result,
|
|
request,
|
|
manifestLoad.Manifest,
|
|
tempDir,
|
|
cancellationToken)
|
|
.ConfigureAwait(false);
|
|
if (merkle.Stop)
|
|
{
|
|
return merkle.Result;
|
|
}
|
|
result = merkle.Result;
|
|
var digests = await ApplyInputDigestVerificationAsync(
|
|
result,
|
|
request,
|
|
manifestLoad.Manifest,
|
|
tempDir,
|
|
cancellationToken)
|
|
.ConfigureAwait(false);
|
|
if (digests.Stop)
|
|
{
|
|
return digests.Result;
|
|
}
|
|
result = digests.Result;
|
|
var extraction = HandleExtraction(result, request, tempDir);
|
|
if (!extraction.Success)
|
|
{
|
|
return extraction.Result with { Success = false, Error = extraction.Error };
|
|
}
|
|
|
|
result = extraction.Result;
|
|
tempDir = extraction.TempDir;
|
|
if (request.LoadReplayInputs)
|
|
{
|
|
var extractPath = result.ExtractedPath ?? tempDir;
|
|
var inputs = await LoadReplayInputsAsync(extractPath, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
result = result with { ReplayInputs = inputs };
|
|
}
|
|
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return AuditBundleReadResult.Failed($"Failed to read bundle: {ex.Message}");
|
|
}
|
|
}
|
|
}
|