HA! I figured out how to get ssh-keygen to read stdin by using the
[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                     # do some crazy "Here Strings" redirection to get the key to
81                     # ssh-keygen, since it doesn't read from stdin cleanly
82                     sshFingerprint=$(ssh-keygen -l -f /dev/stdin \
83                         <<<$(echo "$sshKeyGPG") | \
84                         awk '{ print $2 }')
85
86                     # get the sigs for the matching key
87                     gpgSigOut=$(gpg --check-sigs \
88                         --list-options show-uid-validity \
89                         "$keyid")
90
91                     # output the sigs, but only those on the user ID
92                     # we are looking for
93                     echo "$gpgSigOut" | awk '
94 {
95 if (match($0,"^pub")) { print; }
96 if (match($0,"^uid")) { ok=0; }
97 if (match($0,"^uid.*'$userID'$")) { ok=1; print; }
98 if (ok) { if (match($0,"^sig")) { print; } }
99 }
100 ' >&2
101                     log
102
103                     # output the other user IDs for reference
104                     if (echo "$gpgSigOut" | grep "^uid" | grep -v -q "$userID") ; then
105                         log "Other user IDs on this key:"
106                         echo "$gpgSigOut" | grep "^uid" | grep -v "$userID" >&2
107                         log
108                     fi
109
110                     # output ssh fingerprint
111                     log "RSA key fingerprint is ${sshFingerprint}."
112
113                     # this whole process is in a "while read"
114                     # subshell.  the only way to get information out
115                     # of the subshell is to change the return code.
116                     # therefore we return 1 here to indicate that a
117                     # matching gpg key was found for the ssh key
118                     # offered by the host
119                     return 1
120                 fi
121                 ;;
122         esac
123     done
124
125     # if no key match was made (and the "while read" subshell returned
126     # 1) output how many keys were found
127     if (($? != 1)) ; then
128         log "None of the found keys matched the key offered by the host."
129         log "Run the following command for more info about the found keys:"
130         log "gpg --check-sigs --list-options show-uid-validity =${userID}"
131         # FIXME: should we do anything extra here if the retrieved
132         # host key is actually in the known_hosts file and the ssh
133         # connection will succeed?  Should the user be warned?
134         # prompted?
135     fi
136
137     log "-------------------- ssh continues below --------------------"
138 }
139
140 ########################################################################
141
142 # export the monkeysphere log level
143 export MONKEYSPHERE_LOG_LEVEL
144
145 if [ "$1" = '--no-connect' ] ; then
146     NO_CONNECT='true'
147     shift 1
148 fi
149
150 HOST="$1"
151 PORT="$2"
152
153 if [ -z "$HOST" ] ; then
154     log "Host not specified."
155     usage
156     exit 255
157 fi
158 if [ -z "$PORT" ] ; then
159     PORT=22
160 fi
161
162 # set the host URI
163 if [ "$PORT" != '22' ] ; then
164     HOSTP="${HOST}:${PORT}"
165 else
166     HOSTP="${HOST}"
167 fi
168 URI="ssh://${HOSTP}"
169
170 # specify keyserver checking.  the behavior of this proxy command is
171 # intentionally different than that of running monkeyesphere normally,
172 # and keyserver checking is intentionally done under certain
173 # circumstances.  This can be overridden by setting the
174 # MONKEYSPHERE_CHECK_KEYSERVER environment variable.
175
176 # if the host is in the gpg keyring...
177 if gpg --list-key ="${URI}" 2>&1 >/dev/null ; then
178     # do not check the keyserver
179     CHECK_KEYSERVER="false"
180
181 # if the host is NOT in the keyring...
182 else
183     # if the host key is found in the known_hosts file...
184     # FIXME: this only works for default known_hosts location
185     hostKey=$(ssh-keygen -F "$HOST" 2>/dev/null)
186
187     if [ "$hostKey" ] ; then
188         # do not check the keyserver
189         # FIXME: more nuanced checking should be done here to properly
190         # take into consideration hosts that join monkeysphere by
191         # converting an existing and known ssh key
192         CHECK_KEYSERVER="false"
193
194     # if the host key is not found in the known_hosts file...
195     else
196         # check the keyserver
197         CHECK_KEYSERVER="true"
198     fi
199 fi
200 # set and export the variable for use by monkeysphere
201 MONKEYSPHERE_CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:="$CHECK_KEYSERVER"}
202 export MONKEYSPHERE_CHECK_KEYSERVER
203
204 # update the known_hosts file for the host
205 monkeysphere update-known_hosts "$HOSTP"
206
207 # output on depending on the return of the update-known_hosts
208 # subcommand, which is (ultimately) the return code of the
209 # update_known_hosts function in common
210 case $? in
211     0)
212         # acceptable host key found so continue to ssh
213         true
214         ;;
215     1)
216         # no hosts at all found so also continue (drop through to
217         # regular ssh host verification)
218         true
219         ;;
220     2)
221         # at least one *bad* host key (and no good host keys) was
222         # found, so output some usefull information
223         output_no_valid_key
224         ;;
225     *)
226         # anything else drop through
227         true
228         ;;
229 esac
230
231 # FIXME: what about the case where monkeysphere successfully finds a
232 # valid key for the host and adds it to the known_hosts file, but a
233 # different non-monkeysphere key for the host already exists in the
234 # known_hosts, and it is this non-ms key that is offered by the host?
235 # monkeysphere will succeed, and the ssh connection will succeed, and
236 # the user will be left with the impression that they are dealing with
237 # a OpenPGP/PKI host key when in fact they are not.  should we use
238 # ssh-keyscan to compare the keys first?
239
240 # exec a netcat passthrough to host for the ssh connection
241 if [ -z "$NO_CONNECT" ] ; then
242     if (which nc 2>/dev/null >/dev/null); then
243         exec nc "$HOST" "$PORT"
244     elif (which socat 2>/dev/null >/dev/null); then
245         exec socat STDIO "TCP:$HOST:$PORT"
246     else
247         echo "Neither netcat nor socat found -- could not complete monkeysphere-ssh-proxycommand connection to $HOST:$PORT" >&2
248         exit 255
249     fi
250 fi