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