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 ¶
const KeySize = 32
KeySize is the byte length of a Curve25519 public or private key.
const WindowSize = 64
WindowSize is the number of nonces tracked by ReplayWindow.
Variables ¶
var CipherSuite = noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashBLAKE2s)
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).
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 ¶
DecodeKey parses a base64-encoded 32-byte Curve25519 key. It accepts both standard and URL-safe encodings, with or without padding.
func EncodeKey ¶
EncodeKey returns the standard base64 (with padding) encoding of a key, matching WireGuard's textual key format.
func EqualKey ¶
EqualKey reports whether two keys are byte-equal in constant time. Inputs of differing length compare unequal in constant time as well.
func GenerateKeypair ¶
GenerateKeypair returns a fresh Curve25519 keypair drawn from crypto/rand.
func KeypairFromPrivate ¶
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 ¶
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 ¶
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.
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 ¶
NewResponder creates an IK responder. local is the server's static keypair.
func (*Responder) PeerStatic ¶
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 ¶
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.
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 ¶
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 ¶
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.