# Build-Time DSSE Attestation Walkthrough > **Status:** Complete — implements the November 2025 advisory "Embed in-toto attestations (DSSE-wrapped) into .NET 10/C# builds." Updated 2025-11-27 with CLI verification commands (`DSSE-CLI-401-021`). > **Owners:** Attestor Guild · DevOps Guild · Docs Guild. This guide shows how to emit signed, in-toto compliant DSSE envelopes for every container build step (scan → package → push) using Stella Ops Authority keys. The same primitives power our Signer/Attestor services, but this walkthrough targets developer pipelines (GitHub/GitLab, dotnet builds, container scanners). --- ## 1. Concepts refresher | Term | Meaning | |------|---------| | **In-toto Statement** | JSON document describing what happened (predicate) to which artifact (subject). | | **DSSE** | Dead Simple Signing Envelope: wraps the statement, base64 payload, and signatures. | | **Authority Signer** | Stella Ops client that signs data via file-based keys, HSM/KMS, or keyless Fulcio certs. | | **PAE** | Pre-Authentication Encoding: canonical “DSSEv1 ” byte layout that is signed. | Requirements: 1. .NET 10 SDK (preview) for C# helper code. 2. Authority key material (dev: file-based Ed25519; prod: Authority/KMS signer). 3. Artifact digest (e.g., `pkg:docker/registry/app@sha256:...`) per step. --- ## 2. Helpers (drop-in library) Create `src/StellaOps.Attestation` with: ```csharp public sealed record InTotoStatement( string _type, IReadOnlyList subject, string predicateType, object predicate); public sealed record Subject(string name, IReadOnlyDictionary digest); public sealed record DsseEnvelope( string payloadType, string payload, IReadOnlyList signatures); public sealed record Signature(string keyid, string sig); public interface IAuthoritySigner { Task GetKeyIdAsync(CancellationToken ct = default); Task SignAsync(ReadOnlyMemory pae, CancellationToken ct = default); } ``` DSSE helper: ```csharp public static class DsseHelper { public static async Task WrapAsync( InTotoStatement statement, IAuthoritySigner signer, string payloadType = "application/vnd.in-toto+json", CancellationToken ct = default) { var payloadBytes = JsonSerializer.SerializeToUtf8Bytes(statement, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); var pae = PreAuthEncode(payloadType, payloadBytes); var signature = await signer.SignAsync(pae, ct).ConfigureAwait(false); var keyId = await signer.GetKeyIdAsync(ct).ConfigureAwait(false); return new DsseEnvelope( payloadType, Convert.ToBase64String(payloadBytes), new[] { new Signature(keyId, Convert.ToBase64String(signature)) }); } private static byte[] PreAuthEncode(string payloadType, byte[] payload) { static byte[] Cat(params byte[][] parts) { var len = parts.Sum(p => p.Length); var buf = new byte[len]; var offset = 0; foreach (var part in parts) { Buffer.BlockCopy(part, 0, buf, offset, part.Length); offset += part.Length; } return buf; } var header = Encoding.UTF8.GetBytes("DSSEv1"); var pt = Encoding.UTF8.GetBytes(payloadType); var lenPt = Encoding.UTF8.GetBytes(pt.Length.ToString(CultureInfo.InvariantCulture)); var lenPayload = Encoding.UTF8.GetBytes(payload.Length.ToString(CultureInfo.InvariantCulture)); var space = Encoding.UTF8.GetBytes(" "); return Cat(header, space, lenPt, space, pt, space, lenPayload, space, payload); } } ``` Authority signer examples: /dev (file Ed25519): ```csharp public sealed class FileEd25519Signer : IAuthoritySigner, IDisposable { private readonly Ed25519 _ed; private readonly string _keyId; public FileEd25519Signer(byte[] privateKeySeed, string keyId) { _ed = new Ed25519(privateKeySeed); _keyId = keyId; } public Task GetKeyIdAsync(CancellationToken ct) => Task.FromResult(_keyId); public Task SignAsync(ReadOnlyMemory pae, CancellationToken ct) => Task.FromResult(_ed.Sign(pae.Span.ToArray())); public void Dispose() => _ed.Dispose(); } ``` Prod (Authority KMS): Reuse the existing `StellaOps.Signer.KmsSigner` adapter—wrap it behind `IAuthoritySigner`. --- ## 3. Emitting attestations per step Subject helper: ```csharp static Subject ImageSubject(string imageDigest) => new( name: imageDigest, digest: new Dictionary{{"sha256", imageDigest.Replace("sha256:", "", StringComparison.Ordinal)}}); ``` ### 3.1 Scan ```csharp var scanStmt = new InTotoStatement( _type: "https://in-toto.io/Statement/v1", subject: new[]{ ImageSubject(imageDigest) }, predicateType: "https://stella.ops/predicates/scanner-evidence/v1", predicate: new { scanner = "StellaOps.Scanner 0.9.0", findingsSha256 = scanResultsHash, startedAt = startedIso, finishedAt = finishedIso, rulePack = "lattice:default@2025-11-01" }); var scanEnvelope = await DsseHelper.WrapAsync(scanStmt, signer); await File.WriteAllTextAsync("artifacts/attest-scan.dsse.json", JsonSerializer.Serialize(scanEnvelope)); ``` ### 3.2 Package (SLSA provenance) ```csharp var pkgStmt = new InTotoStatement( "https://in-toto.io/Statement/v1", new[]{ ImageSubject(imageDigest) }, "https://slsa.dev/provenance/v1", new { builder = new { id = "stella://builder/dockerfile" }, buildType = "dockerfile/v1", invocation = new { configSource = repoUrl, entryPoint = dockerfilePath }, materials = new[] { new { uri = repoUrl, digest = new { git = gitSha } } } }); var pkgEnvelope = await DsseHelper.WrapAsync(pkgStmt, signer); await File.WriteAllTextAsync("artifacts/attest-package.dsse.json", JsonSerializer.Serialize(pkgEnvelope)); ``` ### 3.3 Push ```csharp var pushStmt = new InTotoStatement( "https://in-toto.io/Statement/v1", new[]{ ImageSubject(imageDigest) }, "https://stella.ops/predicates/push/v1", new { registry = registryUrl, repository = repoName, tags, pushedAt = DateTimeOffset.UtcNow }); var pushEnvelope = await DsseHelper.WrapAsync(pushStmt, signer); await File.WriteAllTextAsync("artifacts/attest-push.dsse.json", JsonSerializer.Serialize(pushEnvelope)); ``` --- ## 4. CI integration ### 4.1 GitLab example ```yaml .attest-template: &attest image: mcr.microsoft.com/dotnet/sdk:10.0-preview before_script: - dotnet build src/StellaOps.Attestation/StellaOps.Attestation.csproj variables: AUTHORITY_KEY_FILE: "$CI_PROJECT_DIR/secrets/ed25519.key" IMAGE_DIGEST: "$CI_REGISTRY_IMAGE@${CI_COMMIT_SHA}" attest:scan: stage: scan script: - dotnet run --project tools/StellaOps.Attestor.Tool -- step scan --subject "$IMAGE_DIGEST" --out artifacts/attest-scan.dsse.json artifacts: paths: [artifacts/attest-scan.dsse.json] attest:package: stage: package script: - dotnet run --project tools/StellaOps.Attestor.Tool -- step package --subject "$IMAGE_DIGEST" --out artifacts/attest-package.dsse.json attest:push: stage: push script: - dotnet run --project tools/StellaOps.Attestor.Tool -- step push --subject "$IMAGE_DIGEST" --registry "$CI_REGISTRY" --tags "$CI_COMMIT_REF_NAME" ``` ### 4.2 GitHub Actions snippet ```yaml jobs: attest: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v4 with: { dotnet-version: '10.0.x' } - name: Build attestor helpers run: dotnet build src/StellaOps.Attestation/StellaOps.Attestation.csproj - name: Emit scan attestation run: dotnet run --project tools/StellaOps.Attestor.Tool -- step scan --subject "${{ env.IMAGE_DIGEST }}" --out artifacts/attest-scan.dsse.json env: AUTHORITY_KEY_REF: ${{ secrets.AUTHORITY_KEY_REF }} ``` --- ## 5. Verification * `stella attest verify --envelope artifacts/attest-scan.dsse.json` — offline verification using the CLI. * Additional verification options: * `--policy policy.json` — apply custom verification policy * `--root keys/root.pem` — specify trusted root certificate * `--transparency-checkpoint checkpoint.json` — verify against Rekor checkpoint * Manual validation: 1. Base64 decode payload → ensure `_type` = `https://in-toto.io/Statement/v1`, `subject[].digest.sha256` matches artifact. 2. Recompute PAE and verify signature with the Authority public key. 3. Attach envelope to Rekor (optional) via existing Attestor API. --- ## 6. Storage conventions Store DSSE files next to build outputs: ``` artifacts/ attest-scan.dsse.json attest-package.dsse.json attest-push.dsse.json ``` Include the SHA-256 digest of each envelope in promotion manifests (`docs/release/promotion-attestations.md`) so downstream verifiers can trace chain of custody. --- ## 7. References - [In-toto Statement v1](https://in-toto.io/spec/v1) - [DSSE specification](https://github.com/secure-systems-lab/dsse) - `docs/modules/signer/architecture.md` - `docs/modules/attestor/architecture.md` - `docs/release/promotion-attestations.md` This file was updated as part of `DSSE-LIB-401-020` and `DSSE-CLI-401-021` (completed 2025-11-27). See `docs/modules/cli/guides/attest.md` for CI/CD workflow snippets.