keys

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Oct 28, 2026 License: Apache-2.0 Imports: 30 Imported by: 0

Documentation

Overview

Package keys holds the key hierarchy of the online CA keys (PLAN §5 "Keys", security.md "Key storage"):

passphrase --Argon2id--> KEK --AES-GCM--> root key --AES-GCM--> DEK --AES-GCM--> private key
recovery key --HKDF----> recovery KEK --AES-GCM--> root key (second copy)

The server starts sealed. Unseal puts only the root key in memory; DEKs and private keys are decrypted for one signing operation at a time.

Index

Constants

View Source
const (
	PurposeCA   = "ca"
	PurposeLeaf = "leaf"
	// PurposeEAB: the MAC key of an ACME external account binding (PHASE2
	// step 13); stored in acme_eab_keys, never in the keys table.
	PurposeEAB = "acme-eab"
)

Key purposes (keys.purpose in pki.db).

Variables

View Source
var (
	ErrSealed             = errors.New("the server is sealed – unseal it first")
	ErrAlreadyUnsealed    = errors.New("the server is already unsealed")
	ErrNotInitialized     = errors.New("no root key yet – run myownpki init")
	ErrAlreadyInitialized = errors.New("the root key exists already")
	ErrWrongSecret        = errors.New("wrong passphrase or recovery key")
	ErrWeakPassphrase     = errors.New("the passphrase must have at least 16 characters")
	ErrLocked             = errors.New("too many failed attempts")
	// ErrWeakKDF: the passphrase slot asks for less than the production
	// Argon2id parameters (a key ring of a test build).
	ErrWeakKDF = errors.New("the key ring's Argon2id parameters are below the minimum")
)

Errors. ErrWrongSecret is deliberately the same for passphrase and recovery key and says nothing about why.

View Source
var ErrDecrypt = errors.New("decryption failed")

ErrDecrypt means a ciphertext could not be opened: wrong key, or the ciphertext was changed or moved to another row.

View Source
var ErrKeyDestroyed = errors.New("the key was deleted from the server")

ErrKeyDestroyed: the key was destroyed after its download (step 18).

View Source
var ErrNoSuchKey = errors.New("no such key")

ErrNoSuchKey means there is no key with that ID.

View Source
var ErrRecoveryKeyFormat = errors.New("not a valid recovery key")

ErrRecoveryKeyFormat means a typed recovery key is malformed or has a typo (wrong length, unknown character, check characters don't match).

View Source
var ErrRecoveryKeyNeedsFile = errors.New("standard output is not an interactive terminal – " +
	"the recovery key would end up in a log; use --recovery-key-file <path>")

ErrRecoveryKeyNeedsFile: without an interactive terminal (Docker, systemd, a pipe) the key would end up in a log, so it may only go to a file.

Functions

func CheckPassphrase

func CheckPassphrase(p []byte) error

CheckPassphrase applies the passphrase rules: at least 16 characters, at most 1024 bytes, valid UTF-8.

func ConfirmIndexes

func ConfirmIndexes(rnd io.Reader) ([2]int, error)

ConfirmIndexes picks two different group positions (0–8, ascending) the user must type again before init or a rotation finishes.

func HasNonASCII

func HasNonASCII(p []byte) bool

HasNonASCII reports characters that may be typed differently on another keyboard or system; init warns about them.

func IsTerminal

func IsTerminal(f *os.File) bool

IsTerminal reports whether f is a character device (a terminal). Note: a container started with -t has a terminal whose output still reaches the container log – the docs tell operators to use --recovery-key-file there.

func SaveKey

func SaveKey(ctx context.Context, q pkidb.DBTX, key StoredKey, now string) error

SaveKey stores an envelope key in pki.db. Pass the transaction that also stores the certificate, so key and certificate exist together or not at all.

func TestArgon2 added in v0.2.0

func TestArgon2() (timeCost, memoryKiB uint32, threads uint8)

TestArgon2 returns the weak Argon2id parameters of tests (t=1, 8 MiB, p=1). It panics outside a test binary: there is no way to make a server use them – no flag, no configuration key, no environment variable.

func WriteRecoveryKeyFile

func WriteRecoveryKeyFile(path string, k RecoveryKey) (err error)

WriteRecoveryKeyFile writes the key to a new file with mode 0600. An existing file is never overwritten.

Types

type Envelope

type Envelope struct {
	RootKeyVersion int64
	WrappedDEK     []byte // DEK encrypted with the root key
	Ciphertext     []byte // PKCS#8 encrypted with the DEK
}

Envelope is an encrypted private key as stored in pki.db.

type Keyring

type Keyring struct {

	// Wake, if set, is called after an audit event was written to the
	// outbox, so the deliverer picks it up at once.
	Wake func()
	// OnUnseal and BeforeSeal, if set, run right after an unseal and right
	// before a seal – the server makes audit checkpoints there (step 6a).
	OnUnseal, BeforeSeal func(context.Context)
	// contains filtered or unexported fields
}

Keyring holds the root key while the server is unsealed.

func New

func New(system, auditLog *sql.DB, clk clock.Clock, opts ...Option) *Keyring

New returns a sealed keyring. system and auditLog are the writers of system.db and of the instance audit log (<data>/audit.db).

func (*Keyring) ChangePassphrase

func (k *Keyring) ChangePassphrase(ctx context.Context, current, next []byte, who Who) error

ChangePassphrase rewraps the root key with a new passphrase (new salt, current Argon2id defaults). Requires the unsealed keyring and the current passphrase. Both passphrases are cleared.

func (*Keyring) CheckpointKeyFingerprint

func (k *Keyring) CheckpointKeyFingerprint() (string, error)

CheckpointKeyFingerprint is the fingerprint of the checkpoint key to note down (shown by init, logged at unseal; `audit verify --checkpoint-key`).

func (*Keyring) EncryptKey

func (k *Keyring) EncryptKey(keyID, purpose, algorithm string, pkcs8 []byte) (Envelope, error)

EncryptKey encrypts a PKCS#8 private key with a fresh DEK. keyID, purpose and algorithm are bound into the ciphertext: the same bytes in another row won't decrypt. The caller should clear pkcs8 afterwards.

func (*Keyring) Init

func (k *Keyring) Init(ctx context.Context, passphrase []byte, recovery RecoveryKey, who Who) error

Init creates the root key and stores it wrapped twice: with the passphrase and with the recovery key. The keyring stays sealed. passphrase is cleared. Create the recovery key with NewRecoveryKey and let the user confirm it before calling Init – nothing is stored before this call.

func (*Keyring) Initialized

func (k *Keyring) Initialized(ctx context.Context) (bool, error)

Initialized reports whether a root key exists.

func (*Keyring) ResetPassphrase

func (k *Keyring) ResetPassphrase(ctx context.Context, recovery RecoveryKey, next []byte, who Who) error

ResetPassphrase sets a new passphrase with the recovery key – for when the passphrase is lost. Works sealed or unsealed and doesn't change the state.

func (*Keyring) RotateRecoveryKey

func (k *Keyring) RotateRecoveryKey(ctx context.Context, passphrase []byte, next RecoveryKey, who Who) error

RotateRecoveryKey replaces the recovery key; the old one stops working (except for backups taken before). Requires the unsealed keyring and the passphrase. Create next with NewRecoveryKey and let the user confirm it before calling.

func (*Keyring) Seal

func (k *Keyring) Seal(ctx context.Context, who Who) error

Seal removes the root key from memory. It waits for running signing operations to finish. Sealing a sealed keyring does nothing.

func (*Keyring) SignCheckpoint

func (k *Keyring) SignCheckpoint(msg []byte) ([]byte, ed25519.PublicKey, error)

SignCheckpoint signs an audit checkpoint (PHASE1 step 6a) with an Ed25519 key derived from the root key. The key exists only for this call; sealed, it fails with an error that wraps audit.ErrNoSigner.

func (*Keyring) Status

func (k *Keyring) Status() Status

Status reports whether the keyring is sealed.

func (*Keyring) Unseal

func (k *Keyring) Unseal(ctx context.Context, passphrase []byte, who Who) error

Unseal derives the KEK from passphrase, unlocks the root key and keeps it in memory. passphrase is cleared.

func (*Keyring) UnsealWithRecovery

func (k *Keyring) UnsealWithRecovery(ctx context.Context, recovery RecoveryKey, who Who) error

UnsealWithRecovery unlocks the root key with the recovery key.

func (*Keyring) VerifyPassphrase

func (k *Keyring) VerifyPassphrase(ctx context.Context, passphrase []byte, who Who) error

VerifyPassphrase checks the unseal passphrase without changing the seal state: the proof a console emergency needs (myownpki user reset-2fa) – shell access alone is not enough. Rate-limited and audited like unseal.

func (*Keyring) VerifyRecoveryKey

func (k *Keyring) VerifyRecoveryKey(ctx context.Context, recovery RecoveryKey, who Who) error

VerifyRecoveryKey is VerifyPassphrase with the recovery key.

func (*Keyring) WithKey

func (k *Keyring) WithKey(keyID, purpose, algorithm string, env Envelope, fn func(pkcs8 []byte) error) error

WithKey decrypts the private key for the duration of fn and clears it afterwards. Seal waits until fn returns. fn must not keep pkcs8.

func (*Keyring) WithSigner

func (k *Keyring) WithSigner(keyID, purpose, algorithm string, env Envelope, fn func(crypto.Signer) error) error

WithSigner is WithKey for signing: fn gets the parsed key. Go can't clear the parsed key's big numbers; the object is dropped when fn returns.

type LockedError

type LockedError struct{ RetryAfter time.Duration }

LockedError carries how long the caller must wait (HTTP 429 Retry-After).

func (*LockedError) Error

func (e *LockedError) Error() string

func (*LockedError) Unwrap

func (e *LockedError) Unwrap() error

type Option added in v0.2.0

type Option func(*options)

Option changes a new Keyring.

func TestKDF added in v0.2.0

func TestKDF() Option

TestKDF makes Init, ChangePassphrase and ResetPassphrase store the weak Argon2id parameters of tests (TestArgon2) – a test instance then needs 8 MiB per derivation instead of 256 MiB. New panics if it is used outside a test binary; and outside a test binary Unseal refuses such a key ring.

type RecoveryKey

type RecoveryKey [32]byte

RecoveryKey is the second way to unlock the root key: 256 random bits, shown once at init. It is printed only on purpose (Reveal); String, %v, %#v and JSON all show a placeholder, so it can't end up in a log by accident.

func NewRecoveryKey

func NewRecoveryKey(rnd io.Reader) (RecoveryKey, error)

NewRecoveryKey creates a random recovery key. Nothing is stored: init and rotation show it, let the user confirm it and only then pass it on.

func ParseRecoveryKey

func ParseRecoveryKey(s string) (RecoveryKey, error)

ParseRecoveryKey reads a typed recovery key. Case, spaces and dashes don't matter; a typo is reported as ErrRecoveryKeyFormat before anything is tried against the database.

func (RecoveryKey) CheckGroups

func (k RecoveryKey) CheckGroups(typed map[int]string) bool

CheckGroups reports whether every typed group matches the key at its position (1st group = index 0). At least one group must be given.

func (RecoveryKey) GoString

func (RecoveryKey) GoString() string

func (RecoveryKey) Groups

func (k RecoveryKey) Groups() []string

Groups returns the nine groups of six characters.

func (RecoveryKey) MarshalJSON

func (RecoveryKey) MarshalJSON() ([]byte, error)

func (RecoveryKey) MarshalText

func (RecoveryKey) MarshalText() ([]byte, error)

func (RecoveryKey) Reveal

func (k RecoveryKey) Reveal() string

Reveal returns the key for display or the recovery key file: nine groups of six characters, e.g. "K7QM2X-9TRPHD-…".

func (RecoveryKey) String

func (RecoveryKey) String() string

type RecoveryOutput

type RecoveryOutput int

RecoveryOutput is where init shows a new recovery key.

const (
	ShowOnTerminal RecoveryOutput = iota + 1
	WriteToFile
)

Output targets.

func ChooseRecoveryOutput

func ChooseRecoveryOutput(interactive bool, file string) (RecoveryOutput, error)

ChooseRecoveryOutput applies the rule (owner decision 2026-10-13): a file if one is given, the terminal only if stdout is interactive, otherwise refuse.

type Status

type Status struct {
	Sealed      bool
	SealedSince time.Time // when Sealed: since start or the last seal
}

Status is the seal state.

type StoredKey

type StoredKey struct {
	ID, Purpose, Algorithm string
	Envelope               Envelope
}

StoredKey is a row of pki.db keys.

func LoadKey

func LoadKey(ctx context.Context, q pkidb.DBTX, id string) (StoredKey, error)

LoadKey reads an envelope key from pki.db.

type Who

type Who struct {
	Actor     audit.Actor
	SourceIP  string
	Principal *authz.Principal
}

Who identifies the person or process behind an operation: the audit log records Actor and SourceIP, the services check Principal (nil = denied).

Jump to

Keyboard shortcuts

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