crypto

package
v0.0.0-...-ff9acf4 Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2026 License: AGPL-3.0 Imports: 14 Imported by: 0

Documentation

Overview

Package crypto implements the IKEv2 cryptographic primitives and proposal negotiation.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrDecryptionFailed = errors.New("decryption failed: authentication tag mismatch")
	ErrInvalidKeyLength = errors.New("invalid key length")
	ErrIntegrityFailed  = errors.New("integrity verification failed")
)
View Source
var (
	ErrInvalidPublicKey = errors.New("invalid DH public key")
	ErrUnsupportedGroup = errors.New("unsupported DH group")
	// ErrPublicKeyLength reports a peer Diffie-Hellman value whose length is not the
	// length of the group modulus. RFC 7296 Section 3.4 makes that pad an obligation of
	// the sender. A value of another length therefore names another group, or a peer
	// that did not pad. Both cases end in a secret the two sides do not share, so the
	// exponentiation never runs. It wraps ErrInvalidPublicKey. A value of the wrong
	// length is an invalid public key, so a caller that tests for the general case
	// still matches this one.
	ErrPublicKeyLength = fmt.Errorf("%w: length does not match the group modulus", ErrInvalidPublicKey)
	// ErrClearedExchange reports a shared-secret computation attempted after the
	// exchange forgot its private value.
	//
	// RFC 7296 Section 2.12 requires a closed connection to forget the information that
	// could recompute its keys, and SA.forgetKeys clears exactly this. A caller reaching
	// SharedSecret afterwards would exponentiate with no exponent, so the answer is an
	// error rather than a value neither side shares.
	ErrClearedExchange = errors.New("diffie-hellman exchange has been cleared")
)
View Source
var (
	// ErrProposalIncomplete reports a proposal that omits a mandatory transform
	// type (RFC 7296 Section 3.3.6).
	ErrProposalIncomplete = fmt.Errorf("%w: proposal omits a mandatory transform type", ErrNoProposalChosen)
	// ErrTransformUnspecified reports a transform identifier this implementation
	// has no specification for (RFC 7296 Sections 3.3.6 and 3.14).
	ErrTransformUnspecified = fmt.Errorf("%w: transform has no specification", ErrNoProposalChosen)
	// ErrTransformTypeNotUnderstood reports a proposal that carries a Transform Type the
	// protocol does not use (RFC 7296 Sections 3.3.3 and 3.3.6).
	ErrTransformTypeNotUnderstood = fmt.Errorf("%w: proposal carries a transform type this protocol does not use", ErrNoProposalChosen)
	// ErrKeyLengthMissing reports a transform that requires a Key Length attribute
	// and carries none (RFC 7296 Section 3.3.5).
	ErrKeyLengthMissing = fmt.Errorf("%w: transform requires a key length attribute", ErrNoProposalChosen)
	// ErrDHGroupNone reports a Diffie-Hellman group of NONE offered for an IKE SA
	// (RFC 7296 Section 2.18).
	ErrDHGroupNone = fmt.Errorf("%w: Diffie-Hellman group NONE is not allowed for an IKE SA", ErrNoProposalChosen)
	// ErrPRFTooWeak reports a PRF whose output is below the floor RFC 7296
	// Section 5 sets.
	ErrPRFTooWeak = fmt.Errorf("%w: PRF output is below 128 bits", ErrNoProposalChosen)
)
View Source
var ErrNoProposalChosen = errNoProposalChosen{}

ErrNoProposalChosen is the refusal the responder reports to the peer. It becomes the NO_PROPOSAL_CHOSEN notification. Every reason below wraps it. A caller that asks only whether negotiation failed keeps working. A caller that wants the reason reads it with errors.Is.

View Source
var ErrUnsupportedAlgorithm = errors.New("unsupported algorithm")

Functions

func ComputeIntegrity

func ComputeIntegrity(id IntegrityID, key, data []byte) ([]byte, error)

ComputeIntegrity computes a truncated HMAC for the given data.

func DecryptAESCBCRaw

func DecryptAESCBCRaw(key, data []byte) ([]byte, error)

DecryptAESCBCRaw decrypts without unpadding (caller handles IKEv2 padding). Input is iv(blockSize) || ciphertext.

func DecryptIKEAEAD

func DecryptIKEAEAD(keyWithSalt, data, aad []byte) ([]byte, error)

DecryptIKEAEAD decrypts using the IKEv2 AEAD construction (RFC 5282). keyWithSalt is the AEAD key material: AES key || 4-byte salt. data is IV(8) || ciphertext || tag(16) from the wire.

func DeriveRekeyedSKEYSEED

func DeriveRekeyedSKEYSEED(prfID PRFID, skDOld, newSharedSecret, ni, nr []byte) ([]byte, error)

DeriveRekeyedSKEYSEED computes the seed key for a rekeyed IKE SA. RFC 7296 Section 2.18: SKEYSEED = prf(SK_d_old, g^ir_new | Ni | Nr).

func DeriveSKEYSEED

func DeriveSKEYSEED(prfID PRFID, ni, nr, sharedSecret []byte) ([]byte, error)

DeriveSKEYSEED computes the initial IKE SA seed key. RFC 7296 Section 2.14: SKEYSEED = prf(Ni | Nr, g^ir).

func PRF

func PRF(id PRFID, key, data []byte) ([]byte, error)

PRF computes prf(key, data) using the selected HMAC algorithm.

func PRFPlus

func PRFPlus(id PRFID, key, seed []byte, length int) ([]byte, error)

PRFPlus implements RFC 7296 Section 2.13 prf+ key expansion. T1 = prf(K, S | 0x01) T2 = prf(K, T1 | S | 0x02) T3 = prf(K, T2 | S | 0x03) ... prf+(K, S) = T1 | T2 | T3 | ...

func SupportedDHGroupIDs

func SupportedDHGroupIDs() []uint8

SupportedDHGroupIDs lists every Diffie-Hellman group this build implements, in ascending order. RFC 7296 Section 3.3.2 assigns Transform Type 4 a far wider number space than any build carries. The config parser therefore names this list in the error it returns for a group it refuses, so the two can never disagree (ai/rules/evidence.md).

func SupportedEncryptionNames

func SupportedEncryptionNames() []string

SupportedEncryptionNames lists every encryption algorithm this build implements, in sorted order. The config parser names the list in the error it returns for an algorithm it refuses, so the two can never disagree (ai/rules/evidence.md).

func SupportedIntegrityNames

func SupportedIntegrityNames() []string

SupportedIntegrityNames lists every integrity algorithm this build implements, in sorted order. SupportedEncryptionNames gives the reason it is derived.

func SupportedPRFNames

func SupportedPRFNames() []string

SupportedPRFNames lists every PRF this build implements, in sorted order.

func TransformTypeUnderstoodESP

func TransformTypeUnderstoodESP(tt TransformType) bool

TransformTypeUnderstoodESP reports whether an ESP proposal uses this transform type, on the same terms as TransformTypeUnderstoodIKE.

func TransformTypeUnderstoodIKE

func TransformTypeUnderstoodIKE(tt TransformType) bool

TransformTypeUnderstoodIKE reports whether an IKE SA proposal uses this transform type. The reader of a wire proposal calls it for every transform it meets. A type that reads false makes the proposal unacceptable under RFC 7296 Section 3.3.6, and the reader records it in UnknownTransformType.

func VerifyIntegrity

func VerifyIntegrity(id IntegrityID, key, data, expected []byte) error

VerifyIntegrity checks a truncated HMAC using constant-time comparison.

Types

type ChildSAKeys

type ChildSAKeys struct {
	EncryptKeyI []byte
	IntegKeyI   []byte
	EncryptKeyR []byte
	IntegKeyR   []byte
}

ChildSAKeys holds ESP keying material for one direction.

func DeriveChildSAKeys

func DeriveChildSAKeys(prfID PRFID, skD, ni, nr []byte, enc EncryptionTransform, integ IntegrityTransform) (*ChildSAKeys, error)

DeriveChildSAKeys derives ESP keys from SK_d and nonces. RFC 7296 Section 2.17: KEYMAT = prf+(SK_d, Ni | Nr).

An AEAD cipher takes four octets of salt beyond its key (RFC 4106 Section 8.1), so encKeyMaterialLen decides the length of each of the four keys below.

func DeriveChildSAKeysPFS

func DeriveChildSAKeysPFS(prfID PRFID, skD, dhSharedSecret, ni, nr []byte, enc EncryptionTransform, integ IntegrityTransform) (*ChildSAKeys, error)

DeriveChildSAKeysPFS derives ESP keys with Perfect Forward Secrecy. RFC 7296 Section 2.17: KEYMAT = prf+(SK_d, g^ir | Ni | Nr).

The AEAD salt rule of DeriveChildSAKeys applies here without change.

func (*ChildSAKeys) Clear

func (k *ChildSAKeys) Clear()

type DHExchange

type DHExchange struct {
	GroupID   DHGroupID
	PublicKey []byte
	// contains filtered or unexported fields
}

func NewDHExchange

func NewDHExchange(groupID DHGroupID) (*DHExchange, error)

func (*DHExchange) Clear

func (ex *DHExchange) Clear()

func (*DHExchange) HasPrivate

func (ex *DHExchange) HasPrivate() bool

HasPrivate reports whether this exchange still holds its private value.

RFC 7296 Section 2.12 requires a closed connection to forget "any information that could be used to recompute those keys", and the private exponent is that information: with the peer's public value it recomputes g^ir, and SKEYSEED with it. The private fields are unexported, so this is how a caller outside the package confirms Clear ran.

func (*DHExchange) SharedSecret

func (ex *DHExchange) SharedSecret(remotePublic []byte) ([]byte, error)

type DHGroupID

type DHGroupID uint16

RFC 7296 Section 3.3.6: DH Group Transform IDs.

const (
	DH_MODP_2048 DHGroupID = 14
	DH_ECP_256   DHGroupID = 19
	DH_ECP_384   DHGroupID = 20
)

func (DHGroupID) String

func (id DHGroupID) String() string

type DHGroupTransform

type DHGroupTransform struct {
	ID DHGroupID
}

func LookupDHGroup

func LookupDHGroup(id uint8) (DHGroupTransform, error)

type ESPProposal

type ESPProposal struct {
	Number     uint16
	Encryption EncryptionTransform
	Integrity  IntegrityTransform
	// UnknownTransformType holds the first Transform Type the peer offered that ESP
	// does not use. The IKE field of the same name records it on the same terms.
	UnknownTransformType TransformType
}

ESPProposal represents a single ESP/Child SA crypto proposal.

func NegotiateESP

func NegotiateESP(accepted, sent []ESPProposal) (ESPProposal, error)

NegotiateESP checks the Child SA offer a responder accepted against the proposals this side sent. It reads the same per-transform terms as VerifyAcceptedIKE. It is the INITIATOR path, and the initiator check is its only caller. RFC 7296 Section 3.3.6 obliges the initiator to check the accepted offer against its own proposals. The Key Length attribute is therefore read under keyLengthExact.

The responder's own Child SA selection does not come through here. It reads the wire proposals directly, in matchOfferedESPProposal (ike/engine/responder.go), because it also needs the Proposal Num that RFC 7296 Section 3.3.1 makes the response echo. That path compares the key length for equality too.

The accepted proposal is returned with the peer's own attributes, which RFC 7296 Section 3.3.6 requires to be returned unmodified.

type EncryptionID

type EncryptionID uint16

RFC 7296 Section 3.3.3: Encryption Algorithm Transform IDs.

const (
	ENCR_AES_CBC    EncryptionID = 12
	ENCR_AES_GCM_16 EncryptionID = 20
)

func (EncryptionID) IsAEAD

func (id EncryptionID) IsAEAD() bool

IsAEAD reports whether this encryption transform combines integrity with encryption. RFC 7296 Section 3.3 makes an integrity transform of NONE the correct value for such a cipher.

The verdict comes from the Transform ID, which is the identity the peer put on the wire. A caller that holds an EncryptionTransform asks this method on the ID rather than read the IsAEAD field. The field is a cached view. A construction site can leave it at its zero value, and that false value reads as a valid "not AEAD" answer (ai/rules/evidence.md). The ID cannot lie in that way.

Membership in aeadSaltBytes IS the AEAD property. A miss means the cipher is not AEAD. A hit gives that cipher's own salt. Neither answer can be a zero value that reads as valid.

func (EncryptionID) String

func (id EncryptionID) String() string

type EncryptionTransform

type EncryptionTransform struct {
	ID        EncryptionID
	KeyLength uint16 // in bits
	IsAEAD    bool
}

EncryptionTransform names one encryption algorithm and the key it takes.

IsAEAD caches the verdict of EncryptionID.IsAEAD for the ID this transform holds. Build a transform with NewEncryptionTransform to keep the two in agreement. Any decision that must be correct reads ID.IsAEAD, never the field.

func LookupEncryption

func LookupEncryption(name string) (EncryptionTransform, error)

func NewEncryptionTransform

func NewEncryptionTransform(id EncryptionID, keyLengthBits uint16) EncryptionTransform

NewEncryptionTransform builds a transform whose IsAEAD field agrees with its ID. Every site that learns an encryption ID at run time uses it, so no site has to remember the AEAD property on its own.

type IKEProposal

type IKEProposal struct {
	Number     uint16
	Encryption EncryptionTransform
	PRF        PRFTransform
	Integrity  IntegrityTransform
	DHGroup    DHGroupTransform
	// PolicyKeyLength holds the encryption key length local policy configured, when the
	// responder accepted a longer key under RFC 7296 Section 3.3.5. It is zero when the
	// accepted key length is the configured one. The caller reports every non-zero value,
	// because the running key then differs from the configured key.
	PolicyKeyLength uint16
	// UnknownTransformType holds the first Transform Type the peer offered that IKE does
	// not use. The reader of a wire proposal sets it from TransformTypeUnderstoodIKE. RFC
	// 7296 Section 3.3.6 makes such a proposal unacceptable, and negotiation refuses it.
	// Zero means every type the peer offered is one IKE uses, because RFC 7296
	// Section 3.3.2 reserves the type zero.
	UnknownTransformType TransformType
}

IKEProposal represents a single IKE SA crypto proposal.

func NegotiateIKE

func NegotiateIKE(remote, local []IKEProposal) (IKEProposal, error)

NegotiateIKE selects one complete set of IKE SA parameters from the offers a peer sends. It is the RESPONDER path. RFC 7296 Section 3.3.6 requires the responder to select a single complete set of parameters, or to reject every offer.

The responder reads the Key Length attribute under keyLengthAtLeast, which RFC 7296 Section 3.3.5 allows: implementers SHOULD accept values that they deem to supply greater security. An accepted key longer than the configured one sets PolicyKeyLength on the result, and the caller reports it.

The initiator MUST NOT use this function. Its own obligation is stricter, and VerifyAcceptedIKE holds it.

func VerifyAcceptedIKE

func VerifyAcceptedIKE(accepted, sent []IKEProposal) (IKEProposal, error)

VerifyAcceptedIKE checks the IKE offer a responder accepted against the proposals this side sent. It is the INITIATOR path. RFC 7296 Section 3.3.6: "The initiator of an exchange MUST check that the accepted offer is consistent with one of its proposals, and if not MUST terminate the exchange".

The check reads the Key Length attribute under keyLengthExact. RFC 7296 Section 3.3.5 states that the attribute returns unchanged, and that an initiator which accepts several key lengths sends one transform for each. A key length this side never sent is therefore an inconsistent answer, above the configured length as much as below it.

type IntegrityID

type IntegrityID uint16

RFC 7296 Section 3.3.5: Integrity Algorithm Transform IDs.

const (
	AUTH_NONE              IntegrityID = 0
	AUTH_HMAC_SHA2_256_128 IntegrityID = 12
	AUTH_HMAC_SHA2_384_192 IntegrityID = 13
	AUTH_HMAC_SHA2_512_256 IntegrityID = 14
)

func (IntegrityID) String

func (id IntegrityID) String() string

type IntegrityTransform

type IntegrityTransform struct {
	ID              IntegrityID
	KeyLength       uint16 // in bytes
	TruncatedLength uint16 // ICV length in bytes
}

func LookupIntegrity

func LookupIntegrity(name string) (IntegrityTransform, error)

type PRFID

type PRFID uint16

RFC 7296 Section 3.3.4: PRF Transform IDs.

const (
	PRF_HMAC_SHA2_256 PRFID = 5
	PRF_HMAC_SHA2_384 PRFID = 6
	PRF_HMAC_SHA2_512 PRFID = 7
)

type PRFTransform

type PRFTransform struct {
	ID           PRFID
	KeyLength    uint16 // in bytes
	OutputLength uint16 // in bytes
}

func LookupPRF

func LookupPRF(name string) (PRFTransform, error)

type SKKeys

type SKKeys struct {
	SK_d  []byte // Key derivation key for Child SA KEYMAT.
	SK_ai []byte // Initiator integrity key.
	SK_ar []byte // Responder integrity key.
	SK_ei []byte // Initiator encryption key.
	SK_er []byte // Responder encryption key.
	SK_pi []byte // Initiator AUTH payload key.
	SK_pr []byte // Responder AUTH payload key.
}

SKKeys holds the IKE SA key hierarchy derived from SKEYSEED.

func DeriveSKKeys

func DeriveSKKeys(prfID PRFID, skeyseed, ni, nr, spiI, spiR []byte, enc EncryptionTransform, integ IntegrityTransform) (*SKKeys, error)

DeriveSKKeys expands SKEYSEED into the full SK_* key hierarchy. RFC 7296 Section 2.14: {SK_d | SK_ai | SK_ar | SK_ei | SK_er | SK_pi | SK_pr} =

prf+(SKEYSEED, Ni | Nr | SPIi | SPIr).

func (*SKKeys) Clear

func (k *SKKeys) Clear()

type TransformType

type TransformType uint8

RFC 7296 Section 3.3.2: Transform Type Values.

const (
	TransformTypeENCR  TransformType = 1
	TransformTypePRF   TransformType = 2
	TransformTypeINTEG TransformType = 3
	TransformTypeDH    TransformType = 4
	TransformTypeESN   TransformType = 5
)

Jump to

Keyboard shortcuts

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