// Package crypto derives the keys that protect tunnel traffic and provides // the authenticated framing used on direct peer-to-peer connections. package crypto import ( "crypto/sha256" "encoding/hex" "fmt" "io" "strings" "golang.org/x/crypto/hkdf" ) // keySize is the AES-256 key length. const keySize = 32 // hkdfSalt separates this key schedule from any other use of the same tokens. const hkdfSalt = "usb-server/tunnel/v1" // TunnelSecret is the long-lived group secret derived from the three tokens. // // It deliberately is NOT the group hash. The relay is told the hash so it can // group clients, which means anyone running the relay knows it — using it to // encrypt would protect nothing from the party best positioned to look. The // tokens themselves never leave the client, and the hash is a SHA-256 of them, // so knowing the hash does not yield this secret. type TunnelSecret struct { master []byte } // DeriveTunnelSecret builds the group secret from the three tokens. // All three must be non-empty; a client configured with only the group hash // cannot participate in encrypted tunnels. func DeriveTunnelSecret(token1, token2, token3 string) (*TunnelSecret, error) { if token1 == "" || token2 == "" || token3 == "" { return nil, fmt.Errorf("all three tokens are required to derive the tunnel key") } // Same joining as the group hash, so both are bound to the same input. combined := strings.Join([]string{token1, token2, token3}, ":") master := make([]byte, keySize) r := hkdf.New(sha256.New, []byte(combined), []byte(hkdfSalt), []byte("master")) if _, err := io.ReadFull(r, master); err != nil { return nil, fmt.Errorf("deriving master key: %w", err) } return &TunnelSecret{master: master}, nil } // TunnelKey derives the key for one tunnel from its ID. // // Every tunnel gets a fresh random ID, so each connection gets a distinct key // and nonces can restart from zero without ever repeating a (key, nonce) pair. func (s *TunnelSecret) TunnelKey(tunnelID string) ([]byte, error) { key := make([]byte, keySize) r := hkdf.New(sha256.New, s.master, []byte(hkdfSalt), []byte("tunnel:"+tunnelID)) if _, err := io.ReadFull(r, key); err != nil { return nil, fmt.Errorf("deriving tunnel key: %w", err) } return key, nil } // PeerToken produces a short value a peer can present to prove it knows the // group secret, bound to the given context string. // // This authenticates direct connections: the relay can tell two clients how to // reach each other, but it cannot forge this, so a peer that presents a valid // token really is a group member rather than whoever happens to reach the port. func (s *TunnelSecret) PeerToken(context string) string { r := hkdf.New(sha256.New, s.master, []byte(hkdfSalt), []byte("peer-token:"+context)) token := make([]byte, 32) io.ReadFull(r, token) return hex.EncodeToString(token) }