diff options
| author | Sam Anthony <sam@samanthony.xyz> | 2025-04-16 18:21:13 -0400 |
|---|---|---|
| committer | Sam Anthony <sam@samanthony.xyz> | 2025-04-16 18:21:13 -0400 |
| commit | 7b3042859c3f594c7638074987ebb6c32de5cc56 (patch) | |
| tree | 7862f25b2f9a09edd8085c9f185dda9881f9754c /handshake | |
| parent | 0d3f55a926e42ffa45febc4cc722f3d7873206e0 (diff) | |
| download | hose-7b3042859c3f594c7638074987ebb6c32de5cc56.zip | |
refactor handshake
Diffstat (limited to 'handshake')
| -rw-r--r-- | handshake/handshake.go | 174 | ||||
| -rw-r--r-- | handshake/receive.go | 139 | ||||
| -rw-r--r-- | handshake/send.go | 69 |
3 files changed, 208 insertions, 174 deletions
diff --git a/handshake/handshake.go b/handshake/handshake.go index 880e907..e5e3c37 100644 --- a/handshake/handshake.go +++ b/handshake/handshake.go @@ -1,21 +1,10 @@ package handshake import ( - "bufio" "context" - "errors" - "fmt" "golang.org/x/sync/errgroup" - "io" - "net" - "net/netip" - "os" - "slices" - "strings" "time" - "git.samanthony.xyz/hose/hosts" - "git.samanthony.xyz/hose/key" "git.samanthony.xyz/hose/util" ) @@ -27,15 +16,6 @@ const ( retryInterval = 500 * time.Millisecond ) -var errHostKey = errors.New("host key verification failed") - -type keyType string - -const ( - boxPublicKey keyType = "Public encryption key" - sigPublicKey = "Public signature verification key" -) - // Handshake exchanges public keys with a remote host. // The user is asked to verify the received keys before they are saved in the known hosts file. func Handshake(rhost string) error { @@ -66,157 +46,3 @@ func Handshake(rhost string) error { return nil } } - -// send sends the local public box (encryption) key to a remote host. -func send(rhost string) error { - pubBoxkey, err := key.LoadBoxPublicKey() - if err != nil { - return err - } - - pubSigKey, err := key.LoadSigPublicKey() - if err != nil { - return err - } - - raddr := net.JoinHostPort(rhost, port) - util.Logf("connecting to %s...", raddr) - conn, err := dialWithTimeout(network, raddr, timeout) - if err != nil { - return err - } - defer conn.Close() - util.Logf("connected to %s", raddr) - - if _, err := conn.Write(pubBoxkey[:]); err != nil { - return err - } - if _, err := conn.Write(pubSigKey[:]); err != nil { - return err - } - - util.Logf("sent public keys to %s", rhost) - return nil -} - -func dialWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) { - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - for { - select { - case <-ctx.Done(): // timeout. - return nil, fmt.Errorf("dial %s %s: connection refused", network, address) - default: - } - conn, err := net.Dial(network, address) - if err == nil { - return conn, nil - } - time.Sleep(retryInterval) - } -} - -// receive receives the public keys of a remote host. -// The user is asked to verify the keys before they are saved to the known hosts file. -func receive(rhost string) error { - // Listen for connection. - laddr := net.JoinHostPort("", port) - ln, err := net.Listen(network, laddr) - if err != nil { - return err - } - defer ln.Close() - util.Logf("listening on %s", laddr) - - conn, err := ln.Accept() - if err != nil { - return err - } - defer conn.Close() - util.Logf("accepted connection from %s", conn.RemoteAddr()) - - // Receive public box (encryption) key from remote host. - var rBoxPubKey key.BoxPublicKey - _, err = io.ReadFull(conn, rBoxPubKey[:]) - if err != nil { - return err - } - util.Logf("received public encryption key from %s", conn.RemoteAddr()) - - // Receive public signature verification key from remote host. - var rSigPubKey key.SigPublicKey - _, err = io.ReadFull(conn, rSigPubKey[:]) - if err != nil { - return err - } - util.Logf("receive public signature verification key from %s", conn.RemoteAddr()) - - // Ask user to verify the keys. - host, _, err := net.SplitHostPort(conn.RemoteAddr().String()) - if err != nil { - return err - } - raddr, err := netip.ParseAddr(host) - if err != nil { - return err - } - // Verify box key. - ok, err := verifyKey(raddr, rBoxPubKey[:], boxPublicKey) - if err != nil { - return err - } - if !ok { // user rejected the key. - return errHostKey - } - // Verify signature verification key. - ok, err = verifyKey(raddr, rSigPubKey[:], sigPublicKey) - if err != nil { - return err - } - if !ok { // user rejected the key. - return errHostKey - } - - // Save in known hosts file. - return hosts.Add(hosts.Host{raddr, rBoxPubKey, rSigPubKey}) -} - -// verifyKey asks the user to verify a key received from a remote host. -// It returns true if the user accepts the key, or false if they don't, or a non-nil error. -func verifyKey(host netip.Addr, key []byte, kt keyType) (bool, error) { - // Ask host to verify the key. - util.Logf("%s key of host %q: %x\nIs this the correct key (yes/[no])?", - kt, host, key[:]) - response, err := scan([]string{"yes", "no", ""}) - if err != nil { - return false, err - } - switch response { - case "yes": - return true, nil - case "no": - return false, nil - case "": - return false, nil // default option - } - panic("unreachable") -} - -// scan reads from stdin until the user enters one of the valid responses. -func scan(responses []string) (string, error) { - scanner := bufio.NewScanner(os.Stdin) - scanner.Scan() - if err := scanner.Err(); err != nil { - return "", err - } - response := strings.TrimSpace(scanner.Text()) - for !slices.Contains(responses, response) { - util.Logf("Please enter one of %q", responses) - scanner.Scan() - if err := scanner.Err(); err != nil { - return "", err - } - response = strings.TrimSpace(scanner.Text()) - } - return response, nil -} diff --git a/handshake/receive.go b/handshake/receive.go new file mode 100644 index 0000000..986b967 --- /dev/null +++ b/handshake/receive.go @@ -0,0 +1,139 @@ +package handshake + +import ( + "bufio" + "errors" + "io" + "net" + "net/netip" + "os" + "slices" + "strings" + + "git.samanthony.xyz/hose/hosts" + "git.samanthony.xyz/hose/key" + "git.samanthony.xyz/hose/util" +) + +type keyType string + +const ( + boxPublicKey keyType = "Public encryption key" + sigPublicKey = "Public signature verification key" +) + +var errVerifyKey = errors.New("host key verification failed") + +// receive receives the public keys of a remote host. +// The user is asked to verify the keys before they are saved to the known hosts file. +func receive(rhost string) error { + conn, err := acceptConnection() + if err != nil { + return err + } + defer conn.Close() + util.Logf("accepted connection from %s", conn.RemoteAddr()) + + rBoxPubKey, rSigPubKey, err := receiveKeys(conn) + if err != nil { + return err + } + + // Ask user to verify the keys. + host, _, err := net.SplitHostPort(conn.RemoteAddr().String()) + if err != nil { + return err + } + raddr, err := netip.ParseAddr(host) + if err != nil { + return err + } + if err := verifyKeys(raddr, rBoxPubKey, rSigPubKey); err != nil { + return err + } + + // Save in known hosts file. + return hosts.Add(hosts.Host{raddr, rBoxPubKey, rSigPubKey}) +} + +func acceptConnection() (net.Conn, error) { + laddr := net.JoinHostPort("", port) + ln, err := net.Listen(network, laddr) + if err != nil { + return nil, err + } + defer ln.Close() + util.Logf("listening on %s", laddr) + return ln.Accept() +} + +func receiveKeys(conn net.Conn) (key.BoxPublicKey, key.SigPublicKey, error) { + // Receive public box (encryption) key from remote host. + var rBoxPubKey key.BoxPublicKey + _, err := io.ReadFull(conn, rBoxPubKey[:]) + if err != nil { + return key.BoxPublicKey{}, key.SigPublicKey{}, err + } + util.Logf("received public encryption key from %s", conn.RemoteAddr()) + + // Receive public signature verification key from remote host. + var rSigPubKey key.SigPublicKey + _, err = io.ReadFull(conn, rSigPubKey[:]) + if err != nil { + return key.BoxPublicKey{}, key.SigPublicKey{}, err + } + util.Logf("receive public signature verification key from %s", conn.RemoteAddr()) + + return rBoxPubKey, rSigPubKey, nil +} + +// verifyKeys asks the user to verify keys received from a remote host. +// It returns a non-nil error if the user rejects the keys. +func verifyKeys(host netip.Addr, rBoxPubKey key.BoxPublicKey, rSigPubKey key.SigPublicKey) error { + // Verify box key. + if err := verifyKey(host, rBoxPubKey[:], boxPublicKey); err != nil { + return err + } + // Verify signature verification key. + return verifyKey(host, rSigPubKey[:], sigPublicKey) +} + +// verifyKey asks the user to verify a key received from a remote host. +// It returns a non-nil error if the user rejects the key. +func verifyKey(host netip.Addr, key []byte, kt keyType) error { + // Ask host to verify the key. + util.Logf("%s key of host %q: %x\nIs this the correct key (yes/[no])?", + kt, host, key[:]) + response, err := scan([]string{"yes", "no", ""}) + if err != nil { + return err + } + switch response { + case "yes": + return nil + case "no": + return errVerifyKey + case "": + return errVerifyKey // default option + } + panic("unreachable") +} + +// scan reads from stdin until the user enters one of the valid responses. +func scan(responses []string) (string, error) { + scanner := bufio.NewScanner(os.Stdin) + scanner.Scan() + if err := scanner.Err(); err != nil { + return "", err + } + response := strings.TrimSpace(scanner.Text()) + for !slices.Contains(responses, response) { + util.Logf("Please enter one of %q", responses) + scanner.Scan() + if err := scanner.Err(); err != nil { + return "", err + } + response = strings.TrimSpace(scanner.Text()) + } + return response, nil +} diff --git a/handshake/send.go b/handshake/send.go new file mode 100644 index 0000000..444db70 --- /dev/null +++ b/handshake/send.go @@ -0,0 +1,69 @@ +package handshake + +import ( + "context" + "fmt" + "net" + "time" + + "git.samanthony.xyz/hose/key" + "git.samanthony.xyz/hose/util" +) + +// send sends the local public box (encryption) key to a remote host. +func send(rhost string) error { + // Load keys from disc. + boxPubKey, sigPubKey, err := loadKeys() + if err != nil { + return err + } + // Send them to the remote host. + return sendKeys(rhost, boxPubKey, sigPubKey) +} + +func loadKeys() (key.BoxPublicKey, key.SigPublicKey, error) { + boxPubKey, err := key.LoadBoxPublicKey() + if err != nil { + return key.BoxPublicKey{}, key.SigPublicKey{}, err + } + sigPubKey, err := key.LoadSigPublicKey() + return boxPubKey, sigPubKey, err +} + +func sendKeys(rhost string, boxPubKey key.BoxPublicKey, sigPubKey key.SigPublicKey) error { + raddr := net.JoinHostPort(rhost, port) + util.Logf("connecting to %s...", raddr) + conn, err := dialWithTimeout(network, raddr, timeout) + if err != nil { + return err + } + defer conn.Close() + util.Logf("connected to %s", raddr) + + if _, err := conn.Write(boxPubKey[:]); err != nil { + return err + } + if _, err := conn.Write(sigPubKey[:]); err != nil { + return err + } + util.Logf("sent public keys to %s", rhost) + + return nil +} + +func dialWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + for { + select { + case <-ctx.Done(): // timeout. + return nil, fmt.Errorf("dial %s %s: connection refused", network, address) + default: + } + conn, err := net.Dial(network, address) + if err == nil { + return conn, nil + } + time.Sleep(retryInterval) + } +} |