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