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
- Variables
- func CheckPassphrase(p []byte) error
- func ConfirmIndexes(rnd io.Reader) ([2]int, error)
- func HasNonASCII(p []byte) bool
- func IsTerminal(f *os.File) bool
- func SaveKey(ctx context.Context, q pkidb.DBTX, key StoredKey, now string) error
- func TestArgon2() (timeCost, memoryKiB uint32, threads uint8)
- func WriteRecoveryKeyFile(path string, k RecoveryKey) (err error)
- type Envelope
- type Keyring
- func (k *Keyring) ChangePassphrase(ctx context.Context, current, next []byte, who Who) error
- func (k *Keyring) CheckpointKeyFingerprint() (string, error)
- func (k *Keyring) EncryptKey(keyID, purpose, algorithm string, pkcs8 []byte) (Envelope, error)
- func (k *Keyring) Init(ctx context.Context, passphrase []byte, recovery RecoveryKey, who Who) error
- func (k *Keyring) Initialized(ctx context.Context) (bool, error)
- func (k *Keyring) ResetPassphrase(ctx context.Context, recovery RecoveryKey, next []byte, who Who) error
- func (k *Keyring) RotateRecoveryKey(ctx context.Context, passphrase []byte, next RecoveryKey, who Who) error
- func (k *Keyring) Seal(ctx context.Context, who Who) error
- func (k *Keyring) SignCheckpoint(msg []byte) ([]byte, ed25519.PublicKey, error)
- func (k *Keyring) Status() Status
- func (k *Keyring) Unseal(ctx context.Context, passphrase []byte, who Who) error
- func (k *Keyring) UnsealWithRecovery(ctx context.Context, recovery RecoveryKey, who Who) error
- func (k *Keyring) VerifyPassphrase(ctx context.Context, passphrase []byte, who Who) error
- func (k *Keyring) VerifyRecoveryKey(ctx context.Context, recovery RecoveryKey, who Who) error
- func (k *Keyring) WithKey(keyID, purpose, algorithm string, env Envelope, fn func(pkcs8 []byte) error) error
- func (k *Keyring) WithSigner(keyID, purpose, algorithm string, env Envelope, fn func(crypto.Signer) error) error
- type LockedError
- type Option
- type RecoveryKey
- func (k RecoveryKey) CheckGroups(typed map[int]string) bool
- func (RecoveryKey) GoString() string
- func (k RecoveryKey) Groups() []string
- func (RecoveryKey) MarshalJSON() ([]byte, error)
- func (RecoveryKey) MarshalText() ([]byte, error)
- func (k RecoveryKey) Reveal() string
- func (RecoveryKey) String() string
- type RecoveryOutput
- type Status
- type StoredKey
- type Who
Constants ¶
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 ¶
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.
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.
var ErrKeyDestroyed = errors.New("the key was deleted from the server")
ErrKeyDestroyed: the key was destroyed after its download (step 18).
var ErrNoSuchKey = errors.New("no such key")
ErrNoSuchKey means there is no key with that ID.
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).
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 ¶
CheckPassphrase applies the passphrase rules: at least 16 characters, at most 1024 bytes, valid UTF-8.
func ConfirmIndexes ¶
ConfirmIndexes picks two different group positions (0–8, ascending) the user must type again before init or a rotation finishes.
func HasNonASCII ¶
HasNonASCII reports characters that may be typed differently on another keyboard or system; init warns about them.
func IsTerminal ¶
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 ¶
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
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 ¶
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 ¶
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 ¶
CheckpointKeyFingerprint is the fingerprint of the checkpoint key to note down (shown by init, logged at unseal; `audit verify --checkpoint-key`).
func (*Keyring) EncryptKey ¶
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 ¶
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 ¶
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 ¶
Seal removes the root key from memory. It waits for running signing operations to finish. Sealing a sealed keyring does nothing.
func (*Keyring) SignCheckpoint ¶
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) Unseal ¶
Unseal derives the KEK from passphrase, unlocks the root key and keeps it in memory. passphrase is cleared.
func (*Keyring) UnsealWithRecovery ¶
UnsealWithRecovery unlocks the root key with the recovery key.
func (*Keyring) VerifyPassphrase ¶
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 ¶
VerifyRecoveryKey is VerifyPassphrase with the recovery key.
type LockedError ¶
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.