session

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2026 License: MPL-2.0 Imports: 19 Imported by: 0

Documentation

Index

Constants

View Source
const (
	NoncePrefixOutbound uint32 = 0x4F555442 // "OUTB"
	NoncePrefixInbound  uint32 = 0x494E4244 // "INBD"
)

Nonce prefixes for direction separation (prevents nonce reuse with same key).

View Source
const MaxSecureMessageSize = 20 * 1024 * 1024 // 20 MiB

MaxSecureMessageSize caps the encrypted payload a SecureReader will accept. Rejected before allocation, so a malicious length header on the raw stream cannot OOM us.

Variables

View Source
var (
	RekeyBytesThreshold = defaultRekeyBytes
	RekeyMsgsThreshold  = defaultRekeyMsgs
)

Re-keying thresholds for forward secrecy. Vars, not consts, so tests can shrink them to exercise the rekey path; production uses these unless the debug override is applied.

View Source
var ErrFoldNoClient = errors.New("no grpc client to initiate entropy fold")

ErrFoldNoClient is returned by InitiateFold when the session has no gRPC client to carry the fold exchange to the peer.

View Source
var ErrFoldNotReady = errors.New("session not ready for entropy fold")

ErrFoldNotReady is returned when a fold is attempted on a session whose keys or conns are not established, or whose in-band ratchet was not negotiated. Staging a fold on such a session would never apply, so the fold fails closed instead of silently doing nothing.

View Source
var ErrFoldWrongRole = errors.New("entropy fold: this peer initiates, it does not respond")

ErrFoldWrongRole is returned when RespondToFold is called on the deterministic fold initiator. Only the responder (higher fingerprint) answers a fold; the initiator drives one. Refusing out of role stops a misbehaving peer from racing a second, conflicting secret that would reset the conn.

View Source
var RekeyTimeCadence = 60 * time.Second

RekeyTimeCadence bounds how long a direction may go without a ratchet even when below the byte/message thresholds, so a low-volume (heartbeat-only) direction still earns forward secrecy. A var so tests and benchmarks can shrink it.

Functions

func ApplyRekeyThresholdOverride added in v0.4.0

func ApplyRekeyThresholdOverride(bytes, msgs uint64) (uint64, uint64)

ApplyRekeyThresholdOverride lowers the rekey thresholds for testing and benchmarking. A zero argument leaves that threshold unchanged; a non-zero value is clamped to [floor, default], so the override can only make rotation fire sooner. Returns the applied values. Call once at startup before the HealthMonitor goroutine reads the thresholds.

func ComputeFingerprintFromBase64Keys

func ComputeFingerprintFromBase64Keys(pubKeys map[string]string) (string, error)

func CreateRekeyRequest

func CreateRekeyRequest(ownKeys *kbc.OwnKeys, peerKeys *kbc.PeerKeys, epoch uint64, suite kbc.CipherSuite) (*bindings.RekeyRequest, []byte, error)

CreateRekeyRequest generates a RekeyRequest with new encapsulated seeds. Returns the request, the derived new key (for outbound), and any error.

func DialWithStableAddr

func DialWithStableAddr(network, addr string, timeout time.Duration, logger *slog.Logger) (net.Conn, error)

dialWithStableAddr dials a remote address using a net.Dialer that binds to the machine's stable (non-temporary) IPv6 address, so connections survive macOS/Linux deprecating temporary privacy addresses (RFC 4941).

func ExchangePublicKeysLocal

func ExchangePublicKeysLocal(session *Session, conn net.Conn, isInitiator bool) error

ExchangePublicKeysLocal performs a plaintext exchange of public keys over conn. In local mode, the relay is skipped, so both peers need each other's keys before the PQC handshake can run. The initiator (joiner) sends first, responder second.

func FinalizeInboundSession

func FinalizeInboundSession(session *Session, conn net.Conn, encSeeds map[string]string) error

FinalizeInboundSession completes the inbound session setup after peer is verified. It decapsulates seeds, derives the SEKInbound, wraps the net.Conn in SecureConn, and finalizes state.

func PerformInboundHandshake

func PerformInboundHandshake(session *Session, conn net.Conn) error

PerformInboundHandshake handles the first plaintext connection from Bob to Alice.

func PerformOutboundHandshake

func PerformOutboundHandshake(session *Session, remoteAddr string) error

PerformOutboundHandshake dials remoteAddr and sends the PQC handshake.

func PerformOutboundHandshakeOnConn

func PerformOutboundHandshakeOnConn(session *Session, conn net.Conn) error

PerformOutboundHandshakeOnConn sends the PQC handshake on an existing connection. Used by bridge mode where the connection is pre-established (with room token already sent).

func ProcessRekeyRequest

func ProcessRekeyRequest(req *bindings.RekeyRequest, ownKeys *kbc.OwnKeys, peerKeys *kbc.PeerKeys, suite kbc.CipherSuite) (*bindings.RekeyResponse, []byte, error)

ProcessRekeyRequest handles an incoming RekeyRequest. Returns a RekeyResponse, the derived new inbound key, and any error.

func ProcessRekeyResponse

func ProcessRekeyResponse(resp *bindings.RekeyResponse, ownKeys *kbc.OwnKeys, peerKeys *kbc.PeerKeys, suite kbc.CipherSuite) ([]byte, error)

ProcessRekeyResponse handles an incoming RekeyResponse. Returns the derived new outbound key (peer's response seeds).

func SetKeyUpdateEnabled added in v0.4.0

func SetKeyUpdateEnabled(on bool)

SetKeyUpdateEnabled toggles the in-band key-update capability advertised on new handshakes. Off makes this peer negotiate the re-handshake fallback instead. Affects only handshakes after the call, not an established session.

func StartListener

func StartListener(session *Session, port int) error

StartListener starts a TCP listener on the given port and waits for Bob. It will block until Bob connects and sends valid keys that match the expected fingerprint.

Types

type ConnectionHealth

type ConnectionHealth int32

ConnectionHealth represents the health state of the P2P connection.

const (
	HealthUnknown      ConnectionHealth = iota
	HealthHealthy                       // All heartbeats succeeding
	HealthDegraded                      // High latency or some failures
	HealthDisconnected                  // Connection lost, needs reconnection
)

func (ConnectionHealth) String

func (h ConnectionHealth) String() string

String returns a human-readable representation of the health state.

type HealthMonitor

type HealthMonitor struct {

	// Configuration
	Interval    time.Duration // heartbeat interval (default 5s)
	Timeout     time.Duration // per-heartbeat timeout (default 3s)
	DegradedRTT time.Duration // RTT above this = degraded (default 500ms)
	MaxFailures int           // failures before disconnect (default 3)

	// Callbacks
	OnHealthChange func(old, new ConnectionHealth)
	OnDisconnect   func()
	// OnRekeyNeeded fires on an idle heartbeat tick when the session has passed its rekey
	// threshold, so the resilience layer can rotate keys via a re-handshake. Returns true only
	// when it actually initiated a rotation; on false the tick falls through to a normal
	// heartbeat, so liveness detection is never starved. Fires only when RekeyEnabled is set,
	// which the resilience layer ties to the absence of the in-band ratchet.
	OnRekeyNeeded func() bool
	RekeyEnabled  bool
	// ExtraEpoch, when set, reports the highest writer epoch of ratcheting conns OUTSIDE the
	// captured TCP pair (the QUIC control lane). One re-handshake re-derives ALL keys (TCP and
	// QUIC SEKs) and resets every epoch to 0, so a single rescue covers all lanes, but only if
	// the trigger observes them. Without this a hot QUIC lane would hold at the wrap guard with
	// no rescue. Called only on the monitor goroutine.
	ExtraEpoch func() uint16
	// contains filtered or unexported fields
}

HealthMonitor monitors the health of a P2P connection via periodic heartbeats. When enough consecutive heartbeats fail, it triggers the OnDisconnect callback.

func NewHealthMonitor

func NewHealthMonitor(session *Session, client bindings.KeibiServiceClient, logger *slog.Logger) *HealthMonitor

NewHealthMonitor creates a new health monitor with default settings.

func (*HealthMonitor) AvgRTT

func (m *HealthMonitor) AvgRTT() time.Duration

AvgRTT returns the exponential moving average of RTT.

func (*HealthMonitor) Health

func (m *HealthMonitor) Health() ConnectionHealth

Health returns the current connection health state.

func (*HealthMonitor) LastRTT

func (m *HealthMonitor) LastRTT() time.Duration

LastRTT returns the most recent round-trip time in nanoseconds.

func (*HealthMonitor) MaxWriterEpoch added in v0.4.0

func (m *HealthMonitor) MaxWriterEpoch() uint16

MaxWriterEpoch returns the higher key epoch across both captured conns, or 0 if none. It advances when the in-band ratchet rotates either send direction, so a caller can confirm a rotation without a reconnect. The captured conns are immutable for this monitor's lifetime.

func (*HealthMonitor) Start

func (m *HealthMonitor) Start()

Start begins the heartbeat monitoring loop in a goroutine.

func (*HealthMonitor) Stop

func (m *HealthMonitor) Stop()

Stop halts the health monitoring loop and waits for it to exit.

func (*HealthMonitor) TransferEnded added in v0.3.0

func (m *HealthMonitor) TransferEnded()

func (*HealthMonitor) TransferStarted added in v0.3.0

func (m *HealthMonitor) TransferStarted()

type PeerHandshakeMessage

type PeerHandshakeMessage struct {
	Fingerprint      string            `json:"fingerprint"`
	PublicKeys       map[string]string `json:"public_keys"` // base64 encoded
	EncSeeds         map[string]string `json:"enc_seeds"`   // optional for key encapsulation
	OutboundPort     int               `json:"port"`
	SupportedCiphers []string          `json:"supported_ciphers"` // cipher negotiation
	Persistent       bool              `json:"persistent,omitempty"`
	KeyUpdate        bool              `json:"key_update,omitempty"` // in-band ratchet capability
}

PeerHandshakeMessage defines the JSON payload sent during handshake.

type ReconnectManager

type ReconnectManager struct {

	// Configuration
	Backoff     []time.Duration // Exponential backoff delays
	MaxAttempts int             // Maximum reconnection attempts

	// Connection details (cached from last successful connection)
	CachedPeerIP   string
	CachedPeerPort int

	// Bridge relay (for firewall traversal). If set, reconnect uses bridge instead of direct.
	BridgeAddr string
	DialBridge func(direction string) (net.Conn, error) // Dial bridge with direction-tagged room token

	// Callbacks
	OnReconnecting func()                                                    // Called when reconnection starts
	OnReconnected  func()                                                    // Called on successful reconnection
	OnGaveUp       func()                                                    // Called when all attempts exhausted
	RelayRefresh   func() error                                              // Re-register with relay
	RelayLookup    func(fingerprint string) (ip string, port int, err error) // Lookup peer in relay
	AcceptConn     func(timeout time.Duration) (net.Conn, error)             // Accept incoming connection
	// contains filtered or unexported fields
}

ReconnectManager handles automatic reconnection when the P2P connection drops. It coordinates with the health monitor and uses deterministic initiator selection to avoid race conditions.

func NewReconnectManager

func NewReconnectManager(session *Session, logger *slog.Logger) *ReconnectManager

NewReconnectManager creates a new reconnection manager with default settings.

func (*ReconnectManager) Attempts

func (r *ReconnectManager) Attempts() int

Attempts returns the number of reconnection attempts made.

func (*ReconnectManager) IsReconnectInitiator

func (r *ReconnectManager) IsReconnectInitiator() bool

IsReconnectInitiator determines which peer should initiate reconnection: the peer with the lexicographically lower fingerprint, so both peers do not connect simultaneously.

func (*ReconnectManager) OnDisconnect

func (r *ReconnectManager) OnDisconnect()

OnDisconnect is called when the health monitor detects a connection loss. It starts the reconnection loop in a goroutine.

func (*ReconnectManager) Reset

func (r *ReconnectManager) Reset()

Reset resets the manager to connected state (call after manual session restart).

func (*ReconnectManager) State

func (r *ReconnectManager) State() ReconnectState

State returns the current reconnection state.

func (*ReconnectManager) Stop

func (r *ReconnectManager) Stop()

Stop halts any ongoing reconnection attempts and prevents new ones.

type ReconnectState

type ReconnectState int32

ReconnectState represents the current state of the reconnection manager.

const (
	ReconnectStateConnected    ReconnectState = iota // Connection is healthy
	ReconnectStateReconnecting                       // Actively trying to reconnect
	ReconnectStateWaitingPeer                        // Waiting for peer to come online
	ReconnectStateGaveUp                             // Exhausted all retry attempts
)

func (ReconnectState) String

func (s ReconnectState) String() string

String returns a human-readable representation of the reconnect state.

type SecureConn

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

SecureConn wraps a net.Conn with separate inbound/outbound encryption.

func NewSecureConn

func NewSecureConn(conn net.Conn, kek []byte, suite kbc.CipherSuite, writerPrefix uint32) *SecureConn

NewSecureConn wraps conn with AEAD encryption. writerPrefix selects this endpoint's direction nonce prefix (NoncePrefixOutbound for the dialing side, NoncePrefixInbound for the accepting side). Required so the two endpoints of a socket, which share one key, cannot both default to the same prefix and reuse nonces.

func (*SecureConn) Accept

func (s *SecureConn) Accept() (net.Conn, error)

func (*SecureConn) Addr

func (l *SecureConn) Addr() net.Addr

func (*SecureConn) Close

func (s *SecureConn) Close() error

Close closes the underlying connection. Idempotent and race-safe: the proactive rekey drop and gRPC's transport can both close the same *SecureConn. sync.Once collapses the double close so close(s.closed) never panics, and the atomic done keeps Accept's read race-free. Only the first close reports the conn's error; later closes return net.ErrClosed.

func (*SecureConn) GetEpoch

func (s *SecureConn) GetEpoch() uint64

GetEpoch returns the current key epoch.

func (*SecureConn) GetStats

func (s *SecureConn) GetStats() (bytesSent, bytesRecv, msgsSent, msgsRecv uint64)

GetStats returns current byte/message counts for monitoring.

func (*SecureConn) LocalAddr

func (s *SecureConn) LocalAddr() net.Addr

LocalAddr returns the local network address.

func (*SecureConn) Read

func (s *SecureConn) Read(p []byte) (int, error)

func (*SecureConn) ReadMessage

func (s *SecureConn) ReadMessage() ([]byte, error)

ReadMessage reads and decrypts a full message.

func (*SecureConn) RemoteAddr

func (s *SecureConn) RemoteAddr() net.Addr

RemoteAddr returns the remote network address.

func (*SecureConn) ResetStats

func (s *SecureConn) ResetStats()

ResetStats resets the byte/message counters after a rekey.

func (*SecureConn) SetDeadline

func (s *SecureConn) SetDeadline(t time.Time) error

func (*SecureConn) SetKeyUpdate added in v0.4.0

func (s *SecureConn) SetKeyUpdate(on bool)

SetKeyUpdate turns the in-band rekey ratchet on for this conn. Called once during the handshake, before the read and write goroutines start, when both peers advertised the capability. With it off the conn behaves as epoch 0 (old-peer interop) and rotates only via the re-handshake fallback.

func (*SecureConn) SetReadDeadline

func (s *SecureConn) SetReadDeadline(t time.Time) error

func (*SecureConn) SetWriteDeadline

func (s *SecureConn) SetWriteDeadline(t time.Time) error

func (*SecureConn) SetWriterEpochForTest added in v0.4.0

func (s *SecureConn) SetWriterEpochForTest(e uint16)

SetWriterEpochForTest fast-forwards this conn's writer key epoch, for tests that need the epoch-wrap re-handshake path without ratcheting billions of times. Monotonic only: a backward or duplicate epoch would reset the counter under an unchanged key and reuse a nonce, so it refuses to move backward. Not safe on a live conn.

func (*SecureConn) ShouldRekey

func (s *SecureConn) ShouldRekey() bool

ShouldRekey returns true if key rotation is recommended. It considers BOTH directions: a bulk transfer bumps the sender's bytesSent and the receiver's bytesRecv, and either peer may drive the rekey, so both must observe that the threshold was crossed.

func (*SecureConn) StageEntropyFold added in v0.4.0

func (s *SecureConn) StageEntropyFold(secret []byte, isInitiator bool)

StageEntropyFold stages a 32-byte KEM fold secret on both directions of this conn for one fold round: the reader first, then the writer, so a single-conn stage holds the reader- before-writer invariant on its own. The writer folds it into its next epoch bump (promptly for the initiator, or once the gate opens for a responder); the reader folds it when it next follows an epoch bump. Staging is supersede-idempotent. Serialized under wMu (writer) and foldMu-under-keyMu.RLock (reader), so a duplicate Rekey cannot tear the state or race an UpdateKey conn swap.

func (*SecureConn) UpdateKey

func (s *SecureConn) UpdateKey(newKek []byte)

UpdateKey rebuilds both directions on a fresh key, resetting the ratchet to epoch 0. The legacy full-swap primitive kept for the re-handshake fallback and its test. Takes wMu then keyMu, so it is exclusive against both Write and Read. Do not call on a live reader: it holds wMu while waiting for keyMu behind a possibly network-blocked Read, stalling every Write until an inbound frame arrives. The live path rotates via the ratchet.

func (*SecureConn) UsesKeyUpdate added in v0.4.0

func (s *SecureConn) UsesKeyUpdate() bool

UsesKeyUpdate reports whether the in-band ratchet is active on this conn. The health monitor reads it to keep the legacy volume-based re-handshake trigger off ratcheting sessions: their byte counters are cumulative, so past one threshold ShouldRekey stays true forever, and a volume-driven re-handshake would tear down a healthy session that is already rotating.

func (*SecureConn) Write

func (s *SecureConn) Write(p []byte) (int, error)

func (*SecureConn) WriteMessage

func (s *SecureConn) WriteMessage(msg []byte) error

WriteMessage encrypts and writes a full message.

func (*SecureConn) WriterEpoch added in v0.4.0

func (s *SecureConn) WriterEpoch() uint16

WriterEpoch returns the outbound key epoch. It advances each time the in-band ratchet rotates the send key, so a monitor or test can observe rotation without a socket drop. Safe on the live path: s.w is stable there (only test-only UpdateKey swaps it) and the epoch lives in an atomic word.

type SecureReader

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

SecureReader reads encrypted messages and decrypts them.

func NewSecureReader

func NewSecureReader(r io.Reader, kek []byte, suite kbc.CipherSuite, prefix uint32) *SecureReader

func (*SecureReader) Read

func (s *SecureReader) Read() ([]byte, error)

type SecureWriter

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

SecureWriter encrypts messages and writes them to an underlying writer.

func NewSecureWriterWithPrefix

func NewSecureWriterWithPrefix(w io.Writer, kek []byte, suite kbc.CipherSuite, prefix uint32) *SecureWriter

NewSecureWriterWithPrefix creates a writer with the given direction nonce prefix. The prefix is required (no default) so a socket's two endpoints must choose different prefixes; a shared default caused the nonce reuse this replaces.

func (*SecureWriter) Write

func (s *SecureWriter) Write(p []byte) (int, error)

type Session

type Session struct {
	// Known fingerprint of the expected peer, shared out-of-band.
	ExpectedPeerFingerprint string

	OwnKeys        *kbc.OwnKeys
	OwnFingerprint string

	// Populated after receiving peer keys.
	PeerPubKeys *kbc.PeerKeys // "x25519", "mlkem"

	// Symmetric session key.
	SEKInbound  []byte
	SEKOutbound []byte

	// QUIC control-channel keys, derived in the SAME handshake as the TCP keys from extra seeds
	// in the initial payload, so the handshake gains no round trip. Each channel/direction has
	// its own key, so the QUIC channel never shares key + nonce space with TCP. Empty when the
	// peer is an older build that sent no QUIC seeds; then the QUIC channel is not brought up.
	SEKOutboundQUIC []byte
	SEKInboundQUIC  []byte

	// Negotiated cipher suite for this session.
	CipherMu    sync.Mutex
	CipherSuite kbc.CipherSuite

	// Peer-to-peer TCP connections.
	Session  *SessionSockets
	PeerPort int

	DefaultOutboundPort int
	DefaultInboundPort  int

	GRPCListener net.Listener
	GRPCClient   bindings.KeibiServiceClient
	// ExtraFoldConns, when set, returns additional live SecureConns (the QUIC control lane) to
	// include in a fold round. stageFoldBothConns calls it at staging time on BOTH roles, so
	// every lane that exists then gets the fold's fresh entropy, not just the TCP pair. A QUIC
	// wire that comes up later is covered by the round its own arrival triggers. Must be safe to
	// call from any goroutine; nil entries are skipped.
	ExtraFoldConns func() []*SecureConn

	// Session state and lifecycle
	State       SessionState
	Established time.Time

	Err error

	// Persistent identity flags (learned during handshake).
	OwnIsPersistent  bool
	PeerIsPersistent bool

	// PeerSupportsKeyUpdate is learned from the peer's handshake: true if it advertised the
	// in-band key-update capability. Gates whether the ratchet turns on.
	PeerSupportsKeyUpdate bool

	// Internal timeout deadline
	Deadline time.Time

	// Re-keying state. Rotation is performed by an idle re-handshake, not an in-band key swap,
	// so these only track the last rotation for gating.
	RekeyMu      sync.Mutex
	LastRekeyAt  time.Time
	CurrentEpoch uint64
	// contains filtered or unexported fields
}

Session represents the state of a P2P connection between Alice and Bob.

func InitSession

func InitSession(logger *slog.Logger, defaultOutboundPort int, defaultInboundPort int) (*Session, error)

func InitSessionWithKeys added in v0.2.0

func InitSessionWithKeys(logger *slog.Logger, keys *kbc.OwnKeys, defaultOutboundPort int, defaultInboundPort int) (*Session, error)

InitSessionWithKeys creates a session using pre-existing keys (for persistent identity). Same as InitSession but skips key generation.

func NewSession

func NewSession(logger *slog.Logger, expectedFingerprint string, timeout time.Duration) *Session

NewSession initializes a new session with a timeout deadline.

func (*Session) AdoptFoldCustody added in v0.4.0

func (s *Session) AdoptFoldCustody(c *SecureConn)

AdoptFoldCustody makes a conn that joins the session AFTER a fold round adopt every un-retired round and retire session-wide when it commits one. Both the TCP install path and the QUIC control lane (whose conns are created outside install*) call it, so no lane can miss a round the peer's writer stays armed with. Safe on any conn before it carries a live reader.

func (*Session) ApplyKeyUpdateNegotiation added in v0.4.0

func (s *Session) ApplyKeyUpdateNegotiation()

ApplyKeyUpdateNegotiation turns the ratchet on (or off) on both live conns to match the negotiated capability. Must run after both handshakes complete, so PeerSupportsKeyUpdate is known, and before the gRPC reader goroutine starts, since SetKeyUpdate is not safe to flip under a live reader.

func (*Session) BothConns added in v0.4.0

func (s *Session) BothConns() (inbound, outbound *SecureConn)

BothConns snapshots both directions under one RLock, so a caller that needs a consistent pair (a fold round staging both) never mixes a pre- and post-reconnect conn.

func (*Session) FoldSalt added in v0.4.0

func (s *Session) FoldSalt() ([]byte, error)

FoldSalt derives the 32-byte, session-bound salt both peers must agree on for a fold round. The two SEKs are the same pair on both peers, labeled oppositely (peer A's SEKOutbound equals peer B's SEKInbound), so they are sorted before concatenation: without that normalization the peers would derive different fold secrets and every fold would fail.

func (*Session) GetFingerPrint

func (s *Session) GetFingerPrint() string

func (*Session) GetRekeyEpoch

func (s *Session) GetRekeyEpoch() uint64

GetRekeyEpoch returns the current key epoch.

func (*Session) InboundConn added in v0.4.0

func (s *Session) InboundConn() *SecureConn

InboundConn returns the current inbound conn under socketsMu, or nil. The pointer is snapshotted and the lock released before the caller uses it (leaf-lock discipline).

func (*Session) InitiateFold added in v0.4.0

func (s *Session) InitiateFold(ctx context.Context) error

InitiateFold is the initiator side of a fold round over the session's own TCP gRPC client. See InitiateFoldVia for the round itself.

func (*Session) InitiateFoldVia added in v0.4.0

func (s *Session) InitiateFoldVia(ctx context.Context, client bindings.KeibiServiceClient) error

InitiateFoldVia runs the initiator side of a fold round over the given KeibiService client; the caller picks the lane (the eager-fold driver tries QUIC first, falls back to TCP), and the round is identical either way. It derives the salt, generates a fresh ephemeral keypair, sends its publics over the Rekey RPC, derives the fold secret from the response, and stages it on both conns as the initiator. Lane FAILOVER is safe: a failed attempt that still staged the responder is superseded by the retry's fresh round (staging is supersede-idempotent). Lane DUPLICATION of one round would NOT be safe: KEM encapsulation is randomized, so a duplicate request derives a different secret.

func (*Session) IsExpired

func (s *Session) IsExpired() bool

IsExpired returns true if the session has passed its allowed timeout.

func (*Session) IsFoldInitiator added in v0.4.0

func (s *Session) IsFoldInitiator() bool

IsFoldInitiator reports whether this peer drives the fold. The lexicographically lower fingerprint initiates, the same deterministic election ReconnectManager uses, so exactly one peer calls InitiateFold while the other answers via RespondToFold.

func (*Session) IsVerified

func (s *Session) IsVerified() bool

IsVerified returns true if the fingerprint matched and session is accepted.

func (*Session) MarkError

func (s *Session) MarkError(err error)

MarkError marks the session as errored and closes any open connections.

func (*Session) NearEpochWrap added in v0.4.0

func (s *Session) NearEpochWrap() bool

NearEpochWrap reports whether a key-update session's writer epoch has climbed close enough to the wrap guard that it must re-handshake for a fresh epoch-0 key. Gates on UseKeyUpdate (only key-update sessions reach the guard); without it the ratchet would pin its epoch at the guard and stop advancing forward secrecy.

func (*Session) NegotiatedSuite added in v0.4.0

func (s *Session) NegotiatedSuite() kbc.CipherSuite

NegotiatedSuite returns the cipher suite chosen during the handshake, taken under the cipher lock, defaulting to the first supported suite when unset. The QUIC control channel uses the same suite as the TCP channel.

func (*Session) OutboundConn added in v0.4.0

func (s *Session) OutboundConn() *SecureConn

OutboundConn returns the current outbound conn under socketsMu, or nil.

func (*Session) ReadyForEncryption

func (s *Session) ReadyForEncryption() bool

ReadyForEncryption returns true when both connections and SEKs are set.

func (*Session) ResetInboundCrypto added in v0.3.3

func (s *Session) ResetInboundCrypto()

ResetInboundCrypto clears the inbound shared key. The caller closes any existing inbound conn.

func (*Session) ResetOutboundCrypto added in v0.3.3

func (s *Session) ResetOutboundCrypto()

ResetOutboundCrypto clears the outbound shared key and negotiated cipher suite so a fresh outbound handshake can run (e.g. when falling back to the bridge). The caller closes any existing outbound conn.

func (*Session) RespondToFold added in v0.4.0

func (s *Session) RespondToFold(req *bindings.RekeyRequest) (*bindings.RekeyResponse, error)

RespondToFold is the responder side of a fold round (the gRPC server handler). It derives the session-bound salt, completes the hybrid-KEM exchange against the initiator's publics, stages the secret on both conns as a gated responder, and returns the ML-KEM ciphertext and its ephemeral X25519 public for the initiator to finish with. Fails closed if the session is not ready or the request is malformed, so a stray or forged Rekey RPC cannot drive a fold.

func (*Session) SetInboundConn added in v0.4.0

func (s *Session) SetInboundConn(c *SecureConn)

SetInboundConn assigns the inbound conn under socketsMu (nil on teardown). Creates the SessionSockets holder on first use.

func (*Session) SetOutboundConn added in v0.4.0

func (s *Session) SetOutboundConn(c *SecureConn)

SetOutboundConn assigns the outbound conn under socketsMu (nil on teardown).

func (*Session) ShouldRekey

func (s *Session) ShouldRekey() bool

ShouldRekey returns true if either connection has exceeded the rekey threshold.

func (*Session) Transition

func (s *Session) Transition(next SessionState) error

Transition safely updates the session state, if allowed.

func (*Session) UseKeyUpdate added in v0.4.0

func (s *Session) UseKeyUpdate() bool

UseKeyUpdate reports whether the in-band ratchet may run: only when both peers advertised the capability. A new-to-old pair, or one with the off-switch set, returns false and rotates via the re-handshake fallback.

func (*Session) ValidateExpired

func (s *Session) ValidateExpired()

ValidateExpired moves session to 'expired' state if deadline passed.

func (*Session) ValidatePeer

func (s *Session) ValidatePeer() error

ValidatePeer ensures peer handshake and verification are complete.

func (*Session) ValidateReady

func (s *Session) ValidateReady() error

ValidateReady ensures session is fully ready for encryption and data transfer.

type SessionSockets

type SessionSockets struct {
	Inbound  *SecureConn // Bob -> Alice
	Outbound *SecureConn // Alice -> Bob
}

SessionSockets holds a duplex connection for peer communication. The two ends are built by the handshake with opposite nonce prefixes (Inbound uses NoncePrefixInbound, Outbound uses NoncePrefixOutbound) so a socket's shared key never reuses a nonce. Access the pointers through the Session accessors (InboundConn/OutboundConn/BothConns/ Set*), never directly, so the socketsMu guard holds.

type SessionState

type SessionState string
const (
	SessionInit           SessionState = "init"
	SessionStatePending   SessionState = "pending"
	SessionStateVerified  SessionState = "verified"
	SessionStateConnected SessionState = "connected"
	SessionStateError     SessionState = "error"
	SessionStateExpired   SessionState = "expired"
)

Jump to

Keyboard shortcuts

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