diff --git a/hub/api/api.go b/hub/api/api.go index 975149d..8870a85 100644 --- a/hub/api/api.go +++ b/hub/api/api.go @@ -15,6 +15,7 @@ import ( "git.crumpington.com/lib/go/sqliteutil" "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/nacl/box" + "golang.org/x/crypto/nacl/sign" ) //go:embed migrations @@ -24,6 +25,7 @@ type API struct { db *sql.DB lock sync.Mutex peerIntents map[string]PeerCreateArgs + initIntents map[string]byte // Map from intent key to peer IP } func New(dbPath string) (*API, error) { @@ -39,6 +41,7 @@ func New(dbPath string) (*API, error) { a := &API{ db: sqlDB, peerIntents: map[string]PeerCreateArgs{}, + initIntents: map[string]byte{}, } return a, a.ensurePassword() @@ -141,6 +144,16 @@ func (a *API) Session_SignIn(s *Session, pwd string) error { return db.Session_SetSignedIn(a.db, s.SessionID) } +func (a *API) Peer_CreateNew(p *Peer) error { + p.Version = idgen.NextID(0) + p.PubKey = []byte{} + p.PubSignKey = []byte{} + p.APIKey = idgen.NewToken() + + return db.Peer_Insert(a.db, p) +} + +// TODO: Remove type PeerCreateArgs struct { Name string PublicIP []byte @@ -148,6 +161,7 @@ type PeerCreateArgs struct { Relay bool } +// TODO: Remove // Create the intention to add a peer. The returned code is used to complete // the peer creation. The code is valid for 5 minutes. func (a *API) Peer_CreateIntent(args PeerCreateArgs) string { @@ -167,6 +181,78 @@ func (a *API) Peer_CreateIntent(args PeerCreateArgs) string { return code } +// Create the intention to initialize a peer. The returned code is used to +// complete the peer initialization. The code is valid for 5 minutes. +func (a *API) Peer_CreateInitIntent(peerIP byte) string { + a.lock.Lock() + defer a.lock.Unlock() + + code := idgen.NewToken() + a.initIntents[code] = peerIP + + go func() { + time.Sleep(5 * time.Minute) + a.lock.Lock() + defer a.lock.Unlock() + delete(a.initIntents, code) + }() + + return code +} + +func (a *API) Peer_Init(initCode string) (*m.PeerConfig, error) { + a.lock.Lock() + defer a.lock.Unlock() + + ip, ok := a.initIntents[initCode] + if !ok { + return nil, ErrNotAuthorized + } + + peer, err := a.Peer_Get(ip) + if err != nil { + return nil, err + } + + delete(a.initIntents, initCode) + + encPubKey, encPrivKey, err := box.GenerateKey(rand.Reader) + if err != nil { + return nil, err + } + + signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader) + if err != nil { + return nil, err + } + + peer.Version = idgen.NextID(0) + peer.APIKey = idgen.NewToken() + peer.PubKey = encPubKey[:] + peer.PubSignKey = signPubKey[:] + + if err := db.Peer_UpdateFull(a.db, peer); err != nil { + return nil, err + } + + conf := a.Config_Get() + + return &m.PeerConfig{ + PeerIP: peer.PeerIP, + HubAddress: conf.HubAddress, + APIKey: peer.APIKey, + Network: conf.VPNNetwork, + PublicIP: peer.PublicIP, + Port: peer.Port, + Relay: peer.Relay, + PubKey: encPubKey[:], + PrivKey: encPrivKey[:], + PubSignKey: signPubKey[:], + PrivSignKey: signPrivKey[:], + }, nil +} + +// TODO: Remove func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) { a.lock.Lock() defer a.lock.Unlock() @@ -183,6 +269,11 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) { return nil, err } + signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader) + if err != nil { + return nil, err + } + // Get peer IP. peerIP := byte(0) @@ -202,14 +293,15 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) { } peer := &Peer{ - PeerIP: peerIP, - Version: idgen.NextID(0), - APIKey: idgen.NewToken(), - Name: args.Name, - PublicIP: args.PublicIP, - Port: args.Port, - Relay: args.Relay, - PubKey: encPubKey[:], + PeerIP: peerIP, + Version: idgen.NextID(0), + APIKey: idgen.NewToken(), + Name: args.Name, + PublicIP: args.PublicIP, + Port: args.Port, + Relay: args.Relay, + PubKey: encPubKey[:], + PubSignKey: signPubKey[:], } if err := db.Peer_Insert(a.db, peer); err != nil { @@ -219,15 +311,17 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) { conf := a.Config_Get() return &m.PeerConfig{ - PeerIP: peer.PeerIP, - HubAddress: conf.HubAddress, - APIKey: peer.APIKey, - Network: conf.VPNNetwork, - PublicIP: peer.PublicIP, - Port: peer.Port, - Relay: peer.Relay, - PubKey: encPubKey[:], - PrivKey: encPrivKey[:], + PeerIP: peer.PeerIP, + HubAddress: conf.HubAddress, + APIKey: peer.APIKey, + Network: conf.VPNNetwork, + PublicIP: peer.PublicIP, + Port: peer.Port, + Relay: peer.Relay, + PubKey: encPubKey[:], + PrivKey: encPrivKey[:], + PubSignKey: signPubKey[:], + PrivSignKey: signPrivKey[:], }, nil } diff --git a/hub/api/db/generated.go b/hub/api/db/generated.go index 1957b6f..1548afd 100644 --- a/hub/api/db/generated.go +++ b/hub/api/db/generated.go @@ -307,17 +307,18 @@ func Session_List( // ---------------------------------------------------------------------------- type Peer struct { - PeerIP byte - Version int64 - APIKey string - Name string - PublicIP []byte - Port uint16 - Relay bool - PubKey []byte + PeerIP byte + Version int64 + APIKey string + Name string + PublicIP []byte + Port uint16 + Relay bool + PubKey []byte + PubSignKey []byte } -const Peer_SelectQuery = "SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey FROM peers" +const Peer_SelectQuery = "SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey FROM peers" func Peer_Insert( tx TX, @@ -328,7 +329,7 @@ func Peer_Insert( return err } - _, err = tx.Exec("INSERT INTO peers(PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey) VALUES(?,?,?,?,?,?,?,?)", row.PeerIP, row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey) + _, err = tx.Exec("INSERT INTO peers(PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey) VALUES(?,?,?,?,?,?,?,?,?)", row.PeerIP, row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PubSignKey) return err } @@ -369,7 +370,7 @@ func Peer_UpdateFull( return err } - result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,PublicIP=?,Port=?,Relay=?,PubKey=? WHERE PeerIP=?", row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PeerIP) + result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,PublicIP=?,Port=?,Relay=?,PubKey=?,PubSignKey=? WHERE PeerIP=?", row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PubSignKey, row.PeerIP) if err != nil { return err } @@ -419,8 +420,8 @@ func Peer_Get( err error, ) { row = &Peer{} - r := tx.QueryRow("SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey FROM peers WHERE PeerIP=?", PeerIP) - err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey) + r := tx.QueryRow("SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey FROM peers WHERE PeerIP=?", PeerIP) + err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey) return } @@ -434,7 +435,7 @@ func Peer_GetWhere( ) { row = &Peer{} r := tx.QueryRow(query, args...) - err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey) + err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey) return } @@ -454,7 +455,7 @@ func Peer_Iterate( defer rows.Close() for rows.Next() { row := &Peer{} - err := rows.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey) + err := rows.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey) if !yield(row, err) { return } diff --git a/hub/api/db/tables.defs b/hub/api/db/tables.defs index 6df286f..08244e4 100644 --- a/hub/api/db/tables.defs +++ b/hub/api/db/tables.defs @@ -21,5 +21,6 @@ TABLE peers OF Peer ( PublicIP []byte, Port uint16, Relay bool, - PubKey []byte NoUpdate + PubKey []byte NoUpdate, + PubSignKey []byte NoUpdate ); diff --git a/hub/api/migrations/2024-12-27-signing-keys.sql b/hub/api/migrations/2024-12-27-signing-keys.sql new file mode 100644 index 0000000..8731917 --- /dev/null +++ b/hub/api/migrations/2024-12-27-signing-keys.sql @@ -0,0 +1 @@ +ALTER TABLE peers ADD COLUMN PubSignKey BLOB NOT NULL DEFAULT ''; diff --git a/hub/handlers.go b/hub/handlers.go index aabf3c7..238a4c5 100644 --- a/hub/handlers.go +++ b/hub/handlers.go @@ -54,11 +54,18 @@ func (a *App) _adminSignOutSubmit(s *api.Session, w http.ResponseWriter, r *http } func (a *App) _adminConfig(s *api.Session, w http.ResponseWriter, r *http.Request) error { + peers, err := a.api.Peer_List() + if err != nil { + return err + } + return a.render("/admin-config.html", w, struct { Session *api.Session + Peers []*api.Peer Config *api.Config }{ s, + peers, a.api.Config_Get(), }) } @@ -142,21 +149,6 @@ func (a *App) _adminPasswordSubmit(s *api.Session, w http.ResponseWriter, r *htt return a.redirect(w, r, "/admin/config/") } -func (a *App) _adminPeerList(s *api.Session, w http.ResponseWriter, r *http.Request) error { - peers, err := a.api.Peer_List() - if err != nil { - return err - } - - return a.render("/admin-peer-list.html", w, struct { - Session *api.Session - Peers []*api.Peer - }{ - s, - peers, - }) -} - func (a *App) _adminHosts(s *api.Session, w http.ResponseWriter, r *http.Request) error { conf := a.api.Config_Get() @@ -187,25 +179,45 @@ func (a *App) _adminPeerCreate(s *api.Session, w http.ResponseWriter, r *http.Re func (a *App) _adminPeerCreateSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error { var ipStr string - args := api.PeerCreateArgs{} + p := &api.Peer{} err := webutil.NewFormScanner(r.Form). - Scan("Name", &args.Name). + Scan("IP", &p.PeerIP). + Scan("Name", &p.Name). Scan("PublicIP", &ipStr). - Scan("Port", &args.Port). - Scan("Relay", &args.Relay). + Scan("Port", &p.Port). + Scan("Relay", &p.Relay). Error() if err != nil { return err } - if args.PublicIP, err = stringToIP(ipStr); err != nil { + if p.PublicIP, err = stringToIP(ipStr); err != nil { return err } - code := a.api.Peer_CreateIntent(args) - return a.redirect(w, r, "/admin/peer/intent-created/?Code=%s", code) + if err := a.api.Peer_CreateNew(p); err != nil { + return err + } + return a.redirect(w, r, "/admin/peer/view/?PeerIP=%d", p.PeerIP) } +func (a *App) _adminPeerInit(s *api.Session, w http.ResponseWriter, r *http.Request) error { + var peerIP byte + err := webutil.NewFormScanner(r.Form).Scan("PeerIP", &peerIP).Error() + if err != nil { + return err + } + code := a.api.Peer_CreateInitIntent(peerIP) + log.Printf("Got code: %v / %v", peerIP, cod) + + return a.render("/admin-peer-init.html", w, struct { + Session *api.Session + HubAddress string + Code string + }{s, a.api.Config_Get().HubAddress, code}) +} + +// TODO: Remove func (a *App) _adminPeerIntentCreated(s *api.Session, w http.ResponseWriter, r *http.Request) error { code := r.FormValue("Code") if code == "" { @@ -323,6 +335,17 @@ func (a *App) _adminPeerDeleteSubmit(s *api.Session, w http.ResponseWriter, r *h return a.redirect(w, r, "/admin/peer/list/") } +func (a *App) _peerInit(w http.ResponseWriter, r *http.Request) error { + code := r.FormValue("Code") + conf, err := a.api.Peer_Init(code) + if err != nil { + return err + } + + return a.sendJSON(w, conf) +} + +// TODO: Remove func (a *App) _peerCreate(w http.ResponseWriter, r *http.Request) error { code := r.FormValue("Code") conf, err := a.api.Peer_Create(code) @@ -360,14 +383,16 @@ func (a *App) _peerFetchState(w http.ResponseWriter, r *http.Request) error { } for _, p := range peers { - state.Peers[p.PeerIP] = &m.Peer{ - PeerIP: p.PeerIP, - Version: p.Version, - Name: p.Name, - PublicIP: p.PublicIP, - Port: p.Port, - Relay: p.Relay, - PubKey: p.PubKey, + if len(p.PubKey) != 0 { + state.Peers[p.PeerIP] = &m.Peer{ + PeerIP: p.PeerIP, + Version: p.Version, + Name: p.Name, + PublicIP: p.PublicIP, + Port: p.Port, + Relay: p.Relay, + PubKey: p.PubKey, + } } } diff --git a/hub/routes.go b/hub/routes.go index a29736f..a86619e 100644 --- a/hub/routes.go +++ b/hub/routes.go @@ -16,10 +16,11 @@ func (a *App) registerRoutes() { a.handleSignedIn("POST /admin/sign-out/", a._adminSignOutSubmit) a.handleSignedIn("GET /admin/password/edit/", a._adminPasswordEdit) a.handleSignedIn("POST /admin/password/edit/", a._adminPasswordSubmit) - a.handleSignedIn("GET /admin/peer/list/", a._adminPeerList) a.handleSignedIn("GET /admin/peer/hosts/", a._adminHosts) a.handleSignedIn("GET /admin/peer/create/", a._adminPeerCreate) a.handleSignedIn("POST /admin/peer/create/", a._adminPeerCreateSubmit) + a.handleSignedIn("GET /admin/peer/init/", a._adminPeerInit) + // TODO: Remove a.handleSignedIn("GET /admin/peer/intent-created/", a._adminPeerIntentCreated) a.handleSignedIn("GET /admin/peer/view/", a._adminPeerView) a.handleSignedIn("GET /admin/peer/edit/", a._adminPeerEdit) @@ -27,6 +28,7 @@ func (a *App) registerRoutes() { a.handleSignedIn("GET /admin/peer/delete/", a._adminPeerDelete) a.handleSignedIn("POST /admin/peer/delete/", a._adminPeerDeleteSubmit) - a.handlePeer("GET /peer/create/", a._peerCreate) + a.handlePeer("GET /peer/create/", a._peerCreate) // TODO: Remove + a.handlePeer("GET /peer/init/", a._peerInit) a.handlePeer("GET /peer/fetch-state/", a._peerFetchState) } diff --git a/hub/templates/admin-config.html b/hub/templates/admin-config.html index da0f8ae..2c9cb82 100644 --- a/hub/templates/admin-config.html +++ b/hub/templates/admin-config.html @@ -16,4 +16,42 @@
PeerIP | +Name | +Public IP | +Port | +Relay | +
---|---|---|---|---|
+ + {{.PeerIP}} + + | +{{.Name}} | +{{ipToString .PublicIP}} | +{{.Port}} | +{{if .Relay}}T{{else}}F{{end}} | +
No peers.
+{{- end}} {{- end}} diff --git a/hub/templates/admin-peer-create.html b/hub/templates/admin-peer-create.html index 8225fc8..1a7089b 100644 --- a/hub/templates/admin-peer-create.html +++ b/hub/templates/admin-peer-create.html @@ -3,6 +3,10 @@