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