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