eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1package tunnel
2
3// Relay claims: the persistence behind relay mode (docs/relay-mode.md,
4// issue #738 Phase 1). A relay is a tunnel with a stable identity: the client
5// holds an Ed25519 keypair, and the hostname it is assigned on first claim
6// belongs to that keypair from then on. Reconnects prove the key and get the
7// same hostname back; nobody else can take it.
8//
9// Claims live in the gateway's own small SQLite database — deliberately not
10// the router registry, which stays a routing table. The gateway remains the
11// only writer.
12
13import (
14 "crypto/ed25519"
15 "database/sql"
16 "encoding/base64"
17 "fmt"
18 "time"
19
20 _ "modernc.org/sqlite"
21)
22
23type Claims struct{ sql *sql.DB }
24
25func OpenClaims(path string) (*Claims, error) {
26 d, err := sql.Open("sqlite", path)
27 if err != nil {
28 return nil, err
29 }
30 d.SetMaxOpenConns(1)
31 for _, p := range []string{
32 "PRAGMA journal_mode=WAL",
33 "PRAGMA busy_timeout=5000",
34 } {
35 if _, err := d.Exec(p); err != nil {
36 return nil, err
37 }
38 }
39 if _, err := d.Exec(`
40CREATE TABLE IF NOT EXISTS relay_claims (
41 host TEXT PRIMARY KEY,
42 pubkey TEXT NOT NULL UNIQUE, -- base64url raw ed25519 public key
43 created_at INTEGER NOT NULL,
44 last_seen INTEGER NOT NULL
45);`); err != nil {
46 return nil, err
47 }
48 return &Claims{sql: d}, nil
49}
50
51// HostFor returns the hostname already claimed by pubkey ("" if none).
52func (c *Claims) HostFor(pubkey string) (string, error) {
53 var host string
54 err := c.sql.QueryRow(`SELECT host FROM relay_claims WHERE pubkey = ?`, pubkey).Scan(&host)
55 if err == sql.ErrNoRows {
56 return "", nil
57 }
58 return host, err
59}
60
61// Owner returns the pubkey that owns host ("" if unclaimed).
62func (c *Claims) Owner(host string) (string, error) {
63 var pk string
64 err := c.sql.QueryRow(`SELECT pubkey FROM relay_claims WHERE host = ?`, host).Scan(&pk)
65 if err == sql.ErrNoRows {
66 return "", nil
67 }
68 return pk, err
69}
70
71// Claim records host as owned by pubkey (first come, first kept).
72func (c *Claims) Claim(host, pubkey string) error {
73 now := time.Now().Unix()
74 _, err := c.sql.Exec(
75 `INSERT INTO relay_claims(host, pubkey, created_at, last_seen) VALUES(?,?,?,?)`,
76 host, pubkey, now, now)
77 return err
78}
79
80// Touch updates last_seen for pubkey's claim.
81func (c *Claims) Touch(pubkey string) {
82 _, _ = c.sql.Exec(`UPDATE relay_claims SET last_seen = ? WHERE pubkey = ?`, time.Now().Unix(), pubkey)
83}
84
85// DeleteByHost frees a claim: the hostname returns to the pool and whoever
86// held it can no longer reclaim it.
87func (c *Claims) DeleteByHost(host string) error {
88 _, err := c.sql.Exec(`DELETE FROM relay_claims WHERE host = ?`, host)
89 return err
90}
91
92// SweepIdle frees claims not seen for longer than idle, skipping any host in
93// keep (the currently-connected tunnels — a relay that has been online for
94// months without reconnecting is the opposite of abandoned). Returns the
95// freed hostnames.
96func (c *Claims) SweepIdle(idle time.Duration, keep map[string]bool) ([]string, error) {
97 cutoff := time.Now().Add(-idle).Unix()
98 rows, err := c.sql.Query(`SELECT host FROM relay_claims WHERE last_seen < ?`, cutoff)
99 if err != nil {
100 return nil, err
101 }
102 var stale []string
103 for rows.Next() {
104 var h string
105 if err := rows.Scan(&h); err != nil {
106 rows.Close()
107 return nil, err
108 }
109 if !keep[h] {
110 stale = append(stale, h)
111 }
112 }
113 rows.Close()
114 for _, h := range stale {
115 if err := c.DeleteByHost(h); err != nil {
116 return stale, err
117 }
118 }
119 return stale, nil
120}
121
122// VerifyClaimSig checks that sig (base64url) signs nonce with pubkey
123// (base64url raw ed25519).
124func VerifyClaimSig(pubkey, nonce, sig string) error {
125 pk, err := base64.RawURLEncoding.DecodeString(pubkey)
126 if err != nil || len(pk) != ed25519.PublicKeySize {
127 return fmt.Errorf("bad public key")
128 }
129 sg, err := base64.RawURLEncoding.DecodeString(sig)
130 if err != nil {
131 return fmt.Errorf("bad signature encoding")
132 }
133 if !ed25519.Verify(ed25519.PublicKey(pk), []byte(nonce), sg) {
134 return fmt.Errorf("signature doesn't verify")
135 }
136 return nil
137}
138