attest

package
v0.3.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 2, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package attest implements the CANARY attestation mechanism: ed25519 signing and verification of the claims one `canary evidence run-go-test` run makes about itself. It is a LEAF package -- stdlib crypto/ed25519 only, no imports from the rest of this repo -- so any package may depend on it without creating a cycle.

Honesty boundary (read before wiring this in anywhere)

A signature is only as trustworthy as the key that made it. This package gives canary the MECHANISM to authenticate evidence with a key a workspace-local adversary does not hold -- it does not, by itself, make evidence trustworthy. Concretely:

  • The private key used to sign an attestation MUST live outside the workspace the evidence describes (a CI secret store, an operator's own machine, an HSM -- anywhere a workspace-writer cannot read it). A private key committed to the repo, or dropped next to the evidence it signs, protects nothing: an adversary who can write the workspace can also write the key and forge whatever it likes.
  • DEFAULT canary behavior (no --sign-key, no --require-attestation, no evidence.trusted_keys configured) is UNCHANGED by this package: "executed" evidence is trusted purely because it was produced by canary's own operator-named toolchain (see pkg/cmds/evidence's resolveGoToolchain) -- filesystem trust, not cryptographic trust. That remains true whether or not this package is even linked in.
  • Attestation becomes a real trust upgrade only once RequireAttestation is turned on (verify --require-attestation / ingest --require-attestation, both paired with --trusted-keys naming a key the adversary does not control) AND the signing key is actually kept outside the workspace. Turning the flag on with an in-workspace key is security theater, not a fix.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Canonical

func Canonical(a Attestation) ([]byte, error)

Canonical returns a's deterministic wire encoding: attestationDomainPrefix followed by json.Marshal of the struct itself, never a map. Attestation's field order is fixed in source (encoding/json always encodes struct fields in declaration order) and it carries no map-typed field anywhere in its shape, so two calls with identical field values always produce byte-identical output regardless of process, platform, or call order -- the property Sign/Verify depend on: signing and verifying must hash the exact same bytes for the exact same claims, every time. The prefix is part of that encoding, not a separate step, so both Sign and Verify (both of which call Canonical) automatically agree on it.

func Fingerprint

func Fingerprint(pub ed25519.PublicKey) string

Fingerprint returns a stable content identity for pub: "sha256:" followed by the hex-encoded SHA-256 of the raw public key bytes -- the same "sha256:"+hex shape used throughout this codebase for artifact and toolchain digests (evidence.Record.ArtifactDigest, ToolchainDigest), so a producer identity reads the same way as everything else canary hashes.

func GenerateKeypair

func GenerateKeypair() (ed25519.PublicKey, ed25519.PrivateKey, error)

GenerateKeypair returns a fresh ed25519 keypair using crypto/rand. Used by `canary evidence keygen` and by tests; production signing keys generated this way must be moved outside the workspace immediately -- see the package doc comment.

func LoadPrivateKey

func LoadPrivateKey(path string) (ed25519.PrivateKey, error)

LoadPrivateKey reads an ed25519 private key from path. Two encodings are accepted:

  • A PEM-armored PKCS8 private key ("BEGIN PRIVATE KEY" / "END PRIVATE KEY"), as written by MarshalPrivateKeyPEM / `canary evidence keygen`.
  • A raw key, standard-base64 encoded on a single line (whitespace trimmed): either a 32-byte ed25519 seed (expanded via ed25519.NewKeyFromSeed) or the full 64-byte private key.

Any other content, or a key that is not ed25519, is a hard error -- a signing key that cannot be identified with certainty must never be silently skipped.

func LoadPublicKeys

func LoadPublicKeys(pathOrDir string) ([]ed25519.PublicKey, error)

LoadPublicKeys reads one or more ed25519 public keys from pathOrDir:

  • A file: loaded as a single key, either PEM-armored PKIX ("BEGIN PUBLIC KEY") or a raw standard-base64-encoded 32-byte key on one line.
  • A directory: every entry whose name ends in ".pub" or ".pem" is loaded the same way (PEM or raw), in sorted-filename order, and all of them are returned -- a project with multiple trusted producers (several CI runners, several operators) names its keys one file each.

An empty pathOrDir returns (nil, nil): no trusted keys configured. Any unreadable, malformed, or non-ed25519 key is a hard error -- a key that cannot be identified with certainty must never be silently dropped from the trust set (that would look identical to "there simply is no such key", hiding a configuration mistake).

func MarshalPrivateKeyPEM

func MarshalPrivateKeyPEM(priv ed25519.PrivateKey) ([]byte, error)

MarshalPrivateKeyPEM encodes priv as a PEM-armored PKCS8 private key ("PRIVATE KEY" block), the format LoadPrivateKey's PEM path reads back.

func MarshalPublicKeyPEM

func MarshalPublicKeyPEM(pub ed25519.PublicKey) ([]byte, error)

MarshalPublicKeyPEM encodes pub as a PEM-armored PKIX public key ("PUBLIC KEY" block), the format LoadPublicKeys' PEM path reads back.

func Sign

func Sign(priv ed25519.PrivateKey, a Attestation) (string, error)

Sign returns a base64 (standard encoding) detached signature of a's canonical encoding, made with priv. The signature covers Canonical(a) alone -- nothing else needs to travel with it for Verify to check it, though the sidecar format this package's callers use stores the attestation alongside the signature anyway so a verifier does not have to reconstruct it from other sources.

func Verify

func Verify(pub ed25519.PublicKey, a Attestation, sigB64 string) bool

Verify reports whether sigB64 is a valid ed25519 signature, by pub, over a's canonical encoding. Any failure -- a malformed sigB64, a wrong-sized key, a signature made with a different key, or a's fields not matching what was actually signed (Canonical changes with any field, so tampering with even one field after signing changes the bytes Verify re-hashes and the signature no longer matches) -- reports false. Verify never panics and never distinguishes WHY a check failed; that would leak information a verifier does not need and an attacker could use to narrow their forgery attempts.

Types

type Attestation

type Attestation struct {
	// ProducerIdentity is Fingerprint(pub) for the key that will sign this
	// attestation: "sha256:" + hex(sha256(pub)). It lets a verifier holding
	// several trusted keys report WHICH one vouched for a run without
	// needing to try every key just to log an identity.
	ProducerIdentity string   `json:"producer_identity"`
	ToolchainPath    string   `json:"toolchain_path"`
	ToolchainDigest  string   `json:"toolchain_digest"`
	Argv             []string `json:"argv"`
	RunExitStatus    int      `json:"run_exit_status"`
	ProjectID        string   `json:"project_id"`
	CommitSHA        string   `json:"commit_sha"`
	SourceDigest     string   `json:"source_digest"`
	ArtifactDigest   string   `json:"artifact_digest"`
	ObservedAt       string   `json:"observed_at"`
}

CANARY: REQ=CP-236; FEATURE="Attestation"; ASPECT=Security; STATUS=TESTED; TEST=TestRoundTripSignVerify,TestVerifyTamperedFieldFails,TestVerifyWrongKeyFails,TestVerifyMalformedSignatureFails,TestCanonicalDeterministic,TestCanonicalFieldOrderFixed,TestCanonicalHasDomainPrefix,TestSignDomainSeparatesFromRawCanonicalJSON,TestFingerprintStableAndDistinct,TestGenerateKeypairProducesWorkingKeys,TestLoadPrivateKeyPEMRoundTrip,TestLoadPrivateKeyRawSeed,TestLoadPrivateKeyRawFull,TestLoadPrivateKeyRejectsGarbage,TestLoadPublicKeysPEMFile,TestLoadPublicKeysRawFile,TestLoadPublicKeysDirLoadsAll,TestLoadPublicKeysEmptyPathIsNoKeys,TestLoadPublicKeysDirWithNoMatchesErrors,TestLoadPublicKeysMissingPathErrors; UPDATED=2026-09-01 Attestation is the exact set of claims one `canary evidence run-go-test` run makes about itself: which toolchain ran, what it was told to run, how it exited, which project/commit/source/artifact it describes, and when it was observed. Every field is exported with a fixed json tag and the struct has no maps -- see Canonical for why that matters.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL