1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
package main
import (
"fmt"
"golang.org/x/sync/errgroup"
"io"
"net"
"git.samanthony.xyz/hose/key"
)
// handshake exchanges public keys with a remote host.
// The user is asked to verify the fingerprint of the received key
// before it is saved in the known hosts file.
func handshake(rhost string) error {
logf("initiating handshake with %s...", rhost)
var group errgroup.Group
group.Go(func() error { return handshakeSend(rhost) })
group.Go(func() error { return handshakeRecv(rhost) })
return group.Wait()
}
// handshakeSend sends the local public key to a remote host.
func handshakeSend(rhost string) error {
pubkey, err := key.LoadPublicKey()
if err != nil {
return err
}
raddr := net.JoinHostPort(rhost, port)
logf("connecting to %s...", raddr)
conn, err := net.Dial(network, raddr)
if err != nil {
return err
}
defer conn.Close()
logf("connected to %s", raddr)
if _, err := conn.Write(pubkey[:]); err != nil {
return err
}
logf("sent public key to %s", rhost)
return nil
}
// handshakeRecv receives the public key of a remote host.
// 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 {
// 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: %x\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
}
|