Documentation
¶
Index ¶
- Constants
- Variables
- func ApplyRekeyThresholdOverride(bytes, msgs uint64) (uint64, uint64)
- func ComputeFingerprintFromBase64Keys(pubKeys map[string]string) (string, error)
- func CreateRekeyRequest(ownKeys *kbc.OwnKeys, peerKeys *kbc.PeerKeys, epoch uint64, ...) (*bindings.RekeyRequest, []byte, error)
- func DialWithStableAddr(network, addr string, timeout time.Duration, logger *slog.Logger) (net.Conn, error)
- func ExchangePublicKeysLocal(session *Session, conn net.Conn, isInitiator bool) error
- func FinalizeInboundSession(session *Session, conn net.Conn, encSeeds map[string]string) error
- func PerformInboundHandshake(session *Session, conn net.Conn) error
- func PerformOutboundHandshake(session *Session, remoteAddr string) error
- func PerformOutboundHandshakeOnConn(session *Session, conn net.Conn) error
- func ProcessRekeyRequest(req *bindings.RekeyRequest, ownKeys *kbc.OwnKeys, peerKeys *kbc.PeerKeys, ...) (*bindings.RekeyResponse, []byte, error)
- func ProcessRekeyResponse(resp *bindings.RekeyResponse, ownKeys *kbc.OwnKeys, peerKeys *kbc.PeerKeys, ...) ([]byte, error)
- func SetKeyUpdateEnabled(on bool)
- func StartListener(session *Session, port int) error
- type ConnectionHealth
- type HealthMonitor
- func (m *HealthMonitor) AvgRTT() time.Duration
- func (m *HealthMonitor) Health() ConnectionHealth
- func (m *HealthMonitor) LastRTT() time.Duration
- func (m *HealthMonitor) MaxWriterEpoch() uint16
- func (m *HealthMonitor) Start()
- func (m *HealthMonitor) Stop()
- func (m *HealthMonitor) TransferEnded()
- func (m *HealthMonitor) TransferStarted()
- type PeerHandshakeMessage
- type ReconnectManager
- type ReconnectState
- type SecureConn
- func (s *SecureConn) Accept() (net.Conn, error)
- func (l *SecureConn) Addr() net.Addr
- func (s *SecureConn) Close() error
- func (s *SecureConn) GetEpoch() uint64
- func (s *SecureConn) GetStats() (bytesSent, bytesRecv, msgsSent, msgsRecv uint64)
- func (s *SecureConn) LocalAddr() net.Addr
- func (s *SecureConn) Read(p []byte) (int, error)
- func (s *SecureConn) ReadMessage() ([]byte, error)
- func (s *SecureConn) RemoteAddr() net.Addr
- func (s *SecureConn) ResetStats()
- func (s *SecureConn) SetDeadline(t time.Time) error
- func (s *SecureConn) SetKeyUpdate(on bool)
- func (s *SecureConn) SetReadDeadline(t time.Time) error
- func (s *SecureConn) SetWriteDeadline(t time.Time) error
- func (s *SecureConn) SetWriterEpochForTest(e uint16)
- func (s *SecureConn) ShouldRekey() bool
- func (s *SecureConn) StageEntropyFold(secret []byte, isInitiator bool)
- func (s *SecureConn) UpdateKey(newKek []byte)
- func (s *SecureConn) UsesKeyUpdate() bool
- func (s *SecureConn) Write(p []byte) (int, error)
- func (s *SecureConn) WriteMessage(msg []byte) error
- func (s *SecureConn) WriterEpoch() uint16
- type SecureReader
- type SecureWriter
- type Session
- func InitSession(logger *slog.Logger, defaultOutboundPort int, defaultInboundPort int) (*Session, error)
- func InitSessionWithKeys(logger *slog.Logger, keys *kbc.OwnKeys, defaultOutboundPort int, ...) (*Session, error)
- func NewSession(logger *slog.Logger, expectedFingerprint string, timeout time.Duration) *Session
- func (s *Session) AdoptFoldCustody(c *SecureConn)
- func (s *Session) ApplyKeyUpdateNegotiation()
- func (s *Session) BothConns() (inbound, outbound *SecureConn)
- func (s *Session) FoldSalt() ([]byte, error)
- func (s *Session) GetFingerPrint() string
- func (s *Session) GetRekeyEpoch() uint64
- func (s *Session) InboundConn() *SecureConn
- func (s *Session) InitiateFold(ctx context.Context) error
- func (s *Session) InitiateFoldVia(ctx context.Context, client bindings.KeibiServiceClient) error
- func (s *Session) IsExpired() bool
- func (s *Session) IsFoldInitiator() bool
- func (s *Session) IsVerified() bool
- func (s *Session) MarkError(err error)
- func (s *Session) NearEpochWrap() bool
- func (s *Session) NegotiatedSuite() kbc.CipherSuite
- func (s *Session) OutboundConn() *SecureConn
- func (s *Session) ReadyForEncryption() bool
- func (s *Session) ResetInboundCrypto()
- func (s *Session) ResetOutboundCrypto()
- func (s *Session) RespondToFold(req *bindings.RekeyRequest) (*bindings.RekeyResponse, error)
- func (s *Session) SetInboundConn(c *SecureConn)
- func (s *Session) SetOutboundConn(c *SecureConn)
- func (s *Session) ShouldRekey() bool
- func (s *Session) Transition(next SessionState) error
- func (s *Session) UseKeyUpdate() bool
- func (s *Session) ValidateExpired()
- func (s *Session) ValidatePeer() error
- func (s *Session) ValidateReady() error
- type SessionSockets
- type SessionState
Constants ¶
const ( NoncePrefixOutbound uint32 = 0x4F555442 // "OUTB" NoncePrefixInbound uint32 = 0x494E4244 // "INBD" )
Nonce prefixes for direction separation (prevents nonce reuse with same key).
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 ¶
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.
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.
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.
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.
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
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 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 ¶
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 ¶
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 ¶
PerformInboundHandshake handles the first plaintext connection from Bob to Alice.
func PerformOutboundHandshake ¶
PerformOutboundHandshake dials remoteAddr and sends the PQC handshake.
func PerformOutboundHandshakeOnConn ¶
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 ¶
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) 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) 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) 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.
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 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 ¶
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
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 (*Session) GetRekeyEpoch ¶
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
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
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) IsFoldInitiator ¶ added in v0.4.0
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 ¶
IsVerified returns true if the fingerprint matched and session is accepted.
func (*Session) NearEpochWrap ¶ added in v0.4.0
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 ¶
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 ¶
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
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 ¶
ValidatePeer ensures peer handshake and verification are complete.
func (*Session) ValidateReady ¶
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" )