29040d856940fe2289b7d6511271f7a77e340706
[monkeysphere.git] / src / share / m / ssh_proxycommand
1 # -*-shell-script-*-
2 # This should be sourced by bash (though we welcome changes to make it POSIX sh compliant)
3
4 # Monkeysphere ssh-proxycommand subcommand
5 #
6 # The monkeysphere scripts are written by:
7 # Jameson Rollins <jrollins@finestructure.net>
8 # Daniel Kahn Gillmor <dkg@fifthhorseman.net>
9 #
10 # They are Copyright 2008-2009, and are all released under the GPL,
11 # version 3 or later.
12
13 # This is meant to be run as an ssh ProxyCommand to initiate a
14 # monkeysphere known_hosts update before an ssh connection to host is
15 # established.  Can be added to ~/.ssh/config as follows:
16 #  ProxyCommand monkeysphere ssh-proxycommand %h %p
17
18 # "marginal case" ouput in the case that there is not a full
19 # validation path to the host
20 output_no_valid_key() {
21     local sshKeyOffered
22     local userID
23     local type
24     local validity
25     local keyid
26     local uidfpr
27     local usage
28     local sshKeyGPG
29     local tmpkey
30     local sshFingerprint
31     local gpgSigOut
32
33     userID="ssh://${HOSTP}"
34
35     cat <<EOF | log info
36 -------------------- Monkeysphere warning -------------------
37 Monkeysphere found OpenPGP keys for this hostname, but none had full validity.
38 EOF
39
40     # retrieve the actual ssh key
41     sshKeyOffered=$(ssh-keyscan -t rsa -p "$PORT" "$HOST" 2>/dev/null | awk '{ print $2, $3 }')
42     # FIXME: should we do any checks for failed keyscans, eg. host not
43     # found?
44
45     # get the gpg info for userid
46     gpgOut=$(gpg --list-key --fixed-list-mode --with-colon \
47         --with-fingerprint --with-fingerprint \
48         ="$userID" 2>/dev/null)
49
50     # find all 'pub' and 'sub' lines in the gpg output, which each
51     # represent a retrieved key for the user ID
52     echo "$gpgOut" | cut -d: -f1,2,5,10,12 | \
53     while IFS=: read -r type validity keyid uidfpr usage ; do
54         case $type in
55             'pub'|'sub')
56                 # get the ssh key of the gpg key
57                 sshKeyGPG=$(gpg2ssh "$keyid")
58
59                 # if one of keys found matches the one offered by the
60                 # host, then output info
61                 if [ "$sshKeyGPG" = "$sshKeyOffered" ] ; then
62                     cat <<EOF | log info
63 An OpenPGP key matching the ssh key offered by the host was found:
64
65 EOF
66
67                     # do some crazy "Here Strings" redirection to get the key to
68                     # ssh-keygen, since it doesn't read from stdin cleanly
69                     sshFingerprint=$(ssh-keygen -l -f /dev/stdin \
70                         <<<$(echo "$sshKeyGPG") | \
71                         awk '{ print $2 }')
72
73                     # get the sigs for the matching key
74                     gpgSigOut=$(gpg --check-sigs \
75                         --list-options show-uid-validity \
76                         "$keyid")
77
78                     # output the sigs, but only those on the user ID
79                     # we are looking for
80                     echo "$gpgSigOut" | awk '
81 {
82 if (match($0,"^pub")) { print; }
83 if (match($0,"^uid")) { ok=0; }
84 if (match($0,"^uid.*'$userID'$")) { ok=1; print; }
85 if (ok) { if (match($0,"^sig")) { print; } }
86 }
87 ' | log info
88                     echo | log info
89
90                     # output the other user IDs for reference
91                     if (echo "$gpgSigOut" | grep "^uid" | grep -v -q "$userID") ; then
92                         cat <<EOF | log info
93 Other user IDs on this key:
94
95 EOF
96                         echo "$gpgSigOut" | grep "^uid" | grep -v "$userID" | log info
97                         echo | log info
98                     fi
99
100                     # output ssh fingerprint
101                     cat <<EOF | log info
102 RSA key fingerprint is ${sshFingerprint}.
103 EOF
104
105                     # this whole process is in a "while read"
106                     # subshell.  the only way to get information out
107                     # of the subshell is to change the return code.
108                     # therefore we return 1 here to indicate that a
109                     # matching gpg key was found for the ssh key
110                     # offered by the host
111                     return 1
112                 fi
113                 ;;
114         esac
115     done
116
117     # if no key match was made (and the "while read" subshell returned
118     # 1) output how many keys were found
119     if (($? != 1)) ; then
120         cat <<EOF | log info
121 None of the found keys matched the key offered by the host.
122 Run the following command for more info about the found keys:
123 gpg --check-sigs --list-options show-uid-validity =${userID}
124 EOF
125
126         # FIXME: should we do anything extra here if the retrieved
127         # host key is actually in the known_hosts file and the ssh
128         # connection will succeed?  Should the user be warned?
129         # prompted?
130     fi
131
132     cat <<EOF | log info
133 -------------------- ssh continues below --------------------
134 EOF
135 }
136
137
138 # the ssh proxycommand function itself
139 ssh_proxycommand() {
140
141 if [ "$1" = '--no-connect' ] ; then
142     NO_CONNECT='true'
143     shift 1
144 fi
145
146 HOST="$1"
147 PORT="$2"
148
149 if [ -z "$HOST" ] ; then
150     log error "Host not specified."
151     usage
152     exit 255
153 fi
154 if [ -z "$PORT" ] ; then
155     PORT=22
156 fi
157
158 # set the host URI
159 if [ "$PORT" != '22' ] ; then
160     HOSTP="${HOST}:${PORT}"
161 else
162     HOSTP="${HOST}"
163 fi
164 URI="ssh://${HOSTP}"
165
166 # specify keyserver checking.  the behavior of this proxy command is
167 # intentionally different than that of running monkeyesphere normally,
168 # and keyserver checking is intentionally done under certain
169 # circumstances.  This can be overridden by setting the
170 # MONKEYSPHERE_CHECK_KEYSERVER environment variable, or by setting the
171 # CHECK_KEYSERVER variable in the monkeysphere.conf file.
172
173 # if the host is in the gpg keyring...
174 if gpg --list-key ="${URI}" 2>&1 >/dev/null ; then
175     # do not check the keyserver
176     CHECK_KEYSERVER=${CHECK_KEYSERVER:="false"}
177
178 # if the host is NOT in the keyring...
179 else
180     # if the host key is found in the known_hosts file...
181     # FIXME: this only works for default known_hosts location
182     hostKey=$(ssh-keygen -F "$HOST" 2>/dev/null)
183
184     if [ "$hostKey" ] ; then
185         # do not check the keyserver
186         # FIXME: more nuanced checking should be done here to properly
187         # take into consideration hosts that join monkeysphere by
188         # converting an existing and known ssh key
189         CHECK_KEYSERVER=${CHECK_KEYSERVER:="false"}
190
191     # if the host key is not found in the known_hosts file...
192     else
193         # check the keyserver
194         CHECK_KEYSERVER=${CHECK_KEYSERVER:="true"}
195     fi
196 fi
197
198 # finally look in the MONKEYSPHERE_ environment variable for a
199 # CHECK_KEYSERVER setting to override all else
200 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=$CHECK_KEYSERVER}
201
202 # update the known_hosts file for the host
203 source "${MSHAREDIR}/update_known_hosts"
204 update_known_hosts "$HOSTP"
205
206 # output on depending on the return of the update-known_hosts
207 # subcommand, which is (ultimately) the return code of the
208 # update_known_hosts function in common
209 case $? in
210     0)
211         # acceptable host key found so continue to ssh
212         true
213         ;;
214     1)
215         # no hosts at all found so also continue (drop through to
216         # regular ssh host verification)
217         true
218         ;;
219     2)
220         # at least one *bad* host key (and no good host keys) was
221         # found, so output some usefull information
222         output_no_valid_key
223         ;;
224     *)
225         # anything else drop through
226         true
227         ;;
228 esac
229
230 # FIXME: what about the case where monkeysphere successfully finds a
231 # valid key for the host and adds it to the known_hosts file, but a
232 # different non-monkeysphere key for the host already exists in the
233 # known_hosts, and it is this non-ms key that is offered by the host?
234 # monkeysphere will succeed, and the ssh connection will succeed, and
235 # the user will be left with the impression that they are dealing with
236 # a OpenPGP/PKI host key when in fact they are not.  should we use
237 # ssh-keyscan to compare the keys first?
238
239 # exec a netcat passthrough to host for the ssh connection
240 if [ -z "$NO_CONNECT" ] ; then
241     if (which nc 2>/dev/null >/dev/null); then
242         exec nc "$HOST" "$PORT"
243     elif (which socat 2>/dev/null >/dev/null); then
244         exec socat STDIO "TCP:$HOST:$PORT"
245     else
246         echo "Neither netcat nor socat found -- could not complete monkeysphere-ssh-proxycommand connection to $HOST:$PORT" >&2
247         exit 255
248     fi
249 fi
250
251 }