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