auth

package
v0.0.0-...-8c66875 Latest Latest
Warning

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

Go to latest
Published: Apr 17, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package auth implements GHOST's L3 authentication: Noise IK handshake, session ciphers, and key management.

All comparisons of secret-derived material in this package use crypto/subtle to avoid timing side channels (Principle 4 in research/05-design-principles.md).

Index

Constants

View Source
const KeySize = 32

KeySize is the byte length of a Curve25519 public or private key.

View Source
const WindowSize = 64

WindowSize is the number of nonces tracked by ReplayWindow.

Variables

CipherSuite is the GHOST L3 Noise cipher suite:

Noise_IK_25519_ChaChaPoly_BLAKE2s

Curve25519 for DH, ChaCha20-Poly1305 AEAD, BLAKE2s hash. This is the same suite WireGuard uses for its primitives, with a different handshake pattern (IK gives us 0-RTT initiator authentication, suitable for embedding in a single HTTP/2 request body — see docs/protocol.md §L3).

View Source
var HandshakePrologue = []byte("ghost-v1")

HandshakePrologue is mixed into the Noise handshake hash on both sides. It binds the handshake to the protocol version and rejects clients that disagree on the wire format. Bumped on any breaking change.

The byte 0x01 here matches version.Protocol.

Functions

func DecodeKey

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

DecodeKey parses a base64-encoded 32-byte Curve25519 key. It accepts both standard and URL-safe encodings, with or without padding.

func EncodeKey

func EncodeKey(k []byte) string

EncodeKey returns the standard base64 (with padding) encoding of a key, matching WireGuard's textual key format.

func EqualKey

func EqualKey(a, b []byte) bool

EqualKey reports whether two keys are byte-equal in constant time. Inputs of differing length compare unequal in constant time as well.

func GenerateKeypair

func GenerateKeypair() (noise.DHKey, error)

GenerateKeypair returns a fresh Curve25519 keypair drawn from crypto/rand.

func KeypairFromPrivate

func KeypairFromPrivate(priv []byte) (noise.DHKey, error)

KeypairFromPrivate reconstructs a keypair from a 32-byte Curve25519 private key by deriving the matching public key. The returned struct holds copies of the input bytes so callers may safely zero their own buffers afterwards.

Types

type Initiator

type Initiator struct {
	// contains filtered or unexported fields
}

Initiator drives the client side of the IK handshake.

-> e, es, s, ss            (msg1, written by initiator)
<- e, ee, se               (msg2, read by initiator)

After ReadMessage succeeds the handshake state may be discarded; the returned *Session holds the derived send/recv ciphers.

func NewInitiator

func NewInitiator(local noise.DHKey, remoteStatic []byte) (*Initiator, error)

NewInitiator creates an IK initiator. local is the client's static keypair (sent encrypted to the server inside msg1). remoteStatic is the server's static public key, learned out of band (config file, like WireGuard).

func (*Initiator) ReadMessage

func (i *Initiator) ReadMessage(msg []byte) ([]byte, *Session, error)

ReadMessage processes the responder's reply (msg2). On success, returns the decrypted server payload (may be empty) and a Session ready for tunnel data.

On any verification failure (wrong key, tampered ciphertext, malformed) it returns an error. Callers MUST NOT branch on the error in a way that leaks timing — the L2 layer's response is to fall through to the fallback path, which happens regardless of error type.

func (*Initiator) WriteMessage

func (i *Initiator) WriteMessage(payload []byte) ([]byte, error)

WriteMessage builds the first IK handshake message and returns the wire bytes. payload may be nil; any bytes provided are encrypted and authenticated as part of the handshake (0-RTT data).

type ReplayWindow

type ReplayWindow struct {
	// contains filtered or unexported fields
}

ReplayWindow is a 64-entry sliding window for nonce-based replay protection.

It allows out-of-order delivery within a 64-nonce window of the highest nonce ever marked, while rejecting duplicates and stale nonces. The algorithm matches the classic IPsec/WireGuard sliding-window replay check (compressed to a single 64-bit word for v1's modest window size).

v1 does NOT use this on the live data path: GHOST runs over TCP+HTTP/2 which delivers in order, so Session relies on Noise's built-in sequential nonces. ReplayWindow exists for v2 (stream migration / multi-connection / possible UDP transport) where wire-level nonces become explicit and may arrive out of order. It is exported and unit-tested so v2 can adopt it without revisiting correctness.

Zero value is ready to use: it accepts any first nonce (including 0).

func (*ReplayWindow) Head

func (w *ReplayWindow) Head() uint64

Head returns the highest nonce ever marked, or 0 if Mark was never called.

func (*ReplayWindow) Mark

func (w *ReplayWindow) Mark(n uint64) bool

Mark records nonce n as observed. Returns true if n is fresh (not a replay and not too old), false otherwise. A return of true means the caller may process the corresponding message; false means it must be dropped.

func (*ReplayWindow) Reset

func (w *ReplayWindow) Reset()

Reset clears the window, returning it to its zero state.

type Responder

type Responder struct {
	// contains filtered or unexported fields
}

Responder drives the server side of the IK handshake.

func NewResponder

func NewResponder(local noise.DHKey) (*Responder, error)

NewResponder creates an IK responder. local is the server's static keypair.

func (*Responder) PeerStatic

func (r *Responder) PeerStatic() []byte

PeerStatic returns the client's static public key, valid only after a successful ReadMessage. Use EqualKey to compare against an allow-list.

func (*Responder) ReadMessage

func (r *Responder) ReadMessage(msg []byte) ([]byte, error)

ReadMessage processes the initiator's first message (msg1). On success the initiator's static public key is recoverable via PeerStatic.

IMPORTANT: An error here means the client is unauthenticated. The L2 layer must respond by reverse-proxying to the fallback target — and it must do so on the SAME code path that valid auth would take, modulo the actual response bytes, to preserve constant-time behavior. See Principle 3 + 4.

func (*Responder) WriteMessage

func (r *Responder) WriteMessage(payload []byte) ([]byte, *Session, error)

WriteMessage builds the responder's reply (msg2) and finalizes the handshake. Returns the wire bytes plus a Session ready for tunnel data.

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session is the post-handshake symmetric state for one tunnel: a pair of ChaCha20-Poly1305 cipher states derived from the Noise IK handshake.

Encrypt uses the send cipher; Decrypt uses the recv cipher. Each direction's nonce is managed by the underlying Noise CipherState (sequential, starting at 0). Reordering messages within a direction is not supported in v1 — the transport (TCP+TLS+HTTP/2) guarantees in-order delivery, so any out-of-order arrival or replay manifests as a decryption failure, which is exactly the behavior we want.

For v2 (stream migration over multiple connections, possibly UDP) the ReplayWindow type in this package can be wired into Session to allow out-of-order delivery within a 64-nonce window.

func (*Session) Decrypt

func (s *Session) Decrypt(ad, ciphertext []byte) ([]byte, error)

Decrypt opens ciphertext with the recv cipher. ad must match what was passed to Encrypt on the peer. Returns an error on any AEAD failure; nonce sequencing is automatic.

func (*Session) Encrypt

func (s *Session) Encrypt(ad, plaintext []byte) ([]byte, error)

Encrypt seals plaintext with the send cipher. ad is associated data (typically the L4 frame header — Version|Type|StreamID|Length). The returned slice is freshly allocated and contains ciphertext || tag.

func (*Session) RecvNonce

func (s *Session) RecvNonce() uint64

RecvNonce returns the next nonce expected by the recv cipher.

func (*Session) SendNonce

func (s *Session) SendNonce() uint64

SendNonce returns the next nonce that will be used by the send cipher. Useful for diagnostics and triggering rekey before the 2^64-1 limit.

Jump to

Keyboard shortcuts

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