3 # monkeysphere: MonkeySphere client tool
5 # The monkeysphere scripts are written by:
6 # Jameson Rollins <jrollins@fifthhorseman.net>
7 # Jamie McClelland <jm@mayfirst.org>
8 # Daniel Kahn Gillmor <dkg@fifthhorseman.net>
10 # They are Copyright 2008, and are all released under the GPL, version 3
13 ########################################################################
16 SYSSHAREDIR=${MONKEYSPHERE_SYSSHAREDIR:-"/usr/share/monkeysphere"}
18 . "${SYSSHAREDIR}/common" || exit 1
20 # UTC date in ISO 8601 format if needed
21 DATE=$(date -u '+%FT%T')
23 # unset some environment variables that could screw things up
29 # set the file creation mask to be only owner rw
32 ########################################################################
34 ########################################################################
38 usage: $PGRM <subcommand> [options] [args]
39 Monkeysphere client tool.
42 update-known_hosts (k) [HOST]... update known_hosts file
43 update-authorized_keys (a) update authorized_keys file
44 gen-subkey (g) [KEYID] generate an authentication subkey
45 --length (-l) BITS key length in bits (2048)
46 --expire (-e) EXPIRE date to expire
47 subkey-to-ssh-agent (s) store authentication subkey in ssh-agent
53 # generate a subkey with the 'a' usage flags set
61 # set default key parameter values
66 TEMP=$(PATH="/usr/local/bin:$PATH" getopt -o l:e: -l length:,expire: -n "$PGRM" -- "$@") || failure "getopt failed! Does your getopt support GNU-style long options?"
72 # Note the quotes around `$TEMP': they are essential!
95 # find all secret keys
96 keyID=$(gpg --with-colons --list-secret-keys | grep ^sec | cut -f5 -d: | sort -u)
97 # if multiple sec keys exist, fail
98 if (( $(echo "$keyID" | wc -l) > 1 )) ; then
99 echo "Multiple secret keys found:"
101 failure "Please specify which primary key to use."
106 if [ -z "$keyID" ] ; then
107 failure "You have no secret key available. You should create an OpenPGP
108 key before joining the monkeysphere. You can do this with:
112 # get key output, and fail if not found
113 gpgOut=$(gpg --quiet --fixed-list-mode --list-secret-keys --with-colons \
116 # fail if multiple sec lines are returned, which means the id
117 # given is not unique
118 if [ $(echo "$gpgOut" | grep -c '^sec:') -gt '1' ] ; then
119 failure "Key ID '$keyID' is not unique."
122 # prompt if an authentication subkey already exists
123 if echo "$gpgOut" | egrep "^(sec|ssb):" | cut -d: -f 12 | grep -q a ; then
124 echo "An authentication subkey already exists for key '$keyID'."
125 read -p "Are you sure you would like to generate another one? (y/N) " OK; OK=${OK:N}
126 if [ "${OK/y/Y}" != 'Y' ] ; then
131 # set subkey defaults
132 # prompt about key expiration if not specified
133 keyExpire=$(get_gpg_expiration "$keyExpire")
135 # generate the list of commands that will be passed to edit-key
136 editCommands=$(cat <<EOF
149 log verbose "generating subkey..."
150 fifoDir=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
151 (umask 077 && mkfifo "$fifoDir/pass")
152 echo "$editCommands" | gpg --passphrase-fd 3 3< "$fifoDir/pass" --expert --command-fd 0 --edit-key "$keyID" &
154 passphrase_prompt "Please enter your passphrase for $keyID: " "$fifoDir/pass"
161 function subkey_to_ssh_agent() {
162 # try to add all authentication subkeys to the agent:
173 if ! test_gnu_dummy_s2k_extension ; then
174 failure "Your version of GnuTLS does not seem capable of using with gpg's exported subkeys.
175 You may want to consider patching or upgrading to GnuTLS 2.6 or later.
177 For more details, see:
178 http://lists.gnu.org/archive/html/gnutls-devel/2008-08/msg00005.html"
181 # if there's no agent running, don't bother:
182 if [ -z "$SSH_AUTH_SOCK" ] || ! which ssh-add >/dev/null ; then
183 failure "No ssh-agent available."
186 # and if it looks like it's running, but we can't actually talk to
188 ssh-add -l >/dev/null
190 if [ "$sshaddresponse" = "2" ]; then
191 failure "Could not connect to ssh-agent"
194 # get list of secret keys (to work around https://bugs.g10code.com/gnupg/issue945):
195 secretkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode --fingerprint | \
196 grep '^fpr:' | cut -f10 -d: | awk '{ print "0x" $1 "!" }')
198 if [ -z "$secretkeys" ]; then
199 failure "You have no secret keys in your keyring!
200 You might want to run 'gpg --gen-key'."
203 authsubkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode \
204 --fingerprint --fingerprint $secretkeys | \
205 cut -f1,5,10,12 -d: | grep -A1 '^ssb:[^:]*::[^:]*a[^:]*$' | \
206 grep '^fpr::' | cut -f3 -d: | sort -u)
208 if [ -z "$authsubkeys" ]; then
209 failure "no authentication-capable subkeys available.
210 You might want to 'monkeysphere gen-subkey'"
213 workingdir=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
215 mkfifo "$workingdir/passphrase"
218 # FIXME: we're currently allowing any other options to get passed
219 # through to ssh-add. should we limit it to known ones? For
220 # example: -d or -c and/or -t <lifetime>
222 for subkey in $authsubkeys; do
223 # choose a label by which this key will be known in the agent:
224 # we are labelling the key by User ID instead of by
225 # fingerprint, but filtering out all / characters to make sure
226 # the filename is legit.
228 primaryuid=$(gpg --with-colons --list-key "0x${subkey}!" | grep '^pub:' | cut -f10 -d: | tr -d /)
230 #kname="[monkeysphere] $primaryuid"
233 if [ "$1" = '-d' ]; then
234 # we're removing the subkey:
235 gpg --export "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname"
236 (cd "$workingdir" && ssh-add -d "$kname")
238 # we're adding the subkey:
239 mkfifo "$workingdir/$kname"
240 gpg --quiet --passphrase-fd 3 3<"$workingdir/passphrase" \
241 --export-options export-reset-subkey-passwd,export-minimal,no-export-attributes \
242 --export-secret-subkeys "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname" &
243 (cd "$workingdir" && DISPLAY=nosuchdisplay SSH_ASKPASS=/bin/false ssh-add "$@" "$kname" </dev/null )&
245 passphrase_prompt "Enter passphrase for key $kname: " "$workingdir/passphrase"
250 rm -f "$workingdir/$kname"
255 # FIXME: sort out the return values: we're just returning the
256 # success or failure of the final authentication subkey in this
257 # case. What if earlier ones failed?
261 ########################################################################
263 ########################################################################
265 # unset variables that should be defined only in config file
267 unset CHECK_KEYSERVER
269 unset HASH_KNOWN_HOSTS
270 unset AUTHORIZED_KEYS
273 [ -r "${SYSCONFIGDIR}/monkeysphere.conf" ] && . "${SYSCONFIGDIR}/monkeysphere.conf"
275 # set monkeysphere home directory
276 MONKEYSPHERE_HOME=${MONKEYSPHERE_HOME:="${HOME}/.monkeysphere"}
277 mkdir -p -m 0700 "$MONKEYSPHERE_HOME"
280 [ -e ${MONKEYSPHERE_CONFIG:="${MONKEYSPHERE_HOME}/monkeysphere.conf"} ] && . "$MONKEYSPHERE_CONFIG"
282 # set empty config variables with ones from the environment, or from
283 # config file, or with defaults
284 LOG_LEVEL=${MONKEYSPHERE_LOG_LEVEL:=${LOG_LEVEL:="INFO"}}
285 GNUPGHOME=${MONKEYSPHERE_GNUPGHOME:=${GNUPGHOME:="${HOME}/.gnupg"}}
286 KEYSERVER=${MONKEYSPHERE_KEYSERVER:="$KEYSERVER"}
287 # if keyserver not specified in env or monkeysphere.conf,
289 if [ -z "$KEYSERVER" ] ; then
290 if [ -f "${GNUPGHOME}/gpg.conf" ] ; then
291 KEYSERVER=$(grep -e "^[[:space:]]*keyserver " "${GNUPGHOME}/gpg.conf" | tail -1 | awk '{ print $2 }')
294 # if it's still not specified, use the default
295 KEYSERVER=${KEYSERVER:="subkeys.pgp.net"}
296 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}}
297 KNOWN_HOSTS=${MONKEYSPHERE_KNOWN_HOSTS:=${KNOWN_HOSTS:="${HOME}/.ssh/known_hosts"}}
298 HASH_KNOWN_HOSTS=${MONKEYSPHERE_HASH_KNOWN_HOSTS:=${HASH_KNOWN_HOSTS:="true"}}
299 AUTHORIZED_KEYS=${MONKEYSPHERE_AUTHORIZED_KEYS:=${AUTHORIZED_KEYS:="${HOME}/.ssh/authorized_keys"}}
301 # other variables not in config file
302 AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:="${MONKEYSPHERE_HOME}/authorized_user_ids"}
303 REQUIRED_HOST_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_HOST_KEY_CAPABILITY:="a"}
304 REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
306 # export GNUPGHOME and make sure gpg home exists with proper
309 mkdir -p -m 0700 "$GNUPGHOME"
314 [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
318 'update-known_hosts'|'update-known-hosts'|'k')
321 # touch the known_hosts file so that the file permission check
322 # below won't fail upon not finding the file
323 (umask 0022 && touch "$KNOWN_HOSTS")
325 # check permissions on the known_hosts file path
326 check_key_file_permissions "$USER" "$KNOWN_HOSTS" || failure
328 # if hosts are specified on the command line, process just
331 update_known_hosts "$@"
334 # otherwise, if no hosts are specified, process every host
335 # in the user's known_hosts file
337 # exit if the known_hosts file does not exist
338 if [ ! -e "$KNOWN_HOSTS" ] ; then
339 log error "known_hosts file '$KNOWN_HOSTS' does not exist."
348 'update-authorized_keys'|'update-authorized-keys'|'a')
349 MODE='authorized_keys'
351 # check permissions on the authorized_user_ids file path
352 check_key_file_permissions "$USER" "$AUTHORIZED_USER_IDS" || failure
354 # check permissions on the authorized_keys file path
355 check_key_file_permissions "$USER" "$AUTHORIZED_KEYS" || failure
357 # exit if the authorized_user_ids file is empty
358 if [ ! -e "$AUTHORIZED_USER_IDS" ] ; then
359 log error "authorized_user_ids file '$AUTHORIZED_USER_IDS' does not exist."
363 # process authorized_user_ids file
364 process_authorized_user_ids "$AUTHORIZED_USER_IDS"
372 'subkey-to-ssh-agent'|'s')
373 subkey_to_ssh_agent "$@"
376 '--help'|'help'|'-h'|'h'|'?')
381 failure "Unknown command: '$COMMAND'
382 Type '$PGRM help' for usage."