gatewaywire

package
v0.17.5 Latest Latest
Warning

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

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

Documentation

Overview

Package gatewaywire defines the wire contract shared by the blind WSS gateway, the computer relay that registers with it, and the phone app that connects through it.

The gateway copies opaque frames. It never learns the relay key, never sees plaintext, SDP, or push subscriptions, and holds no long-lived secret of its own. Phone connections are authenticated by a challenge-response HMAC that the gateway forwards to the relay; the relay is the only party that can verify it, because only the relay and the paired phone can derive the rendezvous key from the relay key.

Two identifiers are derived one-way from the relay key, so the QR payload is unchanged and the gateway learns nothing about the relay key:

relay_id       = base64url(HKDF-SHA256(relay_key, salt, "herdr-gw-id", 16))
rendezvous_key = HKDF-SHA256(relay_key, salt, "herdr-gw-auth", 32)

Index

Constants

View Source
const (
	// OpData carries one verbatim Herdr E2EE frame (raw ciphertext).
	OpData byte = 0
	// OpOpen announces a new phone connection, gateway to relay only. The
	// payload is a JSON OpenPayload holding the challenge the phone answered.
	OpOpen byte = 1
	// OpClose ends one logical connection in either direction. The payload is
	// an optional short UTF-8 reason.
	OpClose byte = 2
	// OpPing and OpPong keep intermediaries from reaping an idle relay link.
	OpPing byte = 3
	OpPong byte = 4
	// OpNotice carries a gateway control message to the relay, currently
	// quota warnings. The payload is a JSON NoticePayload. Notices are
	// advisory: the relay surfaces them, the gateway never depends on them.
	OpNotice byte = 5
)

Multiplex opcodes. Only the relay link is multiplexed; a phone connection carries bare encrypted frames because it is a single logical connection.

View Source
const (
	TypeServerHello = "gateway_hello"
	TypeRegister    = "register"
	TypeConnect     = "connect"
	TypeReady       = "ready"
	TypeError       = "error"
)

Message type names used in the JSON hello exchange.

View Source
const (
	NoticeQuotaWarning  = "quota_warning"
	NoticeQuotaExceeded = "quota_exceeded"
)

Notice kinds.

View Source
const (
	CodeBadHello      = "bad_hello"
	CodeUnknownRelay  = "unknown_relay"
	CodeRateLimited   = "rate_limited"
	CodeTooManyClient = "too_many_clients"
	// CodeAtCapacity refuses a connection because the gateway as a whole is
	// full, not because the relay it names is. A shared public instance needs
	// ceilings on total relays and total phones that no per-relay cap can
	// express; a client seeing this should try again later or use another
	// gateway.
	CodeAtCapacity    = "at_capacity"
	CodeQuotaExceeded = "quota_exceeded"
	CodeRelayBusy     = "relay_busy"
	CodeInternal      = "internal"
)

Error codes returned in ErrorMessage.

View Source
const FrameVersion = 1

FrameVersion prefixes every multiplexed frame on the relay link.

View Source
const HeaderSize = 1 + 1 + 4

HeaderSize is the fixed multiplex header: version, opcode, connection id.

View Source
const MaxCloseReason = 120

MaxCloseReason bounds the UTF-8 reason attached to OpClose.

View Source
const MaxFramePayload = 1 << 20

MaxFramePayload bounds one multiplexed payload. It is far below the 21 MiB logical message cap because large logical messages travel as several encrypted frames on every transport that uses this protocol.

View Source
const MaxHelloBytes = 4096

MaxHelloBytes bounds a JSON hello message.

View Source
const NonceBytes = 32

NonceBytes is the length of the gateway challenge.

View Source
const Proto = 1

Proto is the gateway protocol version carried in every hello message.

View Source
const RelayIDBytes = 16

RelayIDBytes is the raw length of a derived relay id before base64url.

View Source
const RelayIDLength = 22

RelayIDLength is the encoded relay id length, used for cheap validation.

Variables

View Source
var ErrShortFrame = errors.New("gateway frame is shorter than its header")

ErrShortFrame reports a multiplex frame smaller than the fixed header.

Functions

func AppendFrame

func AppendFrame(dst []byte, op byte, connID uint32, payload []byte) []byte

AppendFrame appends one multiplexed frame to dst and returns the new slice. Callers reuse dst to keep the copy path allocation-free.

func ConnectProof

func ConnectProof(rendezvousKey []byte, relayID string, nonce []byte) []byte

ConnectProof answers a gateway challenge. The relay id is bound into the tag so a proof captured for one relay cannot be replayed against another.

func DecodeFrame

func DecodeFrame(frame []byte) (op byte, connID uint32, payload []byte, err error)

DecodeFrame splits one multiplexed frame. The returned payload aliases frame.

func DeriveRelayID

func DeriveRelayID(relayKey string) (string, error)

DeriveRelayID returns the public rendezvous identifier for a relay key.

func DeriveRendezvousKey

func DeriveRendezvousKey(relayKey string) ([]byte, error)

DeriveRendezvousKey returns the secret both the relay and the paired phone use to authenticate a gateway connection. The gateway never receives it.

func EncodeFrame

func EncodeFrame(op byte, connID uint32, payload []byte) []byte

EncodeFrame builds one multiplexed frame.

func ValidRelayID

func ValidRelayID(relayID string) bool

ValidRelayID reports whether an identifier is shaped like a derived relay id.

func VerifyConnectProof

func VerifyConnectProof(rendezvousKey []byte, relayID string, nonce, proof []byte) bool

VerifyConnectProof checks a phone proof in constant time.

Types

type ConnectHello

type ConnectHello struct {
	Type    string `json:"type"`
	Proto   int    `json:"proto"`
	RelayID string `json:"relay_id"`
	Proof   string `json:"proof"`
}

ConnectHello asks to be paired with a registered relay.

type ErrorMessage

type ErrorMessage struct {
	Type    string `json:"type"`
	Code    string `json:"code"`
	Message string `json:"message"`
}

ErrorMessage is the last message on a rejected connection.

type NoticePayload

type NoticePayload struct {
	Kind         string `json:"kind"`
	Message      string `json:"message"`
	RelayedBytes uint64 `json:"relayed_bytes,omitempty"`
	QuotaBytes   uint64 `json:"quota_bytes,omitempty"`
}

NoticePayload is the JSON body of OpNotice.

type OpenPayload

type OpenPayload struct {
	Nonce string `json:"nonce"`
	Proof string `json:"proof"`
}

OpenPayload is the JSON body of OpOpen. It hands the relay the challenge and the phone's answer so the relay — not the gateway — authenticates the phone.

type ReadyMessage

type ReadyMessage struct {
	Type  string `json:"type"`
	Proto int    `json:"proto"`
}

ReadyMessage tells a client that framing has started.

type RegisterHello

type RegisterHello struct {
	Type    string `json:"type"`
	Proto   int    `json:"proto"`
	RelayID string `json:"relay_id"`
}

RegisterHello claims a relay id for the multiplexed relay link.

type ServerHello

type ServerHello struct {
	Type     string `json:"type"`
	Proto    int    `json:"proto"`
	Nonce    string `json:"nonce"`
	StunPort int    `json:"stun_port,omitempty"`
	Version  string `json:"version,omitempty"`
	Revision string `json:"revision,omitempty"`
}

ServerHello is the first message the gateway sends on any connection. The nonce is the challenge a phone answers; a relay ignores it.

StunPort advertises the gateway's own STUN listener so both peers can learn their mapped address without router configuration. Only the port travels: each peer combines it with the gateway host it already dialed, so a gateway can never redirect address discovery to a third party. 0 means disabled.

Jump to

Keyboard shortcuts

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