Help us shape the future of Go! Take the 2026 Go Developer Survey

utils

package
v0.1.5 Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
const DefaultTenantDatabasePrefix = "tenant"

DefaultTenantDatabasePrefix names tenant databases when DATABASE_PREFIX is unset. It is what every tenant was called before the prefix was configurable, so leaving the setting out changes nothing.

View Source
const MasterKeyLen = 32

Variables

View Source
var ErrNotSealed = errors.New("value is not sealed")
View Source
var LegacyKeyID = uuid.NewSHA1(uuid.NameSpaceOID, []byte("apiary/encryption/legacy"))

LegacyKeyID identifies the single ENCRYPTION_MASTER_KEY, for deployments that have not configured a key registry.

.env describes the intended end state: ENCRYPTION_KEYS holds several key-encryption keys as id:base64 pairs, "and leaving both blank keeps using the single master key above, which is read as the key with id legacy". Until that registry exists, sealing still needs a stable identifier — utils.Seal refuses uuid.Nil precisely so that a rotation can tell re-wrapped rows from un-rewrapped ones, and a zero id would make rotation unverifiable.

Derived rather than generated so it is identical in every process and across restarts; a random id per boot would tag rows with an identifier that means nothing afterwards.

Functions

func AAD

func AAD(table string, orgID, rowID uuid.UUID) []byte

func DecodeB64

func DecodeB64(s string) ([]byte, error)

func DecryptAESGCM

func DecryptAESGCM(cipherNoTag, key, iv, tag []byte) ([]byte, error)

func DecryptAESGCMWithAAD

func DecryptAESGCMWithAAD(cipherNoTag, key, iv, tag, aad []byte) ([]byte, error)

func DeriveKey

func DeriveKey(master []byte, purpose KeyPurpose) ([]byte, error)

func DeriveKeyFromBase64

func DeriveKeyFromBase64(encoded string, purpose KeyPurpose) ([]byte, error)

func EncodeB64

func EncodeB64(b []byte) string

func EncryptAESGCM

func EncryptAESGCM(plaintext, key []byte) (cipherNoTag, iv, tag []byte, _ error)

func EncryptAESGCMWithAAD

func EncryptAESGCMWithAAD(plaintext, key, aad []byte) (cipherNoTag, iv, tag []byte, _ error)

func MasterKeyFromBase64

func MasterKeyFromBase64(encoded string) ([]byte, error)

func RandomBytes

func RandomBytes(n int) ([]byte, error)

func TenantDatabasePrefix added in v0.1.5

func TenantDatabasePrefix(prefix string) string

TenantDatabasePrefix normalizes a configured DATABASE_PREFIX.

The one place it happens, so the worker's startup validation checks exactly the value tenants are then named from. A trailing underscore is dropped because the separator is added by the caller: both "apiary" and "apiary_" name databases apiary_<uuid>, never apiary__<uuid>.

Types

type KeyPurpose

type KeyPurpose string
const (
	// These are the `info` parameter of the HKDF that derives every key in the
	// system, so the STRING IS PART OF THE KEY. They are not labels.
	//
	// The /v2 suffix records that they were renamed from an "eyrie/" prefix on
	// 2026-08-23, which re-derived every key. Anything sealed under the v1
	// derivation is unreadable to this build:
	//
	//   TenantDEK  sealed credentials in tenant databases
	//   TenantDSN  tenant connection strings — the databases become unreachable
	//   TOTPSeed   enrolled authenticators
	//
	// Nothing errors when this changes; the data simply stops opening. The
	// version bump exists so the break is legible rather than mysterious.
	//
	// CHANGING ONE AGAIN IS A KEY-ROTATION EVENT, not an edit: every affected row
	// must be decrypted under the old purpose and re-sealed under the new one,
	// with dual-read during rollout, BEFORE the constant moves. A global
	// find/replace reaches these — that is exactly how the 2026-08-23 change
	// happened, and it was caught only by
	// TestUCRDeriveKeyIsPinnedToItsExactConstruction, which pins derived bytes
	// rather than strings.
	KeyPurposeTOTPSeed  KeyPurpose = "apiary/mfa/totp-seed/v2"
	KeyPurposeTenantDEK KeyPurpose = "apiary/tenant/dek/v2"
	KeyPurposeTenantDSN KeyPurpose = "apiary/tenant/dsn/v2"

	// KeyPurposeQueuePayload seals the body of a queued email.
	//
	// The prefix differs from the three above, and that is deliberate rather than
	// an oversight. These strings are HKDF info parameters: changing one changes
	// every key derived under it, so the three above are frozen — renaming them
	// would make every sealed credential, every tenant DSN and every TOTP seed
	// permanently unreadable. Nothing has ever been sealed under this new purpose,
	// so it is free to carry the correct name, and new purposes should.
	KeyPurposeQueuePayload KeyPurpose = "apiary/queue/payload/v1"
)

type Sealed

type Sealed struct {
	KeyID uuid.UUID `gorm:"type:uuid" json:"-"`

	Ciphertext string `gorm:"type:text;not null;default:''" json:"-"`
	IV         string `gorm:"type:text;not null;default:''" json:"-"`
	Tag        string `gorm:"type:text;not null;default:''" json:"-"`
}

func Seal

func Seal(plaintext []byte, keyID uuid.UUID, key, aad []byte) (Sealed, error)

func (Sealed) IsZero

func (s Sealed) IsZero() bool

func (Sealed) Open

func (s Sealed) Open(key, aad []byte) ([]byte, error)

Jump to

Keyboard shortcuts

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