summaryrefslogtreecommitdiff
path: root/src/share/m/ssh_proxycommand
blob: 01ca488bdf6ed805d3350ea3c25470fe5a76cad4 (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. sshKeyGPGFile=$(msmktempfile)
  24. gpg2ssh "$keyid" >"$sshKeyGPGFile"
  25. sshFingerprint=$(ssh-keygen -l -f "$sshKeyGPGFile" | \
  26. awk '{ print $2 }')
  27. rm -f "$sshKeyGPGFile"
  28. # get the sigs for the matching key
  29. gpgSigOut=$(gpg_user --check-sigs \
  30. --list-options show-uid-validity \
  31. "$keyid")
  32. echo | log info
  33. # output the sigs, but only those on the user ID
  34. # we are looking for
  35. echo "$gpgSigOut" | awk '
  36. {
  37. if (match($0,"^pub")) { print; }
  38. if (match($0,"^uid")) { ok=0; }
  39. if (match($0,"^uid.*'$userID'$")) { ok=1; print; }
  40. if (ok) { if (match($0,"^sig")) { print; } }
  41. }
  42. '
  43. # output ssh fingerprint
  44. cat <<EOF
  45. RSA key fingerprint is ${sshFingerprint}.
  46. EOF
  47. # output the other user IDs for reference
  48. otherUids=$(echo "$gpgSigOut" | grep "^uid" | grep -v "$userID")
  49. if [ "$otherUids" ] ; then
  50. log info <<EOF
  51. Other user IDs on this key:
  52. EOF
  53. echo "$otherUids" | log info
  54. fi
  55. }
  56. # "marginal case" ouput in the case that there is not a full
  57. # validation path to the host
  58. output_no_valid_key() {
  59. local userID
  60. local sshKeyOffered
  61. local gpgOut
  62. local type
  63. local validity
  64. local keyid
  65. local uidfpr
  66. local usage
  67. local sshKeyGPG
  68. local tmpkey
  69. local returnCode=0
  70. userID="ssh://${HOSTP}"
  71. LOG_PREFIX=
  72. # retrieve the ssh key being offered by the host
  73. sshKeyOffered=$(ssh-keyscan -t rsa -p "$PORT" "$HOST" 2>/dev/null \
  74. | awk '{ print $2, $3 }')
  75. # get the gpg info for userid
  76. gpgOut=$(gpg_user --list-key --fixed-list-mode --with-colon \
  77. --with-fingerprint --with-fingerprint \
  78. ="$userID" 2>/dev/null)
  79. # output header
  80. log info <<EOF
  81. -------------------- Monkeysphere warning -------------------
  82. Monkeysphere found OpenPGP keys for this hostname, but none had full validity.
  83. EOF
  84. # output message if host key could not be retrieved from the host
  85. if [ -z "$sshKeyOffered" ] ; then
  86. log info <<EOF
  87. Could not retrieve RSA host key from $HOST.
  88. EOF
  89. # check that there are any marginally valid keys
  90. if echo "$gpgOut" | egrep -q '^(pub|sub):(m|f|u):' ; then
  91. log info <<EOF
  92. The following keys were found with marginal validity:
  93. EOF
  94. fi
  95. fi
  96. # find all 'pub' and 'sub' lines in the gpg output, which each
  97. # represent a retrieved key for the user ID
  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 keys found matches the one offered by the
  107. # host, then output info
  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. # if the current key is marginal, show info
  124. if [ "$validity" = 'm' ] ; then
  125. show_key_info "$keyid" | log info
  126. fi
  127. fi
  128. ;;
  129. esac
  130. done || returnCode="$?"
  131. # if no key match was made (and the "while read" subshell
  132. # returned 1) output how many keys were found
  133. if (( returnCode != 1 )) ; then
  134. echo | log info
  135. # output different footer messages depending on if a key had
  136. # been retrieved from the host
  137. if [ "$sshKeyOffered" ] ; then
  138. log info <<EOF
  139. None of the found keys matched the key offered by the host.
  140. EOF
  141. else
  142. log info <<EOF
  143. There may be keys for this hostname with less than marginal validity.
  144. EOF
  145. fi
  146. log info <<EOF
  147. Run the following command for more info about the found keys:
  148. gpg --check-sigs --list-options show-uid-validity =${userID}
  149. EOF
  150. # FIXME: should we do anything extra here if the retrieved
  151. # host key is actually in the known_hosts file and the ssh
  152. # connection will succeed? Should the user be warned?
  153. # prompted?
  154. fi
  155. # output footer
  156. log info <<EOF
  157. -------------------- ssh continues below --------------------
  158. EOF
  159. }
  160. # the ssh proxycommand function itself
  161. ssh_proxycommand() {
  162. if [ "$1" = '--no-connect' ] ; then
  163. NO_CONNECT='true'
  164. shift 1
  165. fi
  166. HOST="$1"
  167. PORT="$2"
  168. if [ -z "$HOST" ] ; then
  169. log error "Host not specified."
  170. usage
  171. exit 255
  172. fi
  173. if [ -z "$PORT" ] ; then
  174. PORT=22
  175. fi
  176. # set the host URI
  177. if [ "$PORT" != '22' ] ; then
  178. HOSTP="${HOST}:${PORT}"
  179. else
  180. HOSTP="${HOST}"
  181. fi
  182. URI="ssh://${HOSTP}"
  183. # specify keyserver checking. the behavior of this proxy command is
  184. # intentionally different than that of running monkeyesphere normally,
  185. # and keyserver checking is intentionally done under certain
  186. # circumstances. This can be overridden by setting the
  187. # MONKEYSPHERE_CHECK_KEYSERVER environment variable, or by setting the
  188. # CHECK_KEYSERVER variable in the monkeysphere.conf file.
  189. # if the host is in the gpg keyring...
  190. if gpg_user --list-key ="${URI}" &>/dev/null ; then
  191. # do not check the keyserver
  192. CHECK_KEYSERVER=${CHECK_KEYSERVER:="false"}
  193. # if the host is NOT in the keyring...
  194. else
  195. # if the host key is found in the known_hosts file...
  196. # FIXME: this only works for default known_hosts location
  197. hostKey=$(ssh-keygen -F "$HOST" 2>/dev/null)
  198. if [ "$hostKey" ] ; then
  199. # do not check the keyserver
  200. # FIXME: more nuanced checking should be done here to properly
  201. # take into consideration hosts that join monkeysphere by
  202. # converting an existing and known ssh key
  203. CHECK_KEYSERVER=${CHECK_KEYSERVER:="false"}
  204. # if the host key is not found in the known_hosts file...
  205. else
  206. # check the keyserver
  207. CHECK_KEYSERVER=${CHECK_KEYSERVER:="true"}
  208. fi
  209. fi
  210. # finally look in the MONKEYSPHERE_ environment variable for a
  211. # CHECK_KEYSERVER setting to override all else
  212. CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=$CHECK_KEYSERVER}
  213. # update the known_hosts file for the host
  214. local returnCode=0
  215. update_known_hosts "$HOSTP" || returnCode="$?"
  216. # output on depending on the return of the update-known_hosts
  217. # subcommand, which is (ultimately) the return code of the
  218. # update_known_hosts function in common
  219. case "$returnCode" in
  220. 0)
  221. # acceptable host key found so continue to ssh
  222. true
  223. ;;
  224. 1)
  225. # no hosts at all found so also continue (drop through to
  226. # regular ssh host verification)
  227. true
  228. ;;
  229. 2)
  230. # at least one *bad* host key (and no good host keys) was
  231. # found, so output some usefull information
  232. output_no_valid_key
  233. ;;
  234. *)
  235. # anything else drop through
  236. true
  237. ;;
  238. esac
  239. # FIXME: what about the case where monkeysphere successfully finds a
  240. # valid key for the host and adds it to the known_hosts file, but a
  241. # different non-monkeysphere key for the host already exists in the
  242. # known_hosts, and it is this non-ms key that is offered by the host?
  243. # monkeysphere will succeed, and the ssh connection will succeed, and
  244. # the user will be left with the impression that they are dealing with
  245. # a OpenPGP/PKI host key when in fact they are not. should we use
  246. # ssh-keyscan to compare the keys first?
  247. # exec a netcat passthrough to host for the ssh connection
  248. if [ -z "$NO_CONNECT" ] ; then
  249. if (type nc &>/dev/null); then
  250. exec nc "$HOST" "$PORT"
  251. elif (type socat &>/dev/null); then
  252. exec socat STDIO "TCP:$HOST:$PORT"
  253. else
  254. echo "Neither netcat nor socat found -- could not complete monkeysphere-ssh-proxycommand connection to $HOST:$PORT" >&2
  255. exit 255
  256. fi
  257. fi
  258. }