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