# 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(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": "", "signatures": [ { "keyid": "SHA256:abc123...", "sig": "" } ] } ``` ### 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(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)