keystore

package
v0.64.0 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package keystore provides named key-material management for GoBricks applications: RSA key pairs and raw symmetric secrets (HMAC/CMAC keys, HKDF input).

Material is loaded at startup from files or base64-encoded values (typically injected via environment variables for Kubernetes/EKS deployments). Once loaded, the store is read-only and safe for concurrent access. Each entry is exactly one of an RSA pair, a symmetric secret, or a password-protected PKCS#12 bundle — a mixed entry is rejected by the config layer at startup (structural detection, no explicit discriminator).

Configuration

Keys are configured in YAML under the "keystore" section:

keystore:
  secretminlength: 32                        # default 32; a set value can only raise it (ADR-095)
  keys:
    signing:
      public:
        file: "certs/signing_public.der"       # Local dev
      private:
        value: "${SIGNING_PRIVATE_KEY_BASE64}"  # EKS (base64-encoded DER)
    mac-key:
      secret:
        value: "${MAC_KEY_BASE64}"              # base64 raw key material
    vts:
      pkcs12:
        file: "certs/vts.p12"                   # or value: base64 of the bundle
        password:
          env: "VTS_P12_PASSWORD"               # variable NAME; or file: path

Usage

Register the module before modules that need keys:

if err := fw.RegisterModule(keystore.NewModule()); err != nil {
    log.Fatal(err)
}
if err := fw.RegisterModule(&myapp.JWEModule{}); err != nil {
    log.Fatal(err)
}

Access keys via ModuleDeps (nil-check for fail-fast if keys are required):

func (m *Module) Init(deps *app.ModuleDeps) error {
    if deps.KeyStore == nil {
        return fmt.Errorf("KeyStore required but not configured")
    }
    m.keyStore = deps.KeyStore
    return nil
}

privKey, err := m.keyStore.PrivateKey("signing")

Secret returns a defensive copy of symmetric key material; the caller owns the slice and may zeroize it after use:

macKey, err := m.keyStore.Secret("mac-key")

Index

Constants

View Source
const (
	RoleTagJoseRoute = "jose-route"
	RoleTagSeal      = "seal"
)

Role tags a startup resolution carries: which framework feature asked the store for an entry. HTTP jose (a route policy) and payload sealing (a seal-tagged event type) must never share a kid — one key serving two protocols widens what a compromise of either reaches — so the store remembers the tag of every startup resolution and the app WARNs once per entry seen under both (#1306, ADR-097). Warn only: an enforced prefix partition was rejected as breaking shipped HTTP surface. Runtime (per-message) resolutions never record a tag. The app reads the log once route registration is complete, so a NEW startup resolution path must run before that point (app.prepareRuntime) or its entries go unreported.

Variables

This section is empty.

Functions

func CompareGenerations added in v0.63.0

func CompareGenerations(a, b Generation) int

CompareGenerations orders two generations of one family by version, the order FamilyEnumerator guarantees. Versions are canonical decimal, so a shorter digit string is the smaller integer and equal lengths compare lexically — no integer parse, no overflow ceiling on the digit count.

Types

type DualRoleReporter added in v0.63.0

type DualRoleReporter interface {
	DualRoleEntries() map[string][]string
}

DualRoleReporter is the optional door the app reads after registration: every entry resolved under more than one role tag, mapped to the sorted tags it was seen under. Plain map so the app (which keystore imports) can name the method without importing this package.

type FamilyEnumerator added in v0.63.0

type FamilyEnumerator interface {
	// Generations returns the provisioned generations of logical, ascending by
	// version, or an empty slice when none is provisioned. Never an error: an
	// unknown family is simply empty.
	Generations(logical string) []Generation
}

FamilyEnumerator lists the provisioned generations of a Logical kid. The keystore's store implements it; consumers type-assert app.KeyStore to reach it, so the app.KeyStore interface itself is unchanged. The result IS the accept set: provisioning key material is the sole trust act (#1306).

type Generation added in v0.63.0

type Generation struct {
	// Logical is the family name the sealing declaration carries.
	Logical string
	// Version is the generation marker without the hyphen, e.g. "v2".
	Version string
	// Role is what the entry's material permits.
	Role Role
}

Generation is one provisioned key of a Logical kid's family: the entry named <Logical>-<Version> and the role its material grants.

func ActiveGeneration added in v0.63.0

func ActiveGeneration(store FamilyEnumerator, active map[string]string, logical string) (Generation, error)

ActiveGeneration resolves the producer's Activation for one Logical kid: which provisioned generation seals new traffic. active is the messaging.seal.active selector (Logical kid -> "v<N>"), already shape-checked by config.Validate; store is the keystore's family index.

  • no provisioned generation: error naming the family, selector or not;
  • one provisioned, no selector: that one is active;
  • several provisioned, no selector: error — startup never guesses;
  • selector present: it must name a provisioned generation, else an error naming the selector value.

The caller (the component owning sealing declarations) calls it once per Logical kid it resolves, sign and encrypt alike, at startup. active is the bare map rather than config.SealConfig by decision: the keystore stays decoupled from a messaging-owned config type, and the grammar re-check below covers a map that never passed config.Validate.

func (Generation) Kid added in v0.63.0

func (g Generation) Kid() string

Kid is the full entry name, e.g. "svc-payments-sign-v2" — the value that travels on the wire and the name the store's accessors take.

type Module

type Module struct {
	// contains filtered or unexported fields
}

Module implements the GoBricks app.Module interface for named key-material management. It loads named RSA key pairs and raw symmetric secrets at startup and provides them to other modules via deps.KeyStore.

Register before modules that need keys:

if err := fw.RegisterModule(keystore.NewModule()); err != nil {
    log.Fatal(err)
}
if err := fw.RegisterModule(&myapp.JWEModule{}); err != nil {
    log.Fatal(err)
}

func NewModule

func NewModule() *Module

NewModule creates a new Module instance.

func (*Module) Init

func (m *Module) Init(deps *app.ModuleDeps) error

Init implements app.Module. Loads all configured key material (RSA pairs and symmetric secrets) and validates it. Fails fast on any error.

func (*Module) KeyStore

func (m *Module) KeyStore() app.KeyStore

KeyStore implements app.KeyStoreProvider.

func (*Module) Name

func (m *Module) Name() string

Name implements app.Module.

func (*Module) Shutdown

func (m *Module) Shutdown() error

Shutdown implements app.Module.

type Role added in v0.63.0

type Role uint8

Role is the material an entry holds, which decides what a sealing side can do with a generation: verify/encrypt (public only), sign/decrypt (private present), or MAC (symmetric secret).

const (
	// RolePublicOnly holds an RSA public key and no private key.
	RolePublicOnly Role = iota + 1
	// RolePrivate holds an RSA pair, private included.
	RolePrivate
	// RoleSecret holds raw symmetric material.
	RoleSecret
)

func (Role) String added in v0.63.0

func (r Role) String() string

String returns the role name used in errors and logs.

type RoleRecorder added in v0.63.0

type RoleRecorder interface {
	RecordResolution(entry, role string)
}

RoleRecorder is the optional door a startup resolver uses to tag an entry. The keystore module's store implements it; a test double may too.

Directories

Path Synopsis
Package testing provides test utilities for the keystore package.
Package testing provides test utilities for the keystore package.

Jump to

Keyboard shortcuts

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