up
This commit is contained in:
@@ -235,6 +235,10 @@ internal sealed class AttestorSigningKeyRegistry : IDisposable
|
||||
}
|
||||
|
||||
var privateKeyBytes = LoadSm2KeyBytes(key);
|
||||
var metadata = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["source"] = "config"
|
||||
};
|
||||
var signingKey = new CryptoSigningKey(
|
||||
new CryptoKeyReference(providerKeyId, providerName),
|
||||
normalizedAlgorithm,
|
||||
|
||||
@@ -120,6 +120,95 @@ public sealed class AttestorVerificationServiceTests
|
||||
Assert.Equal("missing", verifyResult.Report.Transparency.WitnessStatus);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VerifyAsync_KmsBundle_Passes_WhenTwoSignaturesRequired()
|
||||
{
|
||||
var options = Options.Create(new AttestorOptions
|
||||
{
|
||||
Redis = new AttestorOptions.RedisOptions { Url = string.Empty },
|
||||
Rekor = new AttestorOptions.RekorOptions
|
||||
{
|
||||
Primary = new AttestorOptions.RekorBackendOptions
|
||||
{
|
||||
Url = "https://rekor.stellaops.test",
|
||||
ProofTimeoutMs = 1000,
|
||||
PollIntervalMs = 50,
|
||||
MaxAttempts = 2
|
||||
}
|
||||
},
|
||||
Security = new AttestorOptions.SecurityOptions
|
||||
{
|
||||
SignerIdentity = new AttestorOptions.SignerIdentityOptions
|
||||
{
|
||||
Mode = { "kms" },
|
||||
KmsKeys = { HmacSecretBase64 }
|
||||
}
|
||||
},
|
||||
Verification = new AttestorOptions.VerificationOptions
|
||||
{
|
||||
MinimumSignatures = 2,
|
||||
PolicyId = "policy/dual-sign"
|
||||
}
|
||||
});
|
||||
|
||||
using var metrics = new AttestorMetrics();
|
||||
using var activitySource = new AttestorActivitySource();
|
||||
var canonicalizer = new DefaultDsseCanonicalizer();
|
||||
var engine = new AttestorVerificationEngine(canonicalizer, new TestCryptoHash(), options, NullLogger<AttestorVerificationEngine>.Instance);
|
||||
var repository = new InMemoryAttestorEntryRepository();
|
||||
var dedupeStore = new InMemoryAttestorDedupeStore();
|
||||
var rekorClient = new StubRekorClient(new NullLogger<StubRekorClient>());
|
||||
var archiveStore = new NullAttestorArchiveStore(new NullLogger<NullAttestorArchiveStore>());
|
||||
var auditSink = new InMemoryAttestorAuditSink();
|
||||
var submissionService = new AttestorSubmissionService(
|
||||
new AttestorSubmissionValidator(canonicalizer),
|
||||
repository,
|
||||
dedupeStore,
|
||||
rekorClient,
|
||||
new NullTransparencyWitnessClient(),
|
||||
archiveStore,
|
||||
auditSink,
|
||||
new NullVerificationCache(),
|
||||
options,
|
||||
new NullLogger<AttestorSubmissionService>(),
|
||||
TimeProvider.System,
|
||||
metrics);
|
||||
|
||||
var submission = CreateSubmissionRequestWithTwoSignatures(canonicalizer, HmacSecret);
|
||||
var context = new SubmissionContext
|
||||
{
|
||||
CallerSubject = "urn:stellaops:signer",
|
||||
CallerAudience = "attestor",
|
||||
CallerClientId = "signer-service",
|
||||
CallerTenant = "default"
|
||||
};
|
||||
|
||||
var response = await submissionService.SubmitAsync(submission, context);
|
||||
|
||||
var verificationService = new AttestorVerificationService(
|
||||
repository,
|
||||
canonicalizer,
|
||||
rekorClient,
|
||||
new NullTransparencyWitnessClient(),
|
||||
engine,
|
||||
options,
|
||||
new NullLogger<AttestorVerificationService>(),
|
||||
metrics,
|
||||
activitySource,
|
||||
TimeProvider.System);
|
||||
|
||||
var verifyResult = await verificationService.VerifyAsync(new AttestorVerificationRequest
|
||||
{
|
||||
Uuid = response.Uuid,
|
||||
Bundle = submission.Bundle
|
||||
});
|
||||
|
||||
Assert.True(verifyResult.Ok);
|
||||
Assert.Equal(VerificationSectionStatus.Pass, verifyResult.Report!.Signatures.Status);
|
||||
Assert.Equal(2, verifyResult.Report.Signatures.VerifiedSignatures);
|
||||
Assert.Equal(2, verifyResult.Report.Signatures.RequiredSignatures);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VerifyAsync_FlagsTamperedBundle()
|
||||
{
|
||||
@@ -262,6 +351,32 @@ public sealed class AttestorVerificationServiceTests
|
||||
return request;
|
||||
}
|
||||
|
||||
private static AttestorSubmissionRequest CreateSubmissionRequestWithTwoSignatures(DefaultDsseCanonicalizer canonicalizer, byte[] hmacSecret)
|
||||
{
|
||||
var request = CreateSubmissionRequest(canonicalizer, hmacSecret);
|
||||
|
||||
// Recompute signature and append a second copy to satisfy multi-signature verification
|
||||
if (!TryDecodeBase64(request.Bundle.Dsse.PayloadBase64, out var payload))
|
||||
{
|
||||
throw new InvalidOperationException("Test payload failed to decode.");
|
||||
}
|
||||
|
||||
var preAuth = ComputePreAuthEncodingForTests(request.Bundle.Dsse.PayloadType, payload);
|
||||
using (var hmac = new HMACSHA256(hmacSecret))
|
||||
{
|
||||
var signature = hmac.ComputeHash(preAuth);
|
||||
request.Bundle.Dsse.Signatures.Add(new AttestorSubmissionRequest.DsseSignature
|
||||
{
|
||||
KeyId = "kms-test-2",
|
||||
Signature = Convert.ToBase64String(signature)
|
||||
});
|
||||
}
|
||||
|
||||
var canonical = canonicalizer.CanonicalizeAsync(request).GetAwaiter().GetResult();
|
||||
request.Meta.BundleSha256 = Convert.ToHexString(SHA256.HashData(canonical)).ToLowerInvariant();
|
||||
return request;
|
||||
}
|
||||
|
||||
private static AttestorSubmissionRequest.SubmissionBundle CloneBundle(AttestorSubmissionRequest.SubmissionBundle source)
|
||||
{
|
||||
var clone = new AttestorSubmissionRequest.SubmissionBundle
|
||||
|
||||
Reference in New Issue
Block a user