signing

package
v0.1.0-dev.20260901165439 Latest Latest
Warning

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

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

Documentation

Overview

Package signing signs and verifies the two signable op artifacts — graphs and execution traces — per the settled signing design (docs/plans/extract-starlark-from-op/phase-8/signing-options.md and graph-signing.md; phase-8 step 46).

The model is publisher identity with verifier-side trust: a raw ssh-ed25519 signature over the artifact's namespace-prefixed canonical bytes (NamespaceGraph / NamespaceTrace give domain separation), the publisher's key riding the document in OpenSSH wire format, and trust resolved against an OpenSSH `allowed_signers` file the verifier owns. No envelope, no hash options — the algorithm names the whole ciphersuite.

This package implements the DEFAULT custody tier: the developer's SSH key (`~/.ssh/id_ed25519`) with a generated local Ed25519 keyfile as the fallback. The ssh-agent, cloud-KMS, and keyless tiers are chartered opt-ins (the support matrix in signing-options.md) and are deliberately absent so their dependency weight stays out of the default build.

What a consumer does with a verification outcome is governed by Policy — the settled four-tier ladder — through one enforcement point, [Judge]. Like its prior art (PowerShell's ExecutionPolicy), the policy is a safety feature, not a security boundary.

Index

Constants

View Source
const (
	// NamespaceGraph is the domain-separation prefix signed ahead of a graph's canonical bytes.
	NamespaceGraph = "devlore.graph.v1"

	// NamespaceTrace is the domain-separation prefix signed ahead of a trace's canonical bytes.
	NamespaceTrace = "devlore.trace.v1"

	// AlgorithmEd25519 is the default ciphersuite — Ed25519 under its OpenSSH key-type name.
	AlgorithmEd25519 = "ssh-ed25519"
)

Variables

This section is empty.

Functions

func CanonicalDocument

func CanonicalDocument(data []byte) ([]byte, error)

CanonicalDocument returns a serialized document's canonical bytes: the generically-decoded document with its integrity fields (`checksum`, `signature`) removed, re-marshaled.

This is the verify-side dual of the live artifacts' CanonicalContent for document-form canonicalization (traces): decoding into the typed struct can be lossy (custom unmarshalers), so verification canonicalizes the bytes it was handed. yaml key ordering is stable, so the result matches the sign-time canonical.

Parameters:

  • `data`: the document bytes (YAML).

Returns:

  • `[]byte`: the canonical bytes.
  • `error`: non-nil when the document does not decode or re-marshal.

func External

func External(documentPath, storeHome string) bool

External reports whether a document path lies outside `storeHome` — the settled externality marker (a document under this machine's own store was produced by its own runs; anything else is external).

Parameters:

  • `documentPath`: the document's path as given.
  • `storeHome`: the machine's store root (the devlore state home).

Returns:

  • `bool`: true when the document is external to the store.

Types

type Outcome

type Outcome int

Outcome classifies one artifact's verification result.

const (
	// OutcomeValid means the signature verifies and the publisher key resolved to a trusted principal.
	OutcomeValid Outcome = iota

	// OutcomeUnsigned means the artifact carries no signature — a finding of absence, not a failure.
	OutcomeUnsigned

	// OutcomeInvalid means a signature is present but does not verify over the canonical bytes (the artifact
	// was altered after signing, or the signature is malformed).
	OutcomeInvalid

	// OutcomeUntrusted means the signature verifies but the publisher key resolved to no trusted principal in
	// the verifier's `allowed_signers`.
	OutcomeUntrusted
)

func (Outcome) String

func (o Outcome) String() string

String returns the outcome's lowercase label.

Returns:

  • `string`: "valid", "unsigned", "invalid", or "untrusted".

type Policy

type Policy int

Policy governs what a consumer does with a verification Verdict — the settled four-tier ladder (phase-8 step 46, question 4; prior art: PowerShell ExecutionPolicy, pacman SigLevel, Kubernetes admission enforce/warn/audit).

Like its prior art, the policy is a safety feature, not a security boundary: it prevents accidents and surfaces facts; it does not stop a local adversary who can change the policy.

const (
	// PolicyReport verifies and reports findings — both unsigned (absence) and invalid/untrusted (failure) —
	// but refuses nothing. The floor: unsigned stores keep working while signature state becomes visible.
	PolicyReport Policy = iota

	// PolicyIgnore performs no verification at all.
	PolicyIgnore

	// PolicyRejectExternal rejects unsigned, invalid, or untrusted documents from OUTSIDE this machine's own
	// store; own-store documents behave as [PolicyReport]. The store boundary is the externality marker.
	PolicyRejectExternal

	// PolicyReject rejects every document that is not valid — unsigned, invalid, or untrusted alike.
	PolicyReject
)

func ParsePolicy

func ParsePolicy(value string) (Policy, error)

ParsePolicy parses a config/flag policy value.

Parameters:

  • `value`: "ignore", "report", "reject_external", or "reject"; "" parses as the PolicyReport floor.

Returns:

  • `Policy`: the parsed policy.
  • `error`: non-nil for any other value.

func (Policy) Judge

func (p Policy) Judge(verdict Verdict, external bool) error

Judge applies the policy to a verdict: the one enforcement point every consumer shares.

Parameters:

  • `verdict`: the artifact's verification result.
  • `external`: whether the document came from outside this machine's own store (see External).

Returns:

  • `error`: the rejection when the policy refuses this verdict; nil to proceed (the caller reports the verdict regardless — except under PolicyIgnore, where there is nothing to report).

func (Policy) String

func (p Policy) String() string

String returns the policy's config/flag value (snake_case per the config convention).

Returns:

  • `string`: "ignore", "report", "reject_external", or "reject".

type Signer

type Signer interface {

	// Sign produces the signature over `namespace ‖ canonical`.
	//
	// Parameters:
	//   - `namespace`: the artifact-kind domain separator ([NamespaceGraph] or [NamespaceTrace]).
	//   - `canonical`: the artifact's canonical bytes.
	//
	// Returns:
	//   - `*op.Signature`: the algorithm, the publisher key (OpenSSH wire format), and the raw signature.
	//   - `error`: non-nil when signing fails.
	Sign(namespace string, canonical []byte) (*op.Signature, error)
}

Signer signs canonical artifact bytes under a namespace, producing the document's op.Signature.

func DefaultSigner

func DefaultSigner(configRoot fsroot.Dir, identityPath string) (Signer, error)

DefaultSigner resolves the default-tier signer: the publisher's SSH key, else the generated local key.

Resolution order (signing-options.md): `identityPath` when it names a key parseable without a passphrase; otherwise the generated local Ed25519 keyfile under `configRoot` (`signing/ed25519`, created on first use with its `.pub` in authorized_keys format for `allowed_signers` seeding).

**Both locations are received, never constructed** — the phase 2b invariant. The caller that owns the session supplies them, which is what makes the SSH tier testable: a test names a path inside its own sandbox and no longer depends on where the process thinks home is. It is also what makes "sign this repository with my work key" expressible at all.

Parameters:

  • `configRoot`: the root the generated keyfile lives under, owned by the caller.
  • `identityPath`: the OpenSSH private key to prefer — ssh's `-i`. A path that is absent, passphrase protected, or not an Ed25519 key falls through to the generated key rather than failing.

Returns:

  • `Signer`: the resolved signer.
  • `error`: non-nil when no key can be loaded or generated.

type Verdict

type Verdict struct {

	// Outcome is the classification.
	Outcome Outcome

	// Principal is the trusted identity the publisher key resolved to; "" unless the outcome is valid.
	Principal string

	// Detail elaborates non-valid outcomes for human readers.
	Detail string
}

Verdict is one artifact's verification result: the outcome, the trusted principal when valid, and the human-readable detail otherwise.

func Verify

func Verify(signature *op.Signature, namespace string, canonical []byte, allowedSignersPath string) Verdict

Verify checks an artifact's signature over its namespace-prefixed canonical bytes and resolves the publisher against the trust list.

Parameters:

  • `signature`: the artifact's signature, or nil for unsigned.
  • `namespace`: the artifact-kind domain separator (NamespaceGraph or NamespaceTrace).
  • `canonical`: the artifact's canonical bytes.
  • `allowedSignersPath`: the verifier's trust list; "" resolves to the default (`<config>/devlore/allowed_signers`).

Returns:

  • `Verdict`: the classification, with the trusted principal on validity.

Jump to

Keyboard shortcuts

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