psp

package
v0.19.0 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

Documentation

Overview

Package psp implements the PSP-model cryptographic primitives shared by the icx data plane (package icx) and the key-establishment control plane (package control): the NIST SP 800-108 / AES-CMAC key-derivation function from the PSP Architecture Specification, and the SPI bit layout that partitions the derivation space by master key and allocating role.

The package is a stdlib-only leaf so the data-plane handler can derive its own security-association keys without importing the control plane (and its QUIC/x509 dependency tree).

Index

Constants

View Source
const MasterKeyLen = 32

MasterKeyLen is the required length of a PSP master key (256 bits). PSP master keys are always AES-256 keys regardless of the SA key size.

View Source
const NumMasterKeys = 2

NumMasterKeys is the PSP master-key count: one active, one retained for in-flight SAs during rotation (the MSB of the SPI selects between them).

View Source
const (

	// SPICounterMax is the largest usable per-(index,role) counter value
	// (2^30-1); 0 is reserved.
	SPICounterMax = (uint32(1) << spiRoleShift) - 1
)

SPI bit layout (PSP keeps the SPI opaque except for the MSB master-key selector; we additionally reserve one bit to partition by allocating role):

bit31      master-key index (PSP)
bit30      allocating role (0=initiator, 1=responder)
bits[29:0] per-(index,role) counter, 1..2^30-1 (0 reserved)

Variables

This section is empty.

Functions

func DeriveSAKey

func DeriveSAKey(masterKey []byte, spi uint32, v ICXVersion) ([]byte, error)

DeriveSAKey derives a PSP security-association key from a 256-bit master key and a 32-bit SPI, exactly per the PSP Architecture Specification: a NIST SP 800-108 counter-mode KDF whose PRF is AES-CMAC (see cmac.go). Each PRF input block is the 16-byte concatenation

counter(4) || label(4) || context=SPI(4) || length-in-bits(4)

all in network byte order. A 128-bit key needs one block (counter=1); a 256-bit key needs two (counter=1, counter=2) concatenated.

The caller is responsible for selecting which master key to pass based on the SPI's most-significant bit (the PSP master-key selector); the SPI is fed into the KDF context verbatim, MSB included, so the derivation is bound to it.

func EpochSPIs

func EpochSPIs(local Role, epoch uint32) (rxSPI, txSPI uint32, err error)

EpochSPIs maps a symmetric per-connection (epoch, role) pair onto the SPI layout at master-key index 0: the local receive SPI carries the local role's bit, the transmit SPI the peer role's bit, both with counter = epoch. The two peers of a connection call it with opposite roles and the same epoch and obtain mirrored (rxSPI, txSPI) pairs, so each direction derives a distinct key from the shared master secret. Errors if epoch is 0 or above SPICounterMax; it never truncates.

func MakeSPI

func MakeSPI(masterKeyIndex int, role Role, counter uint32) (uint32, error)

MakeSPI composes an SPI from the active master-key index, the allocating role and a per-(index,role) counter.

func MasterKeyIndex

func MasterKeyIndex(spi uint32) int

MasterKeyIndex returns which master key (0 or 1) an SPI selects: per PSP, the most-significant bit of the SPI.

func ReservedSPI

func ReservedSPI(spi uint32) bool

ReservedSPI reports whether spi is reserved (low 31 bits zero). PSP reserves the all-zero counter in each master-key half; installs must reject it.

Types

type ICXVersion

type ICXVersion uint8

ICXVersion is an AEAD cipher-suite codepoint for an SA. It selects both the AEAD (AES-GCM-128 vs AES-GCM-256) and, via the KDF label, the size of the derived security-association key. It is a local cipher selector, not a wire-format version (that is ProtocolVersion in the control plane).

const (
	// AESGCM128 selects AES-GCM-128: a 16-byte SA key. The ICX default (zero
	// churn to the [16]byte data plane).
	AESGCM128 ICXVersion = 0
	// AESGCM256 selects AES-GCM-256: a 32-byte SA key. The CNSA / 256-bit path.
	AESGCM256 ICXVersion = 1
)

func (ICXVersion) KeyLen

func (v ICXVersion) KeyLen() int

KeyLen returns the derived SA key length in bytes for the version: 16 for AESGCM128, 32 for AESGCM256. Only valid versions reach the KDF (guarded by DeriveSAKey).

func (ICXVersion) Valid

func (v ICXVersion) Valid() bool

Valid reports whether v is a supported cipher suite. Callers must reject unsupported versions before deriving keys (fail-closed), so KeyLen/label are never asked to map an unknown version.

type Role

type Role uint8

Role identifies which peer allocated an SPI. The two directions MUST use distinct SPIs, otherwise both directions would derive the same key (txKey == rxKey). Partitioning the SPI space by role guarantees distinctness even though both peers allocate independently from the shared master keys.

const (
	Initiator Role = iota // canonical lower static key; the connecting side
	Responder             // the accepting side
)

func RoleOf

func RoleOf(spi uint32) Role

RoleOf reports which role allocated an SPI, per the role bit (bit30). It is the inverse of the role argument to MakeSPI and lets a peer validate that an announced RX SPI was allocated by the opposite role, preserving the SPI-space partition that keeps tx and rx keys distinct.

func (Role) Peer

func (r Role) Peer() Role

Peer returns the opposite role.

Jump to

Keyboard shortcuts

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