293 lines
8.6 KiB
Markdown
293 lines
8.6 KiB
Markdown
# 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)
|