envelope

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package envelope implements keyforge's self-describing ciphertext format: its binary and text forms, the authenticated header, and sealing and opening of the payload.

The format is frozen once data exists. Any change needs a new format version, new known-answer vectors in testdata, and a fuzz run. Unknown versions, modes, algorithms and flags fail with typed errors; the parser never guesses.

Layout

Version 1, big-endian:

magic "KF" | format u8 | mode u8 | algorithm u8 | flags u8
purpose   : len u8 + bytes
reference : dek_id 16 bytes                          (reference mode)
inline    : kek_id 16 bytes, kek_version u32,
            wrapped_len u16, wrapped                 (inline mode)
nonce     : 12 bytes
ciphertext || tag

The additional authenticated data is the header bytes up to the nonce, followed by the caller's context, so changing the purpose, key, mode, algorithm or context makes decryption fail.

The text form is "kf1." followed by unpadded base64url of the binary form.

Index

Constants

View Source
const (
	// FormatVersion is the only format version this build writes and reads.
	FormatVersion uint8 = 1

	// NonceSize is the size of the nonce field in format 1.
	NonceSize = 12

	// MaxPurposeLen is the longest purpose name, bounded by its u8 length.
	MaxPurposeLen = 255
	// MaxWrappedLen is the longest wrapped data key in inline mode, bounded
	// by its u16 length.
	MaxWrappedLen = 65535
)

Variables

View Source
var (
	// ErrBadMagic is returned when the input does not begin with "KF".
	ErrBadMagic = errors.New("envelope: bad magic")
	// ErrTruncated is returned when the input ends before the structure it
	// describes.
	ErrTruncated = errors.New("envelope: truncated input")
	// ErrUnsupportedFormat is returned for a format version this build does
	// not implement.
	ErrUnsupportedFormat = errors.New("envelope: unsupported format version")
	// ErrUnknownMode is returned for a mode this build does not implement.
	ErrUnknownMode = errors.New("envelope: unknown mode")
	// ErrUnknownAlgorithm is returned for an algorithm identifier that is not
	// in the registry.
	ErrUnknownAlgorithm = errors.New("envelope: unknown algorithm")
	// ErrUnsupportedFlags is returned when any flag bit is set. All bits are
	// reserved in format 1 and every bit assigned later is critical.
	ErrUnsupportedFlags = errors.New("envelope: unsupported flags")
	// ErrInvalidPurpose is returned for an empty or malformed purpose name.
	ErrInvalidPurpose = errors.New("envelope: invalid purpose")
	// ErrInvalidHeader is returned when a header's fields are inconsistent
	// with its mode.
	ErrInvalidHeader = errors.New("envelope: invalid header")
	// ErrBadTextForm is returned when text input does not have the
	// "kf<version>." prefix or is not valid unpadded base64url.
	ErrBadTextForm = errors.New("envelope: bad text form")
	// ErrAuthentication is returned when the payload does not authenticate:
	// a wrong key, a changed header or context, or a modified ciphertext.
	// It deliberately carries no further detail.
	ErrAuthentication = errors.New("envelope: authentication failed")
	// ErrNotRandomNonce is returned when Seal or Open is given an AEAD that
	// takes an explicit nonce. The format stores a random nonce generated by
	// the AEAD, and nothing else is accepted.
	ErrNotRandomNonce = errors.New("envelope: AEAD must generate its own nonce")
)

Typed errors. Callers match them with errors.Is. Errors that carry the offending value are *ValueError; use errors.As to read it.

Functions

func ValidPurpose

func ValidPurpose(p string) bool

ValidPurpose reports whether p is a legal purpose name: 1 to 255 bytes of lowercase ASCII letters, digits, '.', '_' and '-', beginning with a letter or digit.

Types

type Envelope

type Envelope struct {
	Header
	Nonce      [NonceSize]byte
	Ciphertext []byte // ciphertext || tag
}

Envelope is a parsed or sealed ciphertext: the header, the nonce and the ciphertext with its authentication tag.

func Parse

func Parse(b []byte) (*Envelope, error)

Parse decodes the binary form. It fails with a typed error for any input it does not fully understand: it never guesses and never reads past the input.

func ParseText

func ParseText(s string) (*Envelope, error)

ParseText decodes the text form. The prefix must be exactly "kf1."; a well-formed prefix for another version fails with ErrUnsupportedFormat. Padding, line breaks and non-canonical base64url are rejected.

func Seal

func Seal(aead cipher.AEAD, h Header, plaintext, context []byte) (*Envelope, error)

Seal encrypts plaintext under aead and returns the envelope for header h. The header and context are authenticated: opening needs the same header and the same context.

aead must generate its own nonce, as the AEAD returned by algorithm.New does; anything that takes an explicit nonce is refused so that a nonce can never be chosen by a caller.

func (*Envelope) MarshalBinary

func (e *Envelope) MarshalBinary() ([]byte, error)

MarshalBinary returns the binary form of the envelope.

func (*Envelope) MarshalText

func (e *Envelope) MarshalText() ([]byte, error)

MarshalText returns the text form: "kf1." followed by unpadded base64url of the binary form. It implements encoding.TextMarshaler, so an Envelope is a string in JSON.

func (*Envelope) Open

func (e *Envelope) Open(aead cipher.AEAD, context []byte) ([]byte, error)

Open authenticates and decrypts the envelope under aead with the same context that was given to Seal. Any failure to authenticate, whether a wrong key, a changed header or context, or a modified ciphertext, is ErrAuthentication with no further detail.

func (*Envelope) String

func (e *Envelope) String() string

String returns the text form, or an empty string if the envelope is not valid.

func (*Envelope) UnmarshalText

func (e *Envelope) UnmarshalText(text []byte) error

UnmarshalText parses the text form. It implements encoding.TextUnmarshaler.

type Header struct {
	Mode      Mode
	Algorithm algorithm.ID
	Purpose   string

	DEKID ID // reference mode

	KEKID      ID     // inline mode
	KEKVersion uint32 // inline mode
	Wrapped    []byte // inline mode
}

Header is the authenticated, self-describing part of an envelope.

Which fields are used depends on Mode. In reference mode DEKID names the data key and the inline fields must be zero. In inline mode KEKID, KEKVersion and Wrapped describe the wrapped data key and DEKID must be zero.

func (*Header) AAD

func (h *Header) AAD(context []byte) ([]byte, error)

AAD returns the additional authenticated data for the header and the caller's context: the header bytes up to the nonce, followed by the context. The header is self-delimiting, so the concatenation is unambiguous.

func (*Header) Validate

func (h *Header) Validate() error

Validate checks that the header is one this build can write: a known mode and algorithm, a legal purpose, and fields consistent with the mode.

type ID

type ID [16]byte

ID is a 16-byte key identifier.

func (ID) IsZero

func (id ID) IsZero() bool

IsZero reports whether the identifier is all zero bytes.

func (ID) MarshalText

func (id ID) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler with the hex form, so an identifier is a string in JSON rather than an array of numbers.

func (*ID) Scan

func (id *ID) Scan(src any) error

Scan implements [sql.Scanner].

func (ID) String

func (id ID) String() string

String returns the identifier as lowercase hex.

func (*ID) UnmarshalText

func (id *ID) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler. It accepts only the 32-character hex form.

func (ID) Value

func (id ID) Value() (driver.Value, error)

Value implements driver.Valuer: an identifier is stored as its 16 raw bytes, which both SQLite and PostgreSQL compare bytewise, so a database orders identifiers exactly as bytes.Compare does.

type Mode

type Mode uint8

Mode says where the wrapped data key lives.

const (
	// ModeReference names the data key by identifier. The wrapped key is held
	// by the registry, so rotating the key-encryption key re-wraps registry
	// records and stored data is not touched.
	ModeReference Mode = 1
	// ModeInline carries the wrapped data key, its key-encryption key and
	// version beside the ciphertext, for standalone objects.
	ModeInline Mode = 2
)

func (Mode) String

func (m Mode) String() string

String returns the mode's name.

type ValueError

type ValueError struct {
	Kind  error
	Value uint8
}

ValueError is a typed error that carries the offending value, for example the format version that was not recognized.

func (*ValueError) Error

func (e *ValueError) Error() string

Error implements error.

func (*ValueError) Unwrap

func (e *ValueError) Unwrap() error

Unwrap lets errors.Is match the sentinel in Kind.

Jump to

Keyboard shortcuts

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