keyset

package
v0.85.0-pre.2 Latest Latest
Warning

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

Go to latest
Published: Mar 3, 2026 License: BSD-3-Clause Imports: 6 Imported by: 0

README

kit/keyset

github.com/vormadev/vorma/kit/keyset

keyset is a latest-first key rotation primitive for symmetric crypto workflows.

Use it to:

  • load root secrets from environment variables
  • validate and access active/previous keys
  • derive purpose-specific keysets with HKDF
  • attempt verify/decrypt across rotated keys

Import

import "github.com/vormadev/vorma/kit/keyset"

Core Concepts

  • RootSecret: one base64-encoded 32-byte secret string
  • RootSecrets: latest-first list of root secrets (current, then previous...)
  • UnwrappedKeyset: latest-first list of cryptoutil.Key32
  • Keyset: wrapper around UnwrappedKeyset with validation helpers

Ordering matters: index 0 is always the active key used for new writes.

Quick Start (Env-Backed)

root, err := keyset.LoadRootKeyset("APP_SECRET_CURRENT", "APP_SECRET_PREVIOUS")
if err != nil {
	return err
}

activeKey, err := root.First()
if err != nil {
	return err
}
_ = activeKey

Rotation Fallback (Attempt)

Use Attempt for read paths where payloads may have been created with older keys:

value, err := keyset.Attempt(root, func(k cryptoutil.Key32) (string, error) {
	return decryptWithKey(k, ciphertext)
})
if err != nil {
	return err
}
_ = value

Behavior:

  • stops at first success
  • if all keys fail, returns errors.Join(...) of per-key failures
  • returns error immediately for nil/empty keysets or nil key entries

HKDF Derivation

Derive a parallel latest-first keyset for a specific purpose:

cookieKeys, err := root.HKDF([]byte("my-app"), "cookies")
if err != nil {
	return err
}
_ = cookieKeys

Each root key is derived independently, preserving order.

High-Level Lazy API (AppKeyset)

MustAppKeyset wraps env loading + HKDF in lazy getters:

appKeys := keyset.MustAppKeyset(keyset.AppKeysetConfig{
	LatestFirstEnvVarNames: []string{"APP_SECRET_CURRENT", "APP_SECRET_PREVIOUS"},
	ApplicationName:        "my-app",
})

root := appKeys.Root()            // lazy load root keyset once
csrfKeys := appKeys.HKDF("csrf") // returns lazy getter
_ = csrfKeys()                    // derive once for this purpose
_ = root

Misconfiguration behavior:

  • MustAppKeyset panics for invalid config
  • DeferPanic: true shifts that panic from constructor-time to first use
  • AppKeyset.HKDF("")() panics (empty purpose)

API Contracts And Footguns

  • Keyset.Unwrap() returns the underlying key slice directly (no defensive copy).
  • Mutating the returned slice mutates the Keyset internals.
  • FromUnwrapped validates input but does not clone it.
  • LoadRootSecrets treats missing or empty env values as errors.

If you need immutability guarantees, copy before sharing.

Public API Reference

Type Aliases / Types
  • type RootSecret = string
  • type RootSecrets []RootSecret
  • type UnwrappedKeyset []cryptoutil.Key32
  • type Keyset struct
  • type AppKeysetConfig struct
  • type AppKeyset struct
Exported Fields (AppKeysetConfig)
  • LatestFirstEnvVarNames []string
  • ApplicationName string
  • DeferPanic bool
Constructors / Functions
  • func FromUnwrapped(uks UnwrappedKeyset) (*Keyset, error)
  • func Attempt[R any](ks *Keyset, f func(cryptoutil.Key32) (R, error)) (R, error)
  • func LoadRootSecrets(latestFirstEnvVarNames ...string) (RootSecrets, error)
  • func RootSecretsToRootKeyset(rootSecrets RootSecrets) (*Keyset, error)
  • func LoadRootKeyset(latestFirstEnvVarNames ...string) (*Keyset, error)
  • func MustAppKeyset(cfg AppKeysetConfig) *AppKeyset
Methods
  • func (wk *Keyset) Validate() error
  • func (wk *Keyset) Unwrap() UnwrappedKeyset
  • func (wk *Keyset) First() (cryptoutil.Key32, error)
  • func (ks *Keyset) HKDF(salt []byte, info string) (*Keyset, error)
  • func (ak *AppKeyset) Root() *Keyset
  • func (ak *AppKeyset) HKDF(purpose string) func() *Keyset

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Attempt

func Attempt[R any](ks *Keyset, f func(cryptoutil.Key32) (R, error)) (R, error)

Attempt runs the provided function for each key in the keyset until either (i) an attempt does not return an error (meaning it succeeded) or (ii) all keys have been attempted. This is useful when you want to fallback to a prior key if the current key fails due to a recent rotation.

Types

type AppKeyset

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

func MustAppKeyset

func MustAppKeyset(cfg AppKeysetConfig) *AppKeyset

Panics if anything is misconfigured. If desired, you can defer the panic to the first use (rather than at instantiation) by passing in a true boolean as the second argument.

func (*AppKeyset) HKDF

func (ak *AppKeyset) HKDF(purpose string) func() *Keyset

func (*AppKeyset) Root

func (ak *AppKeyset) Root() *Keyset

type AppKeysetConfig

type AppKeysetConfig struct {
	// Provide a latest-first slice of environment variable names pointing
	// to base64-encoded 32-byte root secrets.
	// Example: []string{"CURRENT_SECRET", "PREVIOUS_SECRET"}
	LatestFirstEnvVarNames []string
	// Passed into the salt parameter of downstream HKDF functions.
	// Once set, do not change this unless you want and entirely new keyset.
	ApplicationName string
	// When instantiated via MustAppKeyset, if this is true, panics
	// due to misconfiguration are deferred to the first use of the
	// keyset rather than at instantiation time.
	DeferPanic bool
}

type Keyset

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

func FromUnwrapped

func FromUnwrapped(uks UnwrappedKeyset) (*Keyset, error)

func LoadRootKeyset

func LoadRootKeyset(latestFirstEnvVarNames ...string) (*Keyset, error)

Pass in a latest-first slice of environment variable names pointing to base64-encoded 32-byte root secrets. Example: LoadRootKeyset("CURRENT_SECRET", "PREVIOUS_SECRET")

func RootSecretsToRootKeyset

func RootSecretsToRootKeyset(rootSecrets RootSecrets) (*Keyset, error)

RootSecretsToRootKeyset converts a slice of base64-encoded root secrets into a Keyset.

func (*Keyset) First

func (wk *Keyset) First() (cryptoutil.Key32, error)

First returns the first key in the keyset and returns an error if the keyset is nil or empty or if the first key is nil.

func (*Keyset) HKDF

func (ks *Keyset) HKDF(salt []byte, info string) (*Keyset, error)

Keyset.HKDF applies HKDF to each key in the base Keyset using the provided salt and info string, returning a new Keyset consisting of the derived keys.

func (*Keyset) Unwrap

func (wk *Keyset) Unwrap() UnwrappedKeyset

Unwrap returns the underlying UnwrappedKeyset, which is a latest-first slice of size 32 byte array pointers.

func (*Keyset) Validate

func (wk *Keyset) Validate() error

type RootSecret

type RootSecret = string

Base64-encoded 32-byte root secret. To generate new root secrets, run `openssl rand -base64 32`.

type RootSecrets

type RootSecrets []RootSecret

Latest-first slice of base64-encoded 32-byte root secrets. To generate new root secrets, run `openssl rand -base64 32`.

func LoadRootSecrets

func LoadRootSecrets(latestFirstEnvVarNames ...string) (RootSecrets, error)

Pass in a latest-first slice of environment variable names pointing to base64-encoded 32-byte root secrets. Example: LoadRootSecrets("CURRENT_SECRET", "PREVIOUS_SECRET")

type UnwrappedKeyset

type UnwrappedKeyset []cryptoutil.Key32

Latest-first slice of size 32 byte array pointers

Jump to

Keyboard shortcuts

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