3 # monkeysphere: MonkeySphere client tool
5 # The monkeysphere scripts are written by:
6 # Jameson Rollins <jrollins@fifthhorseman.net>
7 # Daniel Kahn Gillmor <dkg@fifthhorseman.net>
9 # They are Copyright 2008, and are all released under the GPL, version 3
12 ########################################################################
15 SHARE=${MONKEYSPHERE_SHARE:-"/usr/share/monkeysphere"}
17 . "${SHARE}/common" || exit 1
19 # date in UTF format if needed
20 DATE=$(date -u '+%FT%T')
22 # unset some environment variables that could screw things up
28 # set the file creation mask to be only owner rw
31 ########################################################################
33 ########################################################################
37 usage: $PGRM <subcommand> [options] [args]
38 MonkeySphere client tool.
41 update-known_hosts (k) [HOST]... update known_hosts file
42 update-authorized_keys (a) update authorized_keys file
43 gen-subkey (g) [KEYID] generate an authentication subkey
44 --length (-l) BITS key length in bits (2048)
45 --expire (-e) EXPIRE date to expire
46 subkey-to-ssh-agent (s) store authentication subkey in ssh-agent
52 # generate a subkey with the 'a' usage flags set
60 # set default key parameter values
65 TEMP=$(getopt -o l:e: -l length:,expire: -n "$PGRM" -- "$@")
71 # Note the quotes around `$TEMP': they are essential!
94 # find all secret keys
95 keyID=$(gpg --with-colons --list-secret-keys | grep ^sec | cut -f5 -d:)
96 # if multiple sec keys exist, fail
97 if (( $(echo "$keyID" | wc -l) > 1 )) ; then
98 echo "Multiple secret keys found:"
100 failure "Please specify which primary key to use."
105 if [ -z "$keyID" ] ; then
106 failure "You have no secret key available. You should create an OpenPGP
107 key before joining the monkeysphere. You can do this with:
111 # get key output, and fail if not found
112 gpgOut=$(gpg --quiet --fixed-list-mode --list-secret-keys --with-colons \
115 # fail if multiple sec lines are returned, which means the id
116 # given is not unique
117 if [ $(echo "$gpgOut" | grep '^sec:' | wc -l) -gt '1' ] ; then
118 failure "Key ID '$keyID' is not unique."
121 # prompt if an authentication subkey already exists
122 if echo "$gpgOut" | egrep "^(sec|ssb):" | cut -d: -f 12 | grep -q a ; then
123 echo "An authentication subkey already exists for key '$keyID'."
124 read -p "Are you sure you would like to generate another one? (y/N) " OK; OK=${OK:N}
125 if [ "${OK/y/Y}" != 'Y' ] ; then
130 # set subkey defaults
131 # prompt about key expiration if not specified
132 keyExpire=$(get_gpg_expiration "$keyExpire")
134 # generate the list of commands that will be passed to edit-key
135 editCommands=$(cat <<EOF
148 log "generating subkey..."
150 (umask 077 && mkfifo "$fifoDir/pass")
151 echo "$editCommands" | gpg --passphrase-fd 3 3< "$fifoDir/pass" --expert --command-fd 0 --edit-key "$keyID" &
153 passphrase_prompt "Please enter your passphrase for $keyID: " "$fifoDir/pass"
160 function subkey_to_ssh_agent() {
161 # try to add all authentication subkeys to the agent:
172 if ! test_gnu_dummy_s2k_extension ; then
173 failure "Your version of GnuTLS does not seem capable of using with gpg's exported subkeys.
174 You may want to consider patching or upgrading.
176 For more details, see:
177 http://lists.gnu.org/archive/html/gnutls-devel/2008-08/msg00005.html"
180 # if there's no agent running, don't bother:
181 if [ -z "$SSH_AUTH_SOCK" ] || ! which ssh-add >/dev/null ; then
182 failure "No ssh-agent available."
185 # and if it looks like it's running, but we can't actually talk to
187 ssh-add -l >/dev/null
189 if [ "$sshaddresponse" = "2" ]; then
190 failure "Could not connect to ssh-agent"
193 # get list of secret keys (to work around https://bugs.g10code.com/gnupg/issue945):
194 secretkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode --fingerprint | \
195 grep '^fpr:' | cut -f10 -d: | awk '{ print "0x" $1 "!" }')
197 if [ -z "$secretkeys" ]; then
198 failure "You have no secret keys in your keyring!
199 You might want to run 'gpg --gen-key'."
202 authsubkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode \
203 --fingerprint --fingerprint $secretkeys | \
204 cut -f1,5,10,12 -d: | grep -A1 '^ssb:[^:]*::[^:]*a[^:]*$' | \
205 grep '^fpr::' | cut -f3 -d: | sort -u)
207 if [ -z "$authsubkeys" ]; then
208 failure "no authentication-capable subkeys available.
209 You might want to 'monkeysphere gen-subkey'"
212 workingdir=$(mktemp -d)
214 mkfifo "$workingdir/passphrase"
217 # FIXME: we're currently allowing any other options to get passed
218 # through to ssh-add. should we limit it to known ones? For
219 # example: -d or -c and/or -t <lifetime>
221 for subkey in $authsubkeys; do
222 # choose a label by which this key will be known in the agent:
223 # we are labelling the key by User ID instead of by
224 # fingerprint, but filtering out all / characters to make sure
225 # the filename is legit.
227 primaryuid=$(gpg --with-colons --list-key "0x${subkey}!" | grep '^pub:' | cut -f10 -d: | tr -d /)
229 #kname="[monkeysphere] $primaryuid"
232 if [ "$1" = '-d' ]; then
233 # we're removing the subkey:
234 gpg --export "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname"
235 (cd "$workingdir" && ssh-add -d "$kname")
237 # we're adding the subkey:
238 mkfifo "$workingdir/$kname"
239 gpg --quiet --passphrase-fd 3 3<"$workingdir/passphrase" \
240 --export-options export-reset-subkey-passwd,export-minimal,no-export-attributes \
241 --export-secret-subkeys "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname" &
242 (cd "$workingdir" && DISPLAY=nosuchdisplay SSH_ASKPASS=/bin/false ssh-add "$@" "$kname" </dev/null )&
244 passphrase_prompt "Enter passphrase for key $kname: " "$workingdir/passphrase"
249 rm -f "$workingdir/$kname"
254 # FIXME: sort out the return values: we're just returning the
255 # success or failure of the final authentication subkey in this
256 # case. What if earlier ones failed?
260 ########################################################################
262 ########################################################################
264 # unset variables that should be defined only in config file
266 unset CHECK_KEYSERVER
268 unset HASH_KNOWN_HOSTS
269 unset AUTHORIZED_KEYS
272 [ -r "${ETC}/monkeysphere.conf" ] && . "${ETC}/monkeysphere.conf"
274 # set monkeysphere home directory
275 MONKEYSPHERE_HOME=${MONKEYSPHERE_HOME:="${HOME}/.config/monkeysphere"}
276 mkdir -p -m 0700 "$MONKEYSPHERE_HOME"
279 [ -e ${MONKEYSPHERE_CONFIG:="${MONKEYSPHERE_HOME}/monkeysphere.conf"} ] && . "$MONKEYSPHERE_CONFIG"
281 # set empty config variables with ones from the environment, or from
282 # config file, or with defaults
283 GNUPGHOME=${MONKEYSPHERE_GNUPGHOME:=${GNUPGHOME:="${HOME}/.gnupg"}}
284 KEYSERVER=${MONKEYSPHERE_KEYSERVER:="$KEYSERVER"}
285 # if keyserver not specified in env or monkeysphere.conf,
287 if [ -z "$KEYSERVER" ] ; then
288 if [ -f "${GNUPGHOME}/gpg.conf" ] ; then
289 KEYSERVER=$(grep -e "^[[:space:]]*keyserver " "${GNUPGHOME}/gpg.conf" | tail -1 | awk '{ print $2 }')
292 # if it's still not specified, use the default
293 KEYSERVER=${KEYSERVER:="subkeys.pgp.net"}
294 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}}
295 KNOWN_HOSTS=${MONKEYSPHERE_KNOWN_HOSTS:=${KNOWN_HOSTS:="${HOME}/.ssh/known_hosts"}}
296 HASH_KNOWN_HOSTS=${MONKEYSPHERE_HASH_KNOWN_HOSTS:=${HASH_KNOWN_HOSTS:="true"}}
297 AUTHORIZED_KEYS=${MONKEYSPHERE_AUTHORIZED_KEYS:=${AUTHORIZED_KEYS:="${HOME}/.ssh/authorized_keys"}}
299 # other variables not in config file
300 AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:="${MONKEYSPHERE_HOME}/authorized_user_ids"}
301 REQUIRED_HOST_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_HOST_KEY_CAPABILITY:="a"}
302 REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
304 # export GNUPGHOME and make sure gpg home exists with proper
307 mkdir -p -m 0700 "$GNUPGHOME"
311 [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
315 'update-known_hosts'|'update-known-hosts'|'k')
318 # check permissions on the known_hosts file path
319 if ! check_key_file_permissions "$USER" "$KNOWN_HOSTS" ; then
320 failure "Improper permissions on known_hosts file path."
323 # if hosts are specified on the command line, process just
326 update_known_hosts "$@"
329 # otherwise, if no hosts are specified, process every host
330 # in the user's known_hosts file
332 # exit if the known_hosts file does not exist
333 if [ ! -e "$KNOWN_HOSTS" ] ; then
334 log "known_hosts file '$KNOWN_HOSTS' does not exist."
343 'update-authorized_keys'|'update-authorized-keys'|'a')
344 MODE='authorized_keys'
346 # check permissions on the authorized_user_ids file path
347 if ! check_key_file_permissions "$USER" "$AUTHORIZED_USER_IDS" ; then
348 failure "Improper permissions on authorized_user_ids file path."
351 # check permissions on the authorized_keys file path
352 if ! check_key_file_permissions "$USER" "$AUTHORIZED_KEYS" ; then
353 failure "Improper permissions on authorized_keys file path."
356 # exit if the authorized_user_ids file is empty
357 if [ ! -e "$AUTHORIZED_USER_IDS" ] ; then
358 log "authorized_user_ids file '$AUTHORIZED_USER_IDS' does not exist."
362 # process authorized_user_ids file
363 process_authorized_user_ids "$AUTHORIZED_USER_IDS"
371 'subkey-to-ssh-agent'|'s')
372 subkey_to_ssh_agent "$@"
375 '--help'|'help'|'-h'|'h'|'?')
380 failure "Unknown command: '$COMMAND'
381 Type '$PGRM help' for usage."