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.
106 # check that there are any marginally valid keys
107 if echo "$gpgOut" | egrep -q '^(pub|sub):(m|f|u):' ; then
109 The following keys were found with marginal validity:
114 # find all 'pub' and 'sub' lines in the gpg output, which each
115 # represent a retrieved key for the user ID
116 echo "$gpgOut" | cut -d: -f1,2,5,10,12 | \
117 while IFS=: read -r type validity keyid uidfpr usage ; do
120 # get the ssh key of the gpg key
121 sshKeyGPG=$(gpg2ssh "$keyid")
123 # if a key was retrieved from the host...
124 if [ "$sshKeyOffered" ] ; then
126 # if one of keys found matches the one offered by the
127 # host, then output info
128 if [ "$sshKeyGPG" = "$sshKeyOffered" ] ; then
130 An OpenPGP key matching the ssh key offered by the host was found:
133 show_key_info "$keyid" | log info
135 # this whole process is in a "while read"
136 # subshell. the only way to get information
137 # out of the subshell is to change the return
138 # code. therefore we return 1 here to
139 # indicate that a matching gpg key was found
140 # for the ssh key offered by the host
144 # else if a key was not retrieved from the host
147 # if the current key is marginal, show info
148 if [ "$validity" = 'm' ] ; then
149 show_key_info "$keyid" | log info
154 done || returnCode="$?"
156 # if no key match was made (and the "while read" subshell
157 # returned 1) output how many keys were found
158 if (( returnCode != 1 )) ; then
162 # output different footer messages depending on if a key had
163 # been retrieved from the host
164 if [ "$sshKeyOffered" ] ; then
166 None of the found keys matched the key offered by the host.
170 There may be keys for this hostname with less than marginal validity.
174 Run the following command for more info about the found keys:
175 gpg --check-sigs --list-options show-uid-validity =${userID}
178 # FIXME: should we do anything extra here if the retrieved
179 # host key is actually in the known_hosts file and the ssh
180 # connection will succeed? Should the user be warned?
186 -------------------- ssh continues below --------------------
191 # the ssh proxycommand function itself
194 if [ "$1" = '--no-connect' ] ; then
202 if [ -z "$HOST" ] ; then
203 log error "Host not specified."
207 if [ -z "$PORT" ] ; then
212 if [ "$PORT" != '22' ] ; then
213 HOSTP="${HOST}:${PORT}"
219 # specify keyserver checking. the behavior of this proxy command is
220 # intentionally different than that of running monkeyesphere normally,
221 # and keyserver checking is intentionally done under certain
222 # circumstances. This can be overridden by setting the
223 # MONKEYSPHERE_CHECK_KEYSERVER environment variable, or by setting the
224 # CHECK_KEYSERVER variable in the monkeysphere.conf file.
226 # if the host is in the gpg keyring...
227 if gpg_user --list-key ="${URI}" &>/dev/null ; then
228 # do not check the keyserver
229 CHECK_KEYSERVER=${CHECK_KEYSERVER:="false"}
231 # if the host is NOT in the keyring...
233 # if the host key is found in the known_hosts file...
234 hostKey=$( [ ! -r "$KNOWN_HOSTS" ] || ssh-keygen -F "$HOST" -f "$KNOWN_HOSTS" 2>/dev/null)
236 if [ "$hostKey" ] ; then
237 # do not check the keyserver
238 # FIXME: more nuanced checking should be done here to properly
239 # take into consideration hosts that join monkeysphere by
240 # converting an existing and known ssh key
241 CHECK_KEYSERVER=${CHECK_KEYSERVER:="false"}
243 # if the host key is not found in the known_hosts file...
245 # check the keyserver
246 CHECK_KEYSERVER=${CHECK_KEYSERVER:="true"}
250 # finally look in the MONKEYSPHERE_ environment variable for a
251 # CHECK_KEYSERVER setting to override all else
252 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=$CHECK_KEYSERVER}
254 # update the known_hosts file for the host
256 update_known_hosts "$HOSTP" || returnCode="$?"
258 # output on depending on the return of the update-known_hosts
259 # subcommand, which is (ultimately) the return code of the
260 # update_known_hosts function in common
261 case "$returnCode" in
263 # acceptable host key found so continue to ssh
267 # no hosts at all found so also continue (drop through to
268 # regular ssh host verification)
272 # at least one *bad* host key (and no good host keys) was
273 # found, so output some usefull information
277 # anything else drop through
282 # FIXME: what about the case where monkeysphere successfully finds a
283 # valid key for the host and adds it to the known_hosts file, but a
284 # different non-monkeysphere key for the host already exists in the
285 # known_hosts, and it is this non-ms key that is offered by the host?
286 # monkeysphere will succeed, and the ssh connection will succeed, and
287 # the user will be left with the impression that they are dealing with
288 # a OpenPGP/PKI host key when in fact they are not. should we use
289 # ssh-keyscan to compare the keys first?
291 # exec a netcat passthrough to host for the ssh connection
292 if [ -z "$NO_CONNECT" ] ; then
293 if (type nc &>/dev/null); then
294 exec nc "$HOST" "$PORT"
295 elif (type socat &>/dev/null); then
296 exec socat STDIO "TCP:$HOST:$PORT"
298 echo "Neither netcat nor socat found -- could not complete monkeysphere-ssh-proxycommand connection to $HOST:$PORT" >&2