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