keymgmt

package
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Jun 12, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package keymgmt holds the safe header-write protocol shared by every operation that mutates the key-slot header. A clobbered or half-written header locks the user out of every blob under it, so writes go through SafePut: back up the current header, confirm it hasn't changed underneath us, write, then read back and verify the result still unlocks before trusting it. RestoreBackup is the recovery counterpart.

This is the one place that needs both the storage layer (backend) and the crypto layer (crypto): the backend handles backup and the raw read/write, crypto parses and unlocks the result. Keeping the orchestration here lets each of those layers stay unaware of the other.

Index

Constants

This section is empty.

Variables

View Source
var ErrEpochChanged = errors.New("the vault's master key changed since this operation started")

ErrEpochChanged reports that the vault's header no longer wraps the master key the caller is holding: a rotation (or a substitution) landed since the caller unlocked. The caller's in-flight work was sealed under a superseded key and must not be left on storage.

View Source
var ErrNoPath = errors.New("no signed rotation path from the pinned master to the current one")

ErrNoPath reports that no chain of valid signed transitions connects the pinned master to the observed one.

Functions

func FollowRotations added in v0.7.0

func FollowRotations(ctx context.Context, store backend.Backend, h *crypto.Header, pinned string, pinnedRevision int, mk *crypto.MasterKey) error

FollowRotations proves, from signed transitions alone, that the master a header now wraps descends legitimately from the master this machine pinned. It searches for a chain of valid transitions from the pinned signing key to the header's, each signed by its predecessor, all within this vault, with strictly increasing revisions that stay within the pinned revision's future and end no later than the observed header's. mk is the unlocked master the header yielded; the final hop must name exactly it, binding the chain to the key actually in hand rather than to header metadata.

A nil error means the pin may advance to the observed header silently. Any failure returns ErrNoPath: the caller falls back to the alarm it would have raised anyway — walking can clear an alarm, never create one.

func RestoreBackup

func RestoreBackup(ctx context.Context, store backend.HeaderStore) error

RestoreBackup copies the header backup back over the header and confirms the result parses, so recovery from a bad write is one command instead of a raw rclone incantation. It reports a clear error when there is no backup to restore, which includes versioned remotes (recover a prior object version through the remote's version history there).

func RotateMaster

func RotateMaster(ctx context.Context, store Vault, hdr *crypto.Header, base []byte, oldMK *crypto.MasterKey, verify func(*crypto.Header) (*crypto.MasterKey, error), onFlip func(*crypto.MasterKey)) (*crypto.MasterKey, error)

RotateMaster re-keys a vault: it mints a fresh master, re-encrypts every manifest-recorded blob under it, and rewrites the header to wrap the new master under hdr's slots — with the manifest re-keyed in the same write, since the object MACs are derived from the master. hdr carries the desired slot set (unchanged for a precautionary rotate, minus a slot for offboarding) and the manifest the caller verified; base is the current header bytes (freshness); oldMK the current master; verify re-unlocks the new header with the operator's credential. Returns the new master.

It never leaves a reader unable to decrypt, via a three-phase transition that keeps the invariant "the master the header yields is a recipient of every recorded blob" at all times:

  1. widen — re-encrypt every recorded blob to {old, new}, header still yields old. Each blob's plaintext is checked against its manifest MAC first: a reverted or substituted object must never be laundered into the new epoch.
  2. flip — write the new header (yields new, manifest re-MAC'd under new); every recorded blob already has new.
  3. narrow — re-encrypt every recorded blob to {new} only.

Writes racing the rotation resolve through the header compare-and-swap. A concurrent writer's manifest update either lands before the flip — bumping the revision, so the flip's freshness check aborts the rotation (re-run) — or reaches the swap after the flip, sees the new master, and rolls its own write back. An object whose manifest update never ran (its writer crashed, or is about to roll back) is deliberately left alone: re-keying or adopting it here would race the writer's own rollback deletion, and the next fold classifies whatever remains. Objects deleted while rotating (a concurrent compaction folding them away) are skipped; the compaction's own manifest update arbitrates through the same swap.

A crash loses the in-memory new master, but the operation is re-runnable from any state: a re-run re-keys from whatever the header currently yields.

onFlip, if non-nil, is called with the new master immediately after the header flip succeeds (before the narrow pass). The caller uses it to advance its local rollback pin to the new master at the moment it becomes authoritative, so an interrupted narrow doesn't leave the pin behind the header (which would make a re-run look like a rollback).

func SafePut

func SafePut(ctx context.Context, store backend.HeaderStore, h *crypto.Header, base []byte, mk *crypto.MasterKey, verify func(*crypto.Header) (*crypto.MasterKey, error)) error

SafePut writes header h with the full safety protocol around it. It bumps h's revision, seals it (authentication tag) with mk, and writes the marshaled result.

  • base is the exact header bytes read at the start of the operation (nil if the storage had no header yet). The write goes through the store's SwapHeader, which refuses (backend.ErrHeaderChanged) if the stored header no longer matches base, so a concurrent header mutation can't be silently overwritten. How atomic that is depends on the store; callers that can re-apply their change retry on ErrHeaderChanged.
  • mk is the master the header should wrap; SafePut seals with it and, after writing, confirms the read-back header's authentication tag verifies under it (catches a corrupted/substituted write).
  • verify re-unlocks the read-back header with the caller's own credential (a passphrase or an age identity) and must yield mk, an end-to-end check that the header is still usable by the caller before they walk away.

On any verification failure SafePut returns an error and leaves the backup in place; recover with RestoreBackup.

func UpdateManifest added in v0.8.0

func UpdateManifest(ctx context.Context, store backend.HeaderStore, mk *crypto.MasterKey, delta crypto.ManifestDelta) (*crypto.Header, error)

UpdateManifest records one writer's manifest delta in the vault header, under the header compare-and-swap. It is the write-epoch check and the manifest update in one authenticated step: every attempt re-reads the header, requires it to wrap mk, and verifies its tag, so a rotation that landed since the caller unlocked surfaces as ErrEpochChanged before anything is written — the caller removes the object it just stored and re-runs. When another writer merely lands first (backend.ErrHeaderChanged), the delta is re-applied to the fresh header and the swap retried, so concurrent writers' entries are never clobbered. Returns the header as written, for the caller's local rollback pin.

The header's recipient field is attacker-writable in principle (it is only authenticated by a tag keyed from the master it names), so a mismatch is treated as "redo the unlock ceremony" — which runs the real pin and authentication checks — never as a reason to trust anything new here. When the recipient does match mk, the tag must verify under it, so a tampered header cannot pass as "unchanged".

Types

type Vault

type Vault interface {
	backend.Backend
	backend.HeaderStore
}

Vault is a backend that holds both the ciphertext objects and the key header, which rotation needs together.

Jump to

Keyboard shortcuts

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