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