diff options
| -rw-r--r-- | handshake.go | 87 |
1 files changed, 86 insertions, 1 deletions
diff --git a/handshake.go b/handshake.go index e47813f..cfe7085 100644 --- a/handshake.go +++ b/handshake.go @@ -1,7 +1,9 @@ package main import ( + "fmt" "golang.org/x/sync/errgroup" + "io" "net" "git.samanthony.xyz/hose/key" @@ -46,5 +48,88 @@ func handshakeSend(rhost string) error { // The user is asked to verify the fingerprint of the key before // it is saved to the known hosts file. func handshakeRecv(rhost string) error { - // TODO + // Listen for connection. + laddr := net.JoinHostPort("", port) + ln, err := net.Listen(network, laddr) + if err != nil { + return err + } + defer ln.Close() + logf("listening on %s", laddr) + + conn, err := ln.Accept() + if err != nil { + return err + } + defer conn.Close() + logf("accepted connection from %s", conn.RemoteAddr()) + + // Receive public key from remote host. + var rpubkey [32]byte + _, err = io.ReadFull(conn, rpubkey[:]) + if err != nil { + return err + } + logf("received public key from $s", conn.RemoteAddr()) + + // Ask user to verify the fingerprint of the key. + ok, err := verifyPublicKey(conn.RemoteAddr(), rpubkey) + if err != nil { + return err + } + if !ok { + // User rejected the fingerprint. + return fmt.Errorf("host key verification failed") + } + + return addKnownHost(conn.RemoteAddr(), rpubkey) +} + +// verifyPublicKey asks the user to verify the fingerprint of a public key belonging to a remote host. +// It returns true if the user accepts the fingerprint, or false if they don't, or a non-nil error. +func verifyPublicKey(addr net.Addr, pubkey [32]byte) (bool, error) { + // Lookup human-friendly name of remote host, or fall back to the address. + hostname, err := lookupAddr(addr.String()) + if err != nil { + return false, err + } + + // Ask host to verify fingerprint. + logf("Fingerprint of host %q: %s\nIs this the correct fingerprint (yes/[no])?", + hostname, fingerprint(pubkey)) + var response string + n, err := fmt.Scanln(&response) + if err != nil { + return false, err + } + for n > 0 && response != "yes" && response != "no" { + logf("Please type 'yes' or 'no'") + n, err = fmt.Scanln(&response) + if err != nil { + return false, err + } + } + if n == 0 { + response = "no" // default response (pressed enter without typing anything) + } + switch response { + case "yes": + return true, nil + case "no": + return false, nil + } + panic("unreachable") +} + +// lookupAddr attempts to resolve the host name of an IP address. +// If no names are mapped to the address, the address itself is returned. +func lookupAddr(addr string) (string, error) { + hostNames, err := net.LookupAddr(addr) + if err != nil { + return "", err + } + if len(hostNames) > 0 { + return hostNames[0], nil + } + return addr, nil } |