2 # This should be sourced by bash (though we welcome changes to make it POSIX sh compliant)
4 # Monkeysphere ssh-proxycommand subcommand
6 # The monkeysphere scripts are written by:
7 # Jameson Rollins <jrollins@finestructure.net>
8 # Daniel Kahn Gillmor <dkg@fifthhorseman.net>
10 # They are Copyright 2008-2009, and are all released under the GPL,
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
18 # output the key info, including the RSA fingerprint
26 # get the ssh key of the gpg key
27 sshKeyGPGFile=$(msmktempfile)
28 gpg2ssh "$keyid" >"$sshKeyGPGFile"
29 sshFingerprint=$(ssh-keygen -l -f "$sshKeyGPGFile" | \
31 rm -f "$sshKeyGPGFile"
33 # get the sigs for the matching key
34 gpgSigOut=$(gpg_user --check-sigs \
35 --list-options show-uid-validity \
40 # output the sigs, but only those on the user ID
42 echo "$gpgSigOut" | awk '
44 if (match($0,"^pub")) { print; }
45 if (match($0,"^uid")) { ok=0; }
46 if (match($0,"^uid.*'$userID'$")) { ok=1; print; }
47 if (ok) { if (match($0,"^sig")) { print; } }
51 # output ssh fingerprint
53 RSA key fingerprint is ${sshFingerprint}.
56 # output the other user IDs for reference
57 otherUids=$(echo "$gpgSigOut" | grep "^uid" | grep -v "$userID")
58 if [ "$otherUids" ] ; then
60 Other user IDs on this key:
62 echo "$otherUids" | log info
67 # "marginal case" ouput in the case that there is not a full
68 # validation path to the host
69 output_no_valid_key() {
82 userID="ssh://${HOSTP}"
86 # retrieve the ssh key being offered by the host
87 sshKeyOffered=$(ssh-keyscan -t rsa -p "$PORT" "$HOST" 2>/dev/null \
88 | awk '{ print $2, $3 }')
90 # get the gpg info for userid
91 gpgOut=$(gpg_user --list-key --fixed-list-mode --with-colon \
92 --with-fingerprint --with-fingerprint \
93 ="$userID" 2>/dev/null)
97 -------------------- Monkeysphere warning -------------------
98 Monkeysphere found OpenPGP keys for this hostname, but none had full validity.
101 # output message if host key could not be retrieved from the host
102 if [ -z "$sshKeyOffered" ] ; then
104 Could not retrieve RSA host key from $HOST.
105 The following keys were found with marginal validity:
109 # find all 'pub' and 'sub' lines in the gpg output, which each
110 # represent a retrieved key for the user ID
111 echo "$gpgOut" | cut -d: -f1,2,5,10,12 | \
112 while IFS=: read -r type validity keyid uidfpr usage ; do
115 # get the ssh key of the gpg key
116 sshKeyGPG=$(gpg2ssh "$keyid")
118 # if a key was retrieved from the host...
119 if [ "$sshKeyOffered" ] ; then
121 # if one of keys found matches the one offered by the
122 # host, then output info
123 if [ "$sshKeyGPG" = "$sshKeyOffered" ] ; then
125 An OpenPGP key matching the ssh key offered by the host was found:
128 show_key_info "$keyid" | log info
130 # this whole process is in a "while read"
131 # subshell. the only way to get information
132 # out of the subshell is to change the return
133 # code. therefore we return 1 here to
134 # indicate that a matching gpg key was found
135 # for the ssh key offered by the host
139 # else if a key was not retrieved from the host
142 # if the current key is marginal, show info
143 if [ "$validity" = 'm' -o "$validity" = 'f' ] ; then
144 show_key_info "$keyid" | log info
150 done || returnCode="$?"
152 # if no key match was made (and the "while read" subshell
153 # returned 1) output how many keys were found
154 if (( returnCode != 1 )) ; then
158 # output different footer messages depending on if a key had
159 # been retrieved from the host
160 if [ "$sshKeyOffered" ] ; then
162 None of the found keys matched the key offered by the host.
166 There may be other keys with less than marginal validity for this hostname.
171 Run the following command for more info about the found keys:
172 gpg --check-sigs --list-options show-uid-validity =${userID}
175 # FIXME: should we do anything extra here if the retrieved
176 # host key is actually in the known_hosts file and the ssh
177 # connection will succeed? Should the user be warned?
183 -------------------- ssh continues below --------------------
188 # the ssh proxycommand function itself
191 if [ "$1" = '--no-connect' ] ; then
199 if [ -z "$HOST" ] ; then
200 log error "Host not specified."
204 if [ -z "$PORT" ] ; then
209 if [ "$PORT" != '22' ] ; then
210 HOSTP="${HOST}:${PORT}"
216 # specify keyserver checking. the behavior of this proxy command is
217 # intentionally different than that of running monkeyesphere normally,
218 # and keyserver checking is intentionally done under certain
219 # circumstances. This can be overridden by setting the
220 # MONKEYSPHERE_CHECK_KEYSERVER environment variable, or by setting the
221 # CHECK_KEYSERVER variable in the monkeysphere.conf file.
223 # if the host is in the gpg keyring...
224 if gpg_user --list-key ="${URI}" &>/dev/null ; then
225 # do not check the keyserver
226 CHECK_KEYSERVER=${CHECK_KEYSERVER:="false"}
228 # if the host is NOT in the keyring...
230 # if the host key is found in the known_hosts file...
231 # FIXME: this only works for default known_hosts location
232 hostKey=$(ssh-keygen -F "$HOST" 2>/dev/null)
234 if [ "$hostKey" ] ; then
235 # do not check the keyserver
236 # FIXME: more nuanced checking should be done here to properly
237 # take into consideration hosts that join monkeysphere by
238 # converting an existing and known ssh key
239 CHECK_KEYSERVER=${CHECK_KEYSERVER:="false"}
241 # if the host key is not found in the known_hosts file...
243 # check the keyserver
244 CHECK_KEYSERVER=${CHECK_KEYSERVER:="true"}
248 # finally look in the MONKEYSPHERE_ environment variable for a
249 # CHECK_KEYSERVER setting to override all else
250 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=$CHECK_KEYSERVER}
252 # update the known_hosts file for the host
254 update_known_hosts "$HOSTP" || returnCode="$?"
256 # output on depending on the return of the update-known_hosts
257 # subcommand, which is (ultimately) the return code of the
258 # update_known_hosts function in common
259 case "$returnCode" in
261 # acceptable host key found so continue to ssh
265 # no hosts at all found so also continue (drop through to
266 # regular ssh host verification)
270 # at least one *bad* host key (and no good host keys) was
271 # found, so output some usefull information
275 # anything else drop through
280 # FIXME: what about the case where monkeysphere successfully finds a
281 # valid key for the host and adds it to the known_hosts file, but a
282 # different non-monkeysphere key for the host already exists in the
283 # known_hosts, and it is this non-ms key that is offered by the host?
284 # monkeysphere will succeed, and the ssh connection will succeed, and
285 # the user will be left with the impression that they are dealing with
286 # a OpenPGP/PKI host key when in fact they are not. should we use
287 # ssh-keyscan to compare the keys first?
289 # exec a netcat passthrough to host for the ssh connection
290 if [ -z "$NO_CONNECT" ] ; then
291 if (type nc &>/dev/null); then
292 exec nc "$HOST" "$PORT"
293 elif (type socat &>/dev/null); then
294 exec socat STDIO "TCP:$HOST:$PORT"
296 echo "Neither netcat nor socat found -- could not complete monkeysphere-ssh-proxycommand connection to $HOST:$PORT" >&2