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