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