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
48 version (v) show version number
54 # generate a subkey with the 'a' usage flags set
62 # set default key parameter values
78 if [ "$(echo "$1" | cut -c 1)" = '-' ] ; then
79 failure "Unknown option '$1'.
80 Type '$PGRM help' for usage."
89 gpgSecOut=$(gpg --quiet --fixed-list-mode --list-secret-keys --with-colons 2>/dev/null | egrep '^sec:')
92 gpgSecOut=$(gpg --quiet --fixed-list-mode --list-secret-keys --with-colons "$1" | egrep '^sec:') || failure
95 failure "You must specify only a single primary key ID."
99 # check that only a single secret key was found
100 case $(echo "$gpgSecOut" | grep -c '^sec:') in
102 failure "No secret keys found. Create an OpenPGP key with the following command:
106 keyID=$(echo "$gpgSecOut" | cut -d: -f5)
109 echo "Multiple primary secret keys found:"
110 echo "$gpgSecOut" | cut -d: -f5
111 failure "Please specify which primary key to use."
115 # check that a valid authentication key does not already exist
117 for line in $(gpg --quiet --fixed-list-mode --list-keys --with-colons "$keyID") ; do
118 type=$(echo "$line" | cut -d: -f1)
119 validity=$(echo "$line" | cut -d: -f2)
120 usage=$(echo "$line" | cut -d: -f12)
123 if [ "$type" != 'pub' -a "$type" != 'sub' ] ; then
126 # check for authentication capability
127 if ! check_capability "$usage" 'a' ; then
130 # if authentication key is valid, prompt to continue
131 if [ "$validity" = 'u' ] ; then
132 echo "A valid authentication key already exists for primary key '$keyID'."
133 read -p "Are you sure you would like to generate another one? (y/N) " OK; OK=${OK:N}
134 if [ "${OK/y/Y}" != 'Y' ] ; then
141 # set subkey defaults
142 # prompt about key expiration if not specified
143 keyExpire=$(get_gpg_expiration "$keyExpire")
145 # generate the list of commands that will be passed to edit-key
146 editCommands=$(cat <<EOF
159 log verbose "generating subkey..."
160 fifoDir=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
161 (umask 077 && mkfifo "$fifoDir/pass")
162 echo "$editCommands" | gpg --passphrase-fd 3 3< "$fifoDir/pass" --expert --command-fd 0 --edit-key "$keyID" &
164 # FIXME: this needs to fail more gracefully if the passphrase is incorrect
165 passphrase_prompt "Please enter your passphrase for $keyID: " "$fifoDir/pass"
172 subkey_to_ssh_agent() {
173 # try to add all authentication subkeys to the agent:
184 if ! test_gnu_dummy_s2k_extension ; then
185 failure "Your version of GnuTLS does not seem capable of using with gpg's exported subkeys.
186 You may want to consider patching or upgrading to GnuTLS 2.6 or later.
188 For more details, see:
189 http://lists.gnu.org/archive/html/gnutls-devel/2008-08/msg00005.html"
192 # if there's no agent running, don't bother:
193 if [ -z "$SSH_AUTH_SOCK" ] || ! which ssh-add >/dev/null ; then
194 failure "No ssh-agent available."
197 # and if it looks like it's running, but we can't actually talk to
199 ssh-add -l >/dev/null
201 if [ "$sshaddresponse" = "2" ]; then
202 failure "Could not connect to ssh-agent"
205 # get list of secret keys (to work around https://bugs.g10code.com/gnupg/issue945):
206 secretkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode --fingerprint | \
207 grep '^fpr:' | cut -f10 -d: | awk '{ print "0x" $1 "!" }')
209 if [ -z "$secretkeys" ]; then
210 failure "You have no secret keys in your keyring!
211 You might want to run 'gpg --gen-key'."
214 authsubkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode \
215 --fingerprint --fingerprint $secretkeys | \
216 cut -f1,5,10,12 -d: | grep -A1 '^ssb:[^:]*::[^:]*a[^:]*$' | \
217 grep '^fpr::' | cut -f3 -d: | sort -u)
219 if [ -z "$authsubkeys" ]; then
220 failure "no authentication-capable subkeys available.
221 You might want to 'monkeysphere gen-subkey'"
224 workingdir=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
226 mkfifo "$workingdir/passphrase"
229 # FIXME: we're currently allowing any other options to get passed
230 # through to ssh-add. should we limit it to known ones? For
231 # example: -d or -c and/or -t <lifetime>
233 for subkey in $authsubkeys; do
234 # choose a label by which this key will be known in the agent:
235 # we are labelling the key by User ID instead of by
236 # fingerprint, but filtering out all / characters to make sure
237 # the filename is legit.
239 primaryuid=$(gpg --with-colons --list-key "0x${subkey}!" | grep '^pub:' | cut -f10 -d: | tr -d /)
241 #kname="[monkeysphere] $primaryuid"
244 if [ "$1" = '-d' ]; then
245 # we're removing the subkey:
246 gpg --export "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname"
247 (cd "$workingdir" && ssh-add -d "$kname")
249 # we're adding the subkey:
250 mkfifo "$workingdir/$kname"
251 gpg --quiet --passphrase-fd 3 3<"$workingdir/passphrase" \
252 --export-options export-reset-subkey-passwd,export-minimal,no-export-attributes \
253 --export-secret-subkeys "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname" &
254 (cd "$workingdir" && DISPLAY=nosuchdisplay SSH_ASKPASS=/bin/false ssh-add "$@" "$kname" </dev/null )&
256 passphrase_prompt "Enter passphrase for key $kname: " "$workingdir/passphrase"
261 rm -f "$workingdir/$kname"
266 # FIXME: sort out the return values: we're just returning the
267 # success or failure of the final authentication subkey in this
268 # case. What if earlier ones failed?
272 ########################################################################
274 ########################################################################
276 # unset variables that should be defined only in config file
278 unset CHECK_KEYSERVER
280 unset HASH_KNOWN_HOSTS
281 unset AUTHORIZED_KEYS
284 [ -r "${SYSCONFIGDIR}/monkeysphere.conf" ] && . "${SYSCONFIGDIR}/monkeysphere.conf"
286 # set monkeysphere home directory
287 MONKEYSPHERE_HOME=${MONKEYSPHERE_HOME:="${HOME}/.monkeysphere"}
288 mkdir -p -m 0700 "$MONKEYSPHERE_HOME"
291 [ -e ${MONKEYSPHERE_CONFIG:="${MONKEYSPHERE_HOME}/monkeysphere.conf"} ] && . "$MONKEYSPHERE_CONFIG"
293 # set empty config variables with ones from the environment, or from
294 # config file, or with defaults
295 LOG_LEVEL=${MONKEYSPHERE_LOG_LEVEL:=${LOG_LEVEL:="INFO"}}
296 GNUPGHOME=${MONKEYSPHERE_GNUPGHOME:=${GNUPGHOME:="${HOME}/.gnupg"}}
297 KEYSERVER=${MONKEYSPHERE_KEYSERVER:="$KEYSERVER"}
298 # if keyserver not specified in env or monkeysphere.conf,
300 if [ -z "$KEYSERVER" ] ; then
301 if [ -f "${GNUPGHOME}/gpg.conf" ] ; then
302 KEYSERVER=$(grep -e "^[[:space:]]*keyserver " "${GNUPGHOME}/gpg.conf" | tail -1 | awk '{ print $2 }')
305 # if it's still not specified, use the default
306 KEYSERVER=${KEYSERVER:="subkeys.pgp.net"}
307 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}}
308 KNOWN_HOSTS=${MONKEYSPHERE_KNOWN_HOSTS:=${KNOWN_HOSTS:="${HOME}/.ssh/known_hosts"}}
309 HASH_KNOWN_HOSTS=${MONKEYSPHERE_HASH_KNOWN_HOSTS:=${HASH_KNOWN_HOSTS:="true"}}
310 AUTHORIZED_KEYS=${MONKEYSPHERE_AUTHORIZED_KEYS:=${AUTHORIZED_KEYS:="${HOME}/.ssh/authorized_keys"}}
312 # other variables not in config file
313 AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:="${MONKEYSPHERE_HOME}/authorized_user_ids"}
314 REQUIRED_HOST_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_HOST_KEY_CAPABILITY:="a"}
315 REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
317 # export GNUPGHOME and make sure gpg home exists with proper
320 mkdir -p -m 0700 "$GNUPGHOME"
325 [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
329 'update-known_hosts'|'update-known-hosts'|'k')
332 # touch the known_hosts file so that the file permission check
333 # below won't fail upon not finding the file
334 (umask 0022 && touch "$KNOWN_HOSTS")
336 # check permissions on the known_hosts file path
337 check_key_file_permissions "$USER" "$KNOWN_HOSTS" || failure
339 # if hosts are specified on the command line, process just
342 update_known_hosts "$@"
345 # otherwise, if no hosts are specified, process every host
346 # in the user's known_hosts file
348 # exit if the known_hosts file does not exist
349 if [ ! -e "$KNOWN_HOSTS" ] ; then
350 log error "known_hosts file '$KNOWN_HOSTS' does not exist."
359 'update-authorized_keys'|'update-authorized-keys'|'a')
360 MODE='authorized_keys'
362 # check permissions on the authorized_user_ids file path
363 check_key_file_permissions "$USER" "$AUTHORIZED_USER_IDS" || failure
365 # check permissions on the authorized_keys file path
366 check_key_file_permissions "$USER" "$AUTHORIZED_KEYS" || failure
368 # exit if the authorized_user_ids file is empty
369 if [ ! -e "$AUTHORIZED_USER_IDS" ] ; then
370 log error "authorized_user_ids file '$AUTHORIZED_USER_IDS' does not exist."
374 # process authorized_user_ids file
375 process_authorized_user_ids "$AUTHORIZED_USER_IDS"
383 'subkey-to-ssh-agent'|'s')
384 subkey_to_ssh_agent "$@"
391 '--help'|'help'|'-h'|'h'|'?')
396 failure "Unknown command: '$COMMAND'
397 Type '$PGRM help' for usage."