wrap

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: LGPL-2.1, LGPL-2.1-or-later Imports: 12 Imported by: 0

Documentation

Overview

Package wrap implements the OpenVPN control-channel wrappings — the transform tls-auth and tls-crypt apply to every control packet on its way to the wire, and undo on the way back. A Wrapper takes one packet and returns one packet, so a wrap is testable against known-answer vectors without a client, a socket or a handshake. Data packets never reach one: both wraps cover the control channel only, and the caller checks the opcode first.

The wire formats here are *measured*, not read: testdata/vectors.json was captured from an instrumented OpenVPN 2.4.12 and the known-answer tests are the specification. Every reference cited in this package names the pinned 2.4.12 source that capture used, per docs/openvpn3-reference-policy.md §3.3, and says where a fact came from rather than standing in for the vector that checks it (§3.4). See docker/TLS-WRAP-VECTORS.md.

Index

Constants

View Source
const (
	// StaticKeySize is the length of an OpenVPN 2048-bit static key.
	StaticKeySize = 256

	// NameTLSAuth is what Name reports for a tls-auth control channel, and
	// what the session report records.
	NameTLSAuth = "tls-auth"
)
View Source
const NameNone = "none"

NameNone is what Name reports for an unwrapped control channel, which has no authentication and no replay protection of its own — a fact about OpenVPN rather than an omission here.

View Source
const (
	// NameTLSCrypt is what Name reports for a tls-crypt control channel, and
	// what the session report records.
	NameTLSCrypt = "tls-crypt"
)

Variables

View Source
var (
	// ErrAuth means the packet did not authenticate under the receive key:
	// an HMAC mismatch, or a packet too short for the fields the wrap
	// requires. Both are diag.ClassCrypto.
	ErrAuth = errors.New("wrap: control packet failed authentication")

	// ErrReplay means the packet authenticated but its packet id has been
	// seen before, or has fallen out of the back of the window.
	ErrReplay = errors.New("wrap: control packet id replayed")

	// ErrStaleTimestamp means the packet authenticated but its timestamp is
	// older than one already accepted from this peer, which retires every
	// packet id below it.
	ErrStaleTimestamp = errors.New("wrap: control packet timestamp went backwards")
)

Errors a Wrapper returns for a packet it will not accept. They are sentinels because the caller has to tally them apart: an authentication failure means the key is wrong and the session cannot work, while a replay means the key is right and something on the path duplicated a packet.

Functions

This section is empty.

Types

type Direction

type Direction int

Direction is the --key-direction a profile carries, selecting the halves of the static key used to send and to receive. Absent is a third behaviour and not a default of 0: key_direction_state_init leaves both directions on slot 0, so the two peers share one key rather than splitting the material.

const (
	// DirectionAbsent is a profile with no key-direction directive: both
	// directions use slot 0.
	DirectionAbsent Direction = iota
	// Direction0 is "key-direction 0": send with slot 0, receive with slot 1.
	Direction0
	// Direction1 is "key-direction 1": send with slot 1, receive with slot 0.
	Direction1
)

func (Direction) String

func (d Direction) String() string

String returns the direction as a profile spells it.

type StaticKey

type StaticKey [StaticKeySize]byte

StaticKey is an OpenVPN 2048-bit static key as a wrap consumes it: 256 bytes, two 128-byte slots of 64 cipher bytes then 64 HMAC bytes. It is layout-identical to profile.StaticKey, so a caller converts with (*wrap.StaticKey)(prof.TLSAuth) and copies no key material; it is declared here rather than imported so that this package — the wire format — does not depend on the profile parser.

A StaticKey is a secret, and the type is where that is enforced. String, GoString and MarshalJSON all yield a fixed placeholder, so a key cannot reach a log line, an error or a session report by being swept up in a %v, a %#v or a json.Marshal of whatever holds it. The three methods duplicate profile.StaticKey's on purpose: sharing them is what the declaration above avoids.

func (StaticKey) GoString

func (k StaticKey) GoString() string

GoString returns the same placeholder as String, because %#v bypasses fmt.Stringer and %#v is what someone debugging a wrap reaches for.

func (StaticKey) MarshalJSON

func (k StaticKey) MarshalJSON() ([]byte, error)

MarshalJSON encodes the key as the placeholder string. The result is deliberately not decodable back into a key.

func (StaticKey) String

func (k StaticKey) String() string

String returns a fixed placeholder rather than the key.

type Wrapper

type Wrapper interface {
	// Wrap returns the on-wire form of one outbound control packet.
	Wrap(pkt []byte) ([]byte, error)

	// Unwrap returns the plain form of one inbound control packet, or an
	// error if authentication, decryption or replay checking fails.
	Unwrap(pkt []byte) ([]byte, error)

	// Overhead is the number of bytes Wrap adds, for MTU accounting.
	Overhead() int

	// Name is the wrap as the session report should record it:
	// "none", "tls-auth" or "tls-crypt".
	Name() string
}

Wrapper transforms control packets on their way to and from the wire; data packets never reach it. A Wrapper is fixed for the life of a connection and chosen from the profile before the first packet is sent, because both wraps authenticate the opening HARD_RESET.

Implementations must be safe for use from more than one goroutine: a wrap carrying a packet-ID counter has mutable state, and the client's control, ACK, retransmit and rekey paths all send from goroutines of their own.

func NewTLSAuth

func NewTLSAuth(key *StaticKey, direction Direction, digest crypto.Digest) (Wrapper, error)

NewTLSAuth builds a tls-auth wrapper from a 2048-bit static key and the profile's key-direction. digest is the control-channel HMAC, the profile's auth digest, defaulting to SHA1.

The Wrapper is fixed for the life of one connection and must be chosen before the socket opens, because tls-auth authenticates the opening HARD_RESET. A new one is built for every attempt: the replay window and the send counter are per connection, and a peer restarting its packet ids at 1 against a carried-over window would look like 64 replays.

func NewTLSCrypt

func NewTLSCrypt(key *StaticKey) (Wrapper, error)

NewTLSCrypt builds a tls-crypt wrapper from a 2048-bit static key. There is nothing else to pass: tls_crypt_kt() fixes AES-256-CTR and HMAC-SHA256, there is no --key-direction, and the halves of the key are chosen by which peer you are. The wrapper returned is the client's.

It is fixed for the life of one connection and must be chosen before the socket opens, because tls-crypt encrypts and authenticates the opening HARD_RESET. A new one per attempt: the replay window and the send counter are per connection, and a peer restarting its ids at 1 would look like 64 replays.

func Plain

func Plain() Wrapper

Plain returns the identity wrapper, so that the client carries no special case for an unwrapped control channel. Wrap and Unwrap return the packet they were given, without copying it.

Jump to

Keyboard shortcuts

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