summaryrefslogtreecommitdiff
path: root/src/share/m/ssh_proxycommand
blob: 110309ee6ba671575d2df90e380c2db04d0a5b02 (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. validate_monkeysphere() {
  16. local hostKey
  17. # specify keyserver checking. the behavior of this proxy command
  18. # is intentionally different than that of running monkeyesphere
  19. # normally, and keyserver checking is intentionally done under
  20. # certain circumstances. This can be overridden by setting the
  21. # MONKEYSPHERE_CHECK_KEYSERVER environment variable, or by setting
  22. # the CHECK_KEYSERVER variable in the monkeysphere.conf file.
  23. # if the host is in the gpg keyring...
  24. if gpg_user --list-key ="${URI}" &>/dev/null ; then
  25. # do not check the keyserver
  26. CHECK_KEYSERVER=${CHECK_KEYSERVER:="false"}
  27. # if the host is NOT in the keyring...
  28. else
  29. # FIXME: what about system-wide known_hosts file (/etc/ssh/known_hosts)?
  30. if [ -r "$KNOWN_HOSTS" ]; then
  31. # look up the host key is found in the known_hosts file...
  32. if (type ssh-keygen &>/dev/null) ; then
  33. hostKey=$(ssh-keygen -F "$HOST" -f "$KNOWN_HOSTS" 2>/dev/null)
  34. else
  35. # FIXME: we're not dealing with digested known_hosts
  36. # if we don't have ssh-keygen
  37. # But we could do this without needing ssh-keygen.
  38. # hashed known_hosts looks like: |1|X|Y where 1 means
  39. # SHA1 (nothing else is defined in openssh sources), X
  40. # is the salt (same length as the digest output),
  41. # base64-encoded, and Y is the digested hostname (also
  42. # base64-encoded).
  43. # see hostfile.{c,h} in openssh sources.
  44. hostKey=$(cut -f1 -d\ < .ssh/known_hosts | tr ',' '\n' | grep -Fx -e "$HOST" || :)
  45. fi
  46. fi
  47. if [ "$hostKey" ] ; then
  48. # do not check the keyserver
  49. # FIXME: more nuanced checking should be done here to properly
  50. # take into consideration hosts that join monkeysphere by
  51. # converting an existing and known ssh key
  52. CHECK_KEYSERVER=${CHECK_KEYSERVER:="false"}
  53. # if the host key is not found in the known_hosts file...
  54. else
  55. # check the keyserver
  56. CHECK_KEYSERVER=${CHECK_KEYSERVER:="true"}
  57. fi
  58. fi
  59. # finally look in the MONKEYSPHERE_ environment variable for a
  60. # CHECK_KEYSERVER setting to override all else
  61. CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=$CHECK_KEYSERVER}
  62. declare -i KEYS_PROCESSED=0
  63. declare -i KEYS_VALID=0
  64. # update the known_hosts file for the host
  65. source "${MSHAREDIR}/update_known_hosts"
  66. update_known_hosts "$HOSTP"
  67. if ((KEYS_PROCESSED > 0)) && ((KEYS_VALID == 0)) ; then
  68. log debug "output ssh marginal ui..."
  69. output_no_valid_key
  70. fi
  71. # FIXME: what about the case where monkeysphere successfully finds
  72. # a valid key for the host and adds it to the known_hosts file,
  73. # but a different non-monkeysphere key for the host already exists
  74. # in the known_hosts, and it is this non-ms key that is offered by
  75. # the host? monkeysphere will succeed, and the ssh connection
  76. # will succeed, and the user will be left with the impression that
  77. # they are dealing with a OpenPGP/PKI host key when in fact they
  78. # are not. should we use ssh-keyscan to compare the keys first?
  79. }
  80. # output the key info, including the RSA fingerprint
  81. show_key_info() {
  82. local keyid="$1"
  83. local sshKeyGPGFile
  84. local sshFingerprint
  85. local gpgSigOut
  86. local otherUids
  87. # get the ssh key of the gpg key
  88. sshFingerprint=$(gpg2ssh "$keyid" | "$SYSSHAREDIR/keytrans" sshfpr)
  89. # get the sigs for the matching key
  90. gpgSigOut=$(gpg_user --check-sigs \
  91. --list-options show-uid-validity \
  92. "$keyid")
  93. echo | log info
  94. # output the sigs, but only those on the user ID
  95. # we are looking for
  96. echo "$gpgSigOut" | awk '
  97. {
  98. if (match($0,"^pub")) { print; }
  99. if (match($0,"^uid")) { ok=0; }
  100. if (match($0,"^uid.*'$userID'$")) { ok=1; print; }
  101. if (ok) { if (match($0,"^sig")) { print; } }
  102. }
  103. '
  104. # output ssh fingerprint
  105. cat <<EOF
  106. RSA key fingerprint is ${sshFingerprint}.
  107. EOF
  108. # output the other user IDs for reference
  109. otherUids=$(echo "$gpgSigOut" | grep "^uid" | grep -v "$userID")
  110. if [ "$otherUids" ] ; then
  111. log info <<EOF
  112. Other user IDs on this key:
  113. EOF
  114. echo "$otherUids" | log info
  115. fi
  116. }
  117. # "marginal case" ouput in the case that there is not a full
  118. # validation path to the host
  119. output_no_valid_key() {
  120. local userID
  121. local sshKeyOffered
  122. local gpgOut
  123. local type
  124. local validity
  125. local keyid
  126. local uidfpr
  127. local usage
  128. local sshKeyGPG
  129. local tmpkey
  130. local returnCode=0
  131. userID="ssh://${HOSTP}"
  132. LOG_PREFIX=
  133. # if we don't have ssh-keyscan, we just don't scan:
  134. if ( type ssh-keyscan &>/dev/null ) ; then
  135. # retrieve the ssh key being offered by the host
  136. sshKeyOffered=$(ssh-keyscan -t rsa -p "$PORT" "$HOST" 2>/dev/null \
  137. | awk '{ print $2, $3 }')
  138. fi
  139. # get the gpg info for userid
  140. gpgOut=$(gpg_user --list-key --fixed-list-mode --with-colon \
  141. --with-fingerprint --with-fingerprint \
  142. ="$userID" 2>/dev/null)
  143. # output header
  144. log info <<EOF
  145. -------------------- Monkeysphere warning -------------------
  146. Monkeysphere found OpenPGP keys for this hostname, but none had full validity.
  147. EOF
  148. # output message if host key could not be retrieved from the host
  149. if [ -z "$sshKeyOffered" ] ; then
  150. log info <<EOF
  151. Could not retrieve RSA host key from $HOST.
  152. EOF
  153. # check that there are any marginally valid keys
  154. if echo "$gpgOut" | egrep -q '^(pub|sub):(m|f|u):' ; then
  155. log info <<EOF
  156. The following keys were found with marginal validity:
  157. EOF
  158. fi
  159. fi
  160. # find all keys in the gpg output ('pub' and 'sub' lines) and
  161. # output the ones that match the host key or that have marginal
  162. # validity
  163. echo "$gpgOut" | cut -d: -f1,2,5,10,12 | \
  164. while IFS=: read -r type validity keyid uidfpr usage ; do
  165. case $type in
  166. 'pub'|'sub')
  167. # get the ssh key of the gpg key
  168. sshKeyGPG=$(gpg2ssh "$keyid")
  169. # if a key was retrieved from the host...
  170. if [ "$sshKeyOffered" ] ; then
  171. # if one of the keys matches the one offered by
  172. # the host, then output info and return
  173. if [ "$sshKeyGPG" = "$sshKeyOffered" ] ; then
  174. log info <<EOF
  175. An OpenPGP key matching the ssh key offered by the host was found:
  176. EOF
  177. show_key_info "$keyid" | log info
  178. # this whole process is in a "while read"
  179. # subshell. the only way to get information
  180. # out of the subshell is to change the return
  181. # code. therefore we return 1 here to
  182. # indicate that a matching gpg key was found
  183. # for the ssh key offered by the host
  184. return 1
  185. fi
  186. # else if a key was not retrieved from the host...
  187. else
  188. # and the current key is marginal, show info
  189. if [ "$validity" = 'm' ] \
  190. || [ "$validity" = 'f' ] \
  191. || [ "$validity" = 'u' ] ; then
  192. show_key_info "$keyid" | log info
  193. fi
  194. fi
  195. ;;
  196. esac
  197. done || returnCode="$?"
  198. # if no key match was made (and the "while read" subshell
  199. # returned 1) output how many keys were found
  200. if (( returnCode == 1 )) ; then
  201. echo | log info
  202. else
  203. # if a key was retrieved, but didn't match, note this
  204. if [ "$sshKeyOffered" ] ; then
  205. log info <<EOF
  206. None of the found keys matched the key offered by the host.
  207. EOF
  208. fi
  209. # note how many invalid keys were found
  210. nInvalidKeys=$(echo "$gpgOut" | egrep '^(pub|sub):[^(m|f|u)]:' | wc -l)
  211. if ((nInvalidKeys > 0)) ; then
  212. log info <<EOF
  213. Keys found with less than marginal validity: $nInvalidKeys
  214. EOF
  215. fi
  216. log info <<EOF
  217. Run the following command for more info about the found keys:
  218. gpg --check-sigs --list-options show-uid-validity =${userID}
  219. EOF
  220. # FIXME: should we do anything extra here if the retrieved
  221. # host key is actually in the known_hosts file and the ssh
  222. # connection will succeed? Should the user be warned?
  223. # prompted?
  224. fi
  225. # output footer
  226. log info <<EOF
  227. -------------------- ssh continues below --------------------
  228. EOF
  229. }
  230. # the ssh proxycommand function itself
  231. ssh_proxycommand() {
  232. if [ "$1" = '--no-connect' ] ; then
  233. NO_CONNECT='true'
  234. shift 1
  235. fi
  236. HOST="$1"
  237. PORT="$2"
  238. if [ -z "$HOST" ] ; then
  239. log error "Host not specified."
  240. usage
  241. exit 255
  242. fi
  243. if [ -z "$PORT" ] ; then
  244. PORT=22
  245. fi
  246. # set the host URI
  247. if [ "$PORT" != '22' ] ; then
  248. HOSTP="${HOST}:${PORT}"
  249. else
  250. HOSTP="${HOST}"
  251. fi
  252. URI="ssh://${HOSTP}"
  253. validate_monkeysphere
  254. # exec a netcat passthrough to host for the ssh connection
  255. if [ -z "$NO_CONNECT" ] ; then
  256. if (type nc &>/dev/null); then
  257. exec nc "$HOST" "$PORT"
  258. elif (type socat &>/dev/null); then
  259. exec socat STDIO "TCP:$HOST:$PORT"
  260. else
  261. echo "Neither netcat nor socat found -- could not complete monkeysphere-ssh-proxycommand connection to $HOST:$PORT" >&2
  262. exit 255
  263. fi
  264. fi
  265. }