docs consolidation
This commit is contained in:
292
docs/modules/attestor/dsse-roundtrip-verification.md
Normal file
292
docs/modules/attestor/dsse-roundtrip-verification.md
Normal file
@@ -0,0 +1,292 @@
|
||||
# DSSE Round-Trip Verification
|
||||
|
||||
This document describes the DSSE (Dead Simple Signing Envelope) round-trip verification process in StellaOps, including bundling, offline verification, and cosign compatibility.
|
||||
|
||||
## Overview
|
||||
|
||||
DSSE round-trip verification ensures that attestations can be:
|
||||
1. Created and signed
|
||||
2. Serialized to persistent storage
|
||||
3. Deserialized and rebundled
|
||||
4. Verified offline without network access
|
||||
5. Verified by external tools (cosign)
|
||||
|
||||
## Round-Trip Process
|
||||
|
||||
### Standard Flow
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ Payload │───>│ Sign │───>│ DSSE │
|
||||
│ (in-toto) │ │ │ │ Envelope │
|
||||
└─────────────┘ └──────────────┘ └──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ Verify │<───│ Deserialize │<───│ Bundle │
|
||||
│ (Pass) │ │ │ │ (JSON) │
|
||||
└──────┬──────┘ └──────────────┘ └─────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ Re-bundle │───>│ Serialize │───>│ Archive │
|
||||
│ │ │ │ │ (.tar.gz) │
|
||||
└──────┬──────┘ └──────────────┘ └─────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Re-verify │
|
||||
│ (Pass) │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Create Payload**: Build an in-toto statement with subject digests and predicate
|
||||
2. **Sign**: Create DSSE envelope with signature(s)
|
||||
3. **Bundle**: Wrap envelope in Sigstore-compatible bundle format
|
||||
4. **Serialize**: Convert to JSON bytes
|
||||
5. **Deserialize**: Parse JSON back to bundle structure
|
||||
6. **Extract**: Get DSSE envelope from bundle
|
||||
7. **Re-bundle**: Create new bundle from extracted envelope
|
||||
8. **Re-verify**: Confirm signature validity
|
||||
|
||||
## Verification Types
|
||||
|
||||
### Signature Verification
|
||||
|
||||
Verifies the cryptographic signature against the payload:
|
||||
|
||||
```csharp
|
||||
var result = await signatureService.VerifyAsync(envelope, cancellationToken);
|
||||
if (!result.IsValid)
|
||||
{
|
||||
throw new VerificationException(result.Error);
|
||||
}
|
||||
```
|
||||
|
||||
### Payload Integrity
|
||||
|
||||
Verifies the payload hash matches the signed content:
|
||||
|
||||
```csharp
|
||||
var computedHash = SHA256.HashData(envelope.Payload.Span);
|
||||
var declaredHash = ParseDigest(envelope.PayloadDigest);
|
||||
if (!computedHash.SequenceEqual(declaredHash))
|
||||
{
|
||||
throw new IntegrityException("Payload hash mismatch");
|
||||
}
|
||||
```
|
||||
|
||||
### Certificate Chain Validation
|
||||
|
||||
For keyless (Fulcio) signatures:
|
||||
|
||||
```csharp
|
||||
var chain = envelope.Signatures[0].CertificateChain;
|
||||
var result = await certificateValidator.ValidateAsync(chain, options);
|
||||
// Checks: expiry, revocation, issuer, extended key usage
|
||||
```
|
||||
|
||||
## Determinism Requirements
|
||||
|
||||
Round-trip verification requires deterministic serialization:
|
||||
|
||||
| Property | Requirement |
|
||||
|----------|-------------|
|
||||
| Key Order | Alphabetical |
|
||||
| Whitespace | No trailing whitespace, single space after colons |
|
||||
| Encoding | UTF-8, no BOM |
|
||||
| Numbers | No unnecessary trailing zeros |
|
||||
| Arrays | Stable ordering |
|
||||
|
||||
### Verification
|
||||
|
||||
```csharp
|
||||
var bytes1 = CanonJson.CanonicalizeVersioned(envelope);
|
||||
var envelope2 = JsonSerializer.Deserialize<DsseEnvelope>(bytes1);
|
||||
var bytes2 = CanonJson.CanonicalizeVersioned(envelope2);
|
||||
|
||||
// bytes1 and bytes2 must be identical
|
||||
Assert.Equal(bytes1.ToArray(), bytes2.ToArray());
|
||||
```
|
||||
|
||||
## Multi-Signature Support
|
||||
|
||||
DSSE envelopes can contain multiple signatures from different signers:
|
||||
|
||||
```csharp
|
||||
var envelope = new DsseEnvelope(
|
||||
payloadType: "application/vnd.in-toto+json",
|
||||
payload: canonicalPayload,
|
||||
signatures:
|
||||
[
|
||||
signerA.Sign(canonicalPayload), // Builder signature
|
||||
signerB.Sign(canonicalPayload), // Witness signature
|
||||
signerC.Sign(canonicalPayload) // Approver signature
|
||||
]);
|
||||
```
|
||||
|
||||
Verification checks all signatures:
|
||||
|
||||
```csharp
|
||||
foreach (var signature in envelope.Signatures)
|
||||
{
|
||||
var result = await VerifySignatureAsync(envelope.Payload, signature);
|
||||
if (!result.IsValid)
|
||||
{
|
||||
return VerificationResult.Failure(
|
||||
$"Signature {signature.KeyId} failed: {result.Error}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Cosign Compatibility
|
||||
|
||||
StellaOps DSSE envelopes are compatible with Sigstore cosign for verification.
|
||||
|
||||
### Envelope Format
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.in-toto+json",
|
||||
"payload": "<base64-encoded-payload>",
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "SHA256:abc123...",
|
||||
"sig": "<base64-encoded-signature>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Cosign Verification Commands
|
||||
|
||||
See [Cosign Verification Examples](./cosign-verification-examples.md) for detailed commands.
|
||||
|
||||
## Offline Verification
|
||||
|
||||
For air-gapped environments, verification can proceed without network access:
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Trusted Root**: Pre-downloaded Sigstore TUF root
|
||||
2. **Cached Certificates**: Fulcio certificate chain
|
||||
3. **Rekor Entry** (optional): Cached transparency log entry
|
||||
|
||||
### Offline Flow
|
||||
|
||||
```csharp
|
||||
var verifier = new OfflineVerifier(
|
||||
trustedRoot: TrustedRoot.LoadFromFile("trusted_root.json"),
|
||||
rekorEntries: RekorCache.LoadFromDirectory("rekor-cache/"));
|
||||
|
||||
var result = await verifier.VerifyAsync(envelope);
|
||||
```
|
||||
|
||||
### Bundle for Offline Use
|
||||
|
||||
```bash
|
||||
# Export attestation bundle with all dependencies
|
||||
stellaops export attestation-bundle \
|
||||
--artifact sha256:abc123... \
|
||||
--include-certificate-chain \
|
||||
--include-rekor-entry \
|
||||
--output bundle.tar.gz
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Verification Failures
|
||||
|
||||
| Error | Cause | Resolution |
|
||||
|-------|-------|------------|
|
||||
| `SignatureInvalid` | Signature doesn't match payload | Re-sign with correct key |
|
||||
| `CertificateExpired` | Signing certificate expired | Use Rekor entry timestamp |
|
||||
| `PayloadTampered` | Payload modified after signing | Restore original payload |
|
||||
| `KeyNotTrusted` | Key not in trusted set | Add key to trust policy |
|
||||
| `ParseError` | Malformed envelope JSON | Validate envelope format |
|
||||
|
||||
### Example Error Handling
|
||||
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
var result = await verifier.VerifyAsync(envelope);
|
||||
if (!result.IsValid)
|
||||
{
|
||||
logger.LogWarning("Verification failed: {Reason}", result.FailureReason);
|
||||
// Handle policy violation
|
||||
}
|
||||
}
|
||||
catch (CertificateExpiredException ex)
|
||||
{
|
||||
// Fall back to Rekor timestamp verification
|
||||
var result = await verifier.VerifyWithRekorTimestampAsync(
|
||||
envelope, ex.Certificate, rekorEntry);
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to parse envelope");
|
||||
throw new VerificationException("Malformed envelope", ex);
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Round-Trip Verification
|
||||
|
||||
### Unit Test Example
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public async Task RoundTrip_SignVerifyRebundle_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var payload = CreateInTotoStatement();
|
||||
var signer = CreateTestSigner();
|
||||
|
||||
// Act - Sign
|
||||
var envelope = await signer.SignAsync(payload);
|
||||
|
||||
// Act - Serialize and deserialize
|
||||
var json = JsonSerializer.Serialize(envelope);
|
||||
var restored = JsonSerializer.Deserialize<DsseEnvelope>(json);
|
||||
|
||||
// Act - Verify restored envelope
|
||||
var result = await signer.VerifyAsync(restored);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
restored.Payload.ToArray().Should().Equal(envelope.Payload.ToArray());
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Test Example
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public async Task RoundTrip_ArchiveExtractVerify_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var envelope = await CreateSignedEnvelope();
|
||||
var archive = new AttestationArchive();
|
||||
|
||||
// Act - Archive
|
||||
var archivePath = Path.GetTempFileName() + ".tar.gz";
|
||||
await archive.WriteAsync(envelope, archivePath);
|
||||
|
||||
// Act - Extract and verify
|
||||
var extracted = await archive.ReadAsync(archivePath);
|
||||
var result = await verifier.VerifyAsync(extracted);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Proof Chain Specification](./proof-chain-specification.md)
|
||||
- [Graph Root Attestation](./graph-root-attestation.md)
|
||||
- [Transparency Log Integration](./transparency.md)
|
||||
- [Air-Gap Operation](./airgap.md)
|
||||
- [Cosign Verification Examples](./cosign-verification-examples.md)
|
||||
Reference in New Issue
Block a user