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