summaryrefslogtreecommitdiff
path: root/src/monkeysphere
blob: da72c9a1c906a6af9c4ff6457730a07b52645439 (plain)
  1. #!/bin/bash
  2. # monkeysphere: MonkeySphere client tool
  3. #
  4. # The monkeysphere scripts are written by:
  5. # Jameson Rollins <jrollins@fifthhorseman.net>
  6. # Daniel Kahn Gillmor <dkg@fifthhorseman.net>
  7. #
  8. # They are Copyright 2008, and are all released under the GPL, version 3
  9. # or later.
  10. ########################################################################
  11. PGRM=$(basename $0)
  12. SHARE=${MONKEYSPHERE_SHARE:-"/usr/share/monkeysphere"}
  13. export SHARE
  14. . "${SHARE}/common" || exit 1
  15. # date in UTF format if needed
  16. DATE=$(date -u '+%FT%T')
  17. # unset some environment variables that could screw things up
  18. unset GREP_OPTIONS
  19. # default return code
  20. RETURN=0
  21. # set the file creation mask to be only owner rw
  22. umask 077
  23. ########################################################################
  24. # FUNCTIONS
  25. ########################################################################
  26. usage() {
  27. cat <<EOF >&2
  28. usage: $PGRM <subcommand> [options] [args]
  29. MonkeySphere client tool.
  30. subcommands:
  31. update-known_hosts (k) [HOST]... update known_hosts file
  32. update-authorized_keys (a) update authorized_keys file
  33. gen-subkey (g) [KEYID] generate an authentication subkey
  34. --length (-l) BITS key length in bits (2048)
  35. --expire (-e) EXPIRE date to expire
  36. subkey-to-ssh-agent (s) store authentication subkey in ssh-agent
  37. help (h,?) this help
  38. EOF
  39. }
  40. # generate a subkey with the 'a' usage flags set
  41. gen_subkey(){
  42. local keyLength
  43. local keyExpire
  44. local keyID
  45. local gpgOut
  46. local userID
  47. # set default key parameter values
  48. keyLength=
  49. keyExpire=
  50. # get options
  51. TEMP=$(getopt -o l:e: -l length:,expire: -n "$PGRM" -- "$@")
  52. if [ $? != 0 ] ; then
  53. exit 1
  54. fi
  55. # Note the quotes around `$TEMP': they are essential!
  56. eval set -- "$TEMP"
  57. while true ; do
  58. case "$1" in
  59. -l|--length)
  60. keyLength="$2"
  61. shift 2
  62. ;;
  63. -e|--expire)
  64. keyExpire="$2"
  65. shift 2
  66. ;;
  67. --)
  68. shift
  69. ;;
  70. *)
  71. break
  72. ;;
  73. esac
  74. done
  75. if [ -z "$1" ] ; then
  76. # find all secret keys
  77. keyID=$(gpg --with-colons --list-secret-keys | grep ^sec | cut -f5 -d:)
  78. # if multiple sec keys exist, fail
  79. if (( $(echo "$keyID" | wc -l) > 1 )) ; then
  80. echo "Multiple secret keys found:"
  81. echo "$keyID"
  82. failure "Please specify which primary key to use."
  83. fi
  84. else
  85. keyID="$1"
  86. fi
  87. if [ -z "$keyID" ] ; then
  88. failure "You have no secret key available. You should create an OpenPGP
  89. key before joining the monkeysphere. You can do this with:
  90. gpg --gen-key"
  91. fi
  92. # get key output, and fail if not found
  93. gpgOut=$(gpg --quiet --fixed-list-mode --list-secret-keys --with-colons \
  94. "$keyID") || failure
  95. # fail if multiple sec lines are returned, which means the id
  96. # given is not unique
  97. if [ $(echo "$gpgOut" | grep '^sec:' | wc -l) -gt '1' ] ; then
  98. failure "Key ID '$keyID' is not unique."
  99. fi
  100. # prompt if an authentication subkey already exists
  101. if echo "$gpgOut" | egrep "^(sec|ssb):" | cut -d: -f 12 | grep -q a ; then
  102. echo "An authentication subkey already exists for key '$keyID'."
  103. read -p "Are you sure you would like to generate another one? (y/N) " OK; OK=${OK:N}
  104. if [ "${OK/y/Y}" != 'Y' ] ; then
  105. failure "aborting."
  106. fi
  107. fi
  108. # set subkey defaults
  109. # prompt about key expiration if not specified
  110. keyExpire=$(get_gpg_expiration "$keyExpire")
  111. # generate the list of commands that will be passed to edit-key
  112. editCommands=$(cat <<EOF
  113. addkey
  114. 7
  115. S
  116. E
  117. A
  118. Q
  119. $keyLength
  120. $keyExpire
  121. save
  122. EOF
  123. )
  124. log info "generating subkey..."
  125. fifoDir=$(mktemp -d)
  126. (umask 077 && mkfifo "$fifoDir/pass")
  127. echo "$editCommands" | gpg --passphrase-fd 3 3< "$fifoDir/pass" --expert --command-fd 0 --edit-key "$keyID" &
  128. passphrase_prompt "Please enter your passphrase for $keyID: " "$fifoDir/pass"
  129. rm -rf "$fifoDir"
  130. wait
  131. log info "done."
  132. }
  133. function subkey_to_ssh_agent() {
  134. # try to add all authentication subkeys to the agent:
  135. local sshaddresponse
  136. local secretkeys
  137. local authsubkeys
  138. local workingdir
  139. local keysuccess
  140. local subkey
  141. local publine
  142. local kname
  143. if ! test_gnu_dummy_s2k_extension ; then
  144. failure "Your version of GnuTLS does not seem capable of using with gpg's exported subkeys.
  145. You may want to consider patching or upgrading.
  146. For more details, see:
  147. http://lists.gnu.org/archive/html/gnutls-devel/2008-08/msg00005.html"
  148. fi
  149. # if there's no agent running, don't bother:
  150. if [ -z "$SSH_AUTH_SOCK" ] || ! which ssh-add >/dev/null ; then
  151. failure "No ssh-agent available."
  152. fi
  153. # and if it looks like it's running, but we can't actually talk to
  154. # it, bail out:
  155. ssh-add -l >/dev/null
  156. sshaddresponse="$?"
  157. if [ "$sshaddresponse" = "2" ]; then
  158. failure "Could not connect to ssh-agent"
  159. fi
  160. # get list of secret keys (to work around https://bugs.g10code.com/gnupg/issue945):
  161. secretkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode --fingerprint | \
  162. grep '^fpr:' | cut -f10 -d: | awk '{ print "0x" $1 "!" }')
  163. if [ -z "$secretkeys" ]; then
  164. failure "You have no secret keys in your keyring!
  165. You might want to run 'gpg --gen-key'."
  166. fi
  167. authsubkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode \
  168. --fingerprint --fingerprint $secretkeys | \
  169. cut -f1,5,10,12 -d: | grep -A1 '^ssb:[^:]*::[^:]*a[^:]*$' | \
  170. grep '^fpr::' | cut -f3 -d: | sort -u)
  171. if [ -z "$authsubkeys" ]; then
  172. failure "no authentication-capable subkeys available.
  173. You might want to 'monkeysphere gen-subkey'"
  174. fi
  175. workingdir=$(mktemp -d)
  176. umask 077
  177. mkfifo "$workingdir/passphrase"
  178. keysuccess=1
  179. # FIXME: we're currently allowing any other options to get passed
  180. # through to ssh-add. should we limit it to known ones? For
  181. # example: -d or -c and/or -t <lifetime>
  182. for subkey in $authsubkeys; do
  183. # choose a label by which this key will be known in the agent:
  184. # we are labelling the key by User ID instead of by
  185. # fingerprint, but filtering out all / characters to make sure
  186. # the filename is legit.
  187. primaryuid=$(gpg --with-colons --list-key "0x${subkey}!" | grep '^pub:' | cut -f10 -d: | tr -d /)
  188. #kname="[monkeysphere] $primaryuid"
  189. kname="$primaryuid"
  190. if [ "$1" = '-d' ]; then
  191. # we're removing the subkey:
  192. gpg --export "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname"
  193. (cd "$workingdir" && ssh-add -d "$kname")
  194. else
  195. # we're adding the subkey:
  196. mkfifo "$workingdir/$kname"
  197. gpg --quiet --passphrase-fd 3 3<"$workingdir/passphrase" \
  198. --export-options export-reset-subkey-passwd,export-minimal,no-export-attributes \
  199. --export-secret-subkeys "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname" &
  200. (cd "$workingdir" && DISPLAY=nosuchdisplay SSH_ASKPASS=/bin/false ssh-add "$@" "$kname" </dev/null )&
  201. passphrase_prompt "Enter passphrase for key $kname: " "$workingdir/passphrase"
  202. wait %2
  203. fi
  204. keysuccess="$?"
  205. rm -f "$workingdir/$kname"
  206. done
  207. rm -rf "$workingdir"
  208. # FIXME: sort out the return values: we're just returning the
  209. # success or failure of the final authentication subkey in this
  210. # case. What if earlier ones failed?
  211. exit "$keysuccess"
  212. }
  213. ########################################################################
  214. # MAIN
  215. ########################################################################
  216. # unset variables that should be defined only in config file
  217. unset KEYSERVER
  218. unset CHECK_KEYSERVER
  219. unset KNOWN_HOSTS
  220. unset HASH_KNOWN_HOSTS
  221. unset AUTHORIZED_KEYS
  222. # load global config
  223. [ -r "${ETC}/monkeysphere.conf" ] && . "${ETC}/monkeysphere.conf"
  224. # set monkeysphere home directory
  225. MONKEYSPHERE_HOME=${MONKEYSPHERE_HOME:="${HOME}/.config/monkeysphere"}
  226. mkdir -p -m 0700 "$MONKEYSPHERE_HOME"
  227. # load local config
  228. [ -e ${MONKEYSPHERE_CONFIG:="${MONKEYSPHERE_HOME}/monkeysphere.conf"} ] && . "$MONKEYSPHERE_CONFIG"
  229. # set empty config variables with ones from the environment, or from
  230. # config file, or with defaults
  231. LOG_LEVEL=${MONKEYSPHERE_LOG_LEVEL:=${LOG_LEVEL:="INFO"}}
  232. GNUPGHOME=${MONKEYSPHERE_GNUPGHOME:=${GNUPGHOME:="${HOME}/.gnupg"}}
  233. KEYSERVER=${MONKEYSPHERE_KEYSERVER:="$KEYSERVER"}
  234. # if keyserver not specified in env or monkeysphere.conf,
  235. # look in gpg.conf
  236. if [ -z "$KEYSERVER" ] ; then
  237. if [ -f "${GNUPGHOME}/gpg.conf" ] ; then
  238. KEYSERVER=$(grep -e "^[[:space:]]*keyserver " "${GNUPGHOME}/gpg.conf" | tail -1 | awk '{ print $2 }')
  239. fi
  240. fi
  241. # if it's still not specified, use the default
  242. KEYSERVER=${KEYSERVER:="subkeys.pgp.net"}
  243. CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}}
  244. KNOWN_HOSTS=${MONKEYSPHERE_KNOWN_HOSTS:=${KNOWN_HOSTS:="${HOME}/.ssh/known_hosts"}}
  245. HASH_KNOWN_HOSTS=${MONKEYSPHERE_HASH_KNOWN_HOSTS:=${HASH_KNOWN_HOSTS:="true"}}
  246. AUTHORIZED_KEYS=${MONKEYSPHERE_AUTHORIZED_KEYS:=${AUTHORIZED_KEYS:="${HOME}/.ssh/authorized_keys"}}
  247. # other variables not in config file
  248. AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:="${MONKEYSPHERE_HOME}/authorized_user_ids"}
  249. REQUIRED_HOST_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_HOST_KEY_CAPABILITY:="a"}
  250. REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
  251. # export GNUPGHOME and make sure gpg home exists with proper
  252. # permissions
  253. export GNUPGHOME
  254. mkdir -p -m 0700 "$GNUPGHOME"
  255. export LOG_LEVEL
  256. # get subcommand
  257. COMMAND="$1"
  258. [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
  259. shift
  260. case $COMMAND in
  261. 'update-known_hosts'|'update-known-hosts'|'k')
  262. MODE='known_hosts'
  263. # check permissions on the known_hosts file path
  264. if ! check_key_file_permissions "$USER" "$KNOWN_HOSTS" ; then
  265. failure "Improper permissions on known_hosts file path."
  266. fi
  267. # if hosts are specified on the command line, process just
  268. # those hosts
  269. if [ "$1" ] ; then
  270. update_known_hosts "$@"
  271. RETURN="$?"
  272. # otherwise, if no hosts are specified, process every host
  273. # in the user's known_hosts file
  274. else
  275. # exit if the known_hosts file does not exist
  276. if [ ! -e "$KNOWN_HOSTS" ] ; then
  277. log error "known_hosts file '$KNOWN_HOSTS' does not exist."
  278. exit
  279. fi
  280. process_known_hosts
  281. RETURN="$?"
  282. fi
  283. ;;
  284. 'update-authorized_keys'|'update-authorized-keys'|'a')
  285. MODE='authorized_keys'
  286. # check permissions on the authorized_user_ids file path
  287. if ! check_key_file_permissions "$USER" "$AUTHORIZED_USER_IDS" ; then
  288. failure "Improper permissions on authorized_user_ids file path."
  289. fi
  290. # check permissions on the authorized_keys file path
  291. if ! check_key_file_permissions "$USER" "$AUTHORIZED_KEYS" ; then
  292. failure "Improper permissions on authorized_keys file path."
  293. fi
  294. # exit if the authorized_user_ids file is empty
  295. if [ ! -e "$AUTHORIZED_USER_IDS" ] ; then
  296. log error "authorized_user_ids file '$AUTHORIZED_USER_IDS' does not exist."
  297. exit
  298. fi
  299. # process authorized_user_ids file
  300. process_authorized_user_ids "$AUTHORIZED_USER_IDS"
  301. RETURN="$?"
  302. ;;
  303. 'gen-subkey'|'g')
  304. gen_subkey "$@"
  305. ;;
  306. 'subkey-to-ssh-agent'|'s')
  307. subkey_to_ssh_agent "$@"
  308. ;;
  309. '--help'|'help'|'-h'|'h'|'?')
  310. usage
  311. ;;
  312. *)
  313. failure "Unknown command: '$COMMAND'
  314. Type '$PGRM help' for usage."
  315. ;;
  316. esac
  317. exit "$RETURN"