// Package wireguard implements the persistence + lifecycle of // WireGuard tunnels (server + client mode) and their peer roster. // Sub-files split by concern: // // keys.go — Curve25519 keypair generation + parse helpers // interfaces.go — wireguard_interfaces CRUD // peers.go — wireguard_peers CRUD // import.go — read /etc/wireguard/*.conf into the DB package wireguard import ( "crypto/rand" "encoding/base64" "errors" "fmt" "io" "golang.org/x/crypto/curve25519" ) // Keypair holds a base64-encoded WireGuard keypair (the wire format // wg-quick prints — 32 raw bytes, base64 standard padding included). type Keypair struct { Private string Public string } // GenerateKeypair returns a fresh Curve25519 keypair, clamped per // WireGuard's spec, base64-encoded. func GenerateKeypair() (*Keypair, error) { priv := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, priv); err != nil { return nil, fmt.Errorf("rand: %w", err) } // WireGuard / curve25519 clamping: clear the lowest 3 bits of // priv[0], clear the highest bit and set the second-highest of // priv[31]. Required to make the scalar a valid Curve25519 // private key. priv[0] &= 248 priv[31] &= 127 priv[31] |= 64 pub, err := curve25519.X25519(priv, curve25519.Basepoint) if err != nil { return nil, fmt.Errorf("derive pub: %w", err) } return &Keypair{ Private: base64.StdEncoding.EncodeToString(priv), Public: base64.StdEncoding.EncodeToString(pub), }, nil } // GeneratePresharedKey returns a fresh 32-byte PSK as base64. func GeneratePresharedKey() (string, error) { psk := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, psk); err != nil { return "", err } return base64.StdEncoding.EncodeToString(psk), nil } // PublicFromPrivate derives the matching pubkey for a base64 private // key — used by the import path where the operator hands us a // private key from an existing wg-quick config. func PublicFromPrivate(privB64 string) (string, error) { priv, err := base64.StdEncoding.DecodeString(privB64) if err != nil { return "", fmt.Errorf("decode private key: %w", err) } if len(priv) != 32 { return "", errors.New("private key must be 32 bytes") } pub, err := curve25519.X25519(priv, curve25519.Basepoint) if err != nil { return "", fmt.Errorf("derive pub: %w", err) } return base64.StdEncoding.EncodeToString(pub), nil }