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