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
77 if [ "$(echo "$1" | cut -c 1)" = '-' ] ; then
78 failure "Unknown option '$1'.
79 Type '$PGRM help' for usage."
87 # find all secret keys
88 keyID=$(gpg --with-colons --list-secret-keys | grep ^sec | cut -f5 -d: | sort -u)
89 # if multiple sec keys exist, fail
90 if (( $(echo "$keyID" | wc -l) > 1 )) ; then
91 echo "Multiple secret keys found:"
93 failure "Please specify which primary key to use."
98 if [ -z "$keyID" ] ; then
99 failure "You have no secret key available. You should create an OpenPGP
100 key before joining the monkeysphere. You can do this with:
104 # get key output, and fail if not found
105 gpgOut=$(gpg --quiet --fixed-list-mode --list-secret-keys --with-colons \
108 # fail if multiple sec lines are returned, which means the id
109 # given is not unique
110 if [ $(echo "$gpgOut" | grep -c '^sec:') -gt '1' ] ; then
111 failure "Key ID '$keyID' is not unique."
114 # prompt if an authentication subkey already exists
115 if echo "$gpgOut" | egrep "^(sec|ssb):" | cut -d: -f 12 | grep -q a ; then
116 echo "An authentication subkey already exists for key '$keyID'."
117 read -p "Are you sure you would like to generate another one? (y/N) " OK; OK=${OK:N}
118 if [ "${OK/y/Y}" != 'Y' ] ; then
123 # set subkey defaults
124 # prompt about key expiration if not specified
125 keyExpire=$(get_gpg_expiration "$keyExpire")
127 # generate the list of commands that will be passed to edit-key
128 editCommands=$(cat <<EOF
141 log verbose "generating subkey..."
142 fifoDir=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
143 (umask 077 && mkfifo "$fifoDir/pass")
144 echo "$editCommands" | gpg --passphrase-fd 3 3< "$fifoDir/pass" --expert --command-fd 0 --edit-key "$keyID" &
146 passphrase_prompt "Please enter your passphrase for $keyID: " "$fifoDir/pass"
153 subkey_to_ssh_agent() {
154 # try to add all authentication subkeys to the agent:
165 if ! test_gnu_dummy_s2k_extension ; then
166 failure "Your version of GnuTLS does not seem capable of using with gpg's exported subkeys.
167 You may want to consider patching or upgrading to GnuTLS 2.6 or later.
169 For more details, see:
170 http://lists.gnu.org/archive/html/gnutls-devel/2008-08/msg00005.html"
173 # if there's no agent running, don't bother:
174 if [ -z "$SSH_AUTH_SOCK" ] || ! which ssh-add >/dev/null ; then
175 failure "No ssh-agent available."
178 # and if it looks like it's running, but we can't actually talk to
180 ssh-add -l >/dev/null
182 if [ "$sshaddresponse" = "2" ]; then
183 failure "Could not connect to ssh-agent"
186 # get list of secret keys (to work around https://bugs.g10code.com/gnupg/issue945):
187 secretkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode --fingerprint | \
188 grep '^fpr:' | cut -f10 -d: | awk '{ print "0x" $1 "!" }')
190 if [ -z "$secretkeys" ]; then
191 failure "You have no secret keys in your keyring!
192 You might want to run 'gpg --gen-key'."
195 authsubkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode \
196 --fingerprint --fingerprint $secretkeys | \
197 cut -f1,5,10,12 -d: | grep -A1 '^ssb:[^:]*::[^:]*a[^:]*$' | \
198 grep '^fpr::' | cut -f3 -d: | sort -u)
200 if [ -z "$authsubkeys" ]; then
201 failure "no authentication-capable subkeys available.
202 You might want to 'monkeysphere gen-subkey'"
205 workingdir=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
207 mkfifo "$workingdir/passphrase"
210 # FIXME: we're currently allowing any other options to get passed
211 # through to ssh-add. should we limit it to known ones? For
212 # example: -d or -c and/or -t <lifetime>
214 for subkey in $authsubkeys; do
215 # choose a label by which this key will be known in the agent:
216 # we are labelling the key by User ID instead of by
217 # fingerprint, but filtering out all / characters to make sure
218 # the filename is legit.
220 primaryuid=$(gpg --with-colons --list-key "0x${subkey}!" | grep '^pub:' | cut -f10 -d: | tr -d /)
222 #kname="[monkeysphere] $primaryuid"
225 if [ "$1" = '-d' ]; then
226 # we're removing the subkey:
227 gpg --export "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname"
228 (cd "$workingdir" && ssh-add -d "$kname")
230 # we're adding the subkey:
231 mkfifo "$workingdir/$kname"
232 gpg --quiet --passphrase-fd 3 3<"$workingdir/passphrase" \
233 --export-options export-reset-subkey-passwd,export-minimal,no-export-attributes \
234 --export-secret-subkeys "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname" &
235 (cd "$workingdir" && DISPLAY=nosuchdisplay SSH_ASKPASS=/bin/false ssh-add "$@" "$kname" </dev/null )&
237 passphrase_prompt "Enter passphrase for key $kname: " "$workingdir/passphrase"
242 rm -f "$workingdir/$kname"
247 # FIXME: sort out the return values: we're just returning the
248 # success or failure of the final authentication subkey in this
249 # case. What if earlier ones failed?
253 ########################################################################
255 ########################################################################
257 # unset variables that should be defined only in config file
259 unset CHECK_KEYSERVER
261 unset HASH_KNOWN_HOSTS
262 unset AUTHORIZED_KEYS
265 [ -r "${SYSCONFIGDIR}/monkeysphere.conf" ] && . "${SYSCONFIGDIR}/monkeysphere.conf"
267 # set monkeysphere home directory
268 MONKEYSPHERE_HOME=${MONKEYSPHERE_HOME:="${HOME}/.monkeysphere"}
269 mkdir -p -m 0700 "$MONKEYSPHERE_HOME"
272 [ -e ${MONKEYSPHERE_CONFIG:="${MONKEYSPHERE_HOME}/monkeysphere.conf"} ] && . "$MONKEYSPHERE_CONFIG"
274 # set empty config variables with ones from the environment, or from
275 # config file, or with defaults
276 LOG_LEVEL=${MONKEYSPHERE_LOG_LEVEL:=${LOG_LEVEL:="INFO"}}
277 GNUPGHOME=${MONKEYSPHERE_GNUPGHOME:=${GNUPGHOME:="${HOME}/.gnupg"}}
278 KEYSERVER=${MONKEYSPHERE_KEYSERVER:="$KEYSERVER"}
279 # if keyserver not specified in env or monkeysphere.conf,
281 if [ -z "$KEYSERVER" ] ; then
282 if [ -f "${GNUPGHOME}/gpg.conf" ] ; then
283 KEYSERVER=$(grep -e "^[[:space:]]*keyserver " "${GNUPGHOME}/gpg.conf" | tail -1 | awk '{ print $2 }')
286 # if it's still not specified, use the default
287 KEYSERVER=${KEYSERVER:="subkeys.pgp.net"}
288 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}}
289 KNOWN_HOSTS=${MONKEYSPHERE_KNOWN_HOSTS:=${KNOWN_HOSTS:="${HOME}/.ssh/known_hosts"}}
290 HASH_KNOWN_HOSTS=${MONKEYSPHERE_HASH_KNOWN_HOSTS:=${HASH_KNOWN_HOSTS:="true"}}
291 AUTHORIZED_KEYS=${MONKEYSPHERE_AUTHORIZED_KEYS:=${AUTHORIZED_KEYS:="${HOME}/.ssh/authorized_keys"}}
293 # other variables not in config file
294 AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:="${MONKEYSPHERE_HOME}/authorized_user_ids"}
295 REQUIRED_HOST_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_HOST_KEY_CAPABILITY:="a"}
296 REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
298 # export GNUPGHOME and make sure gpg home exists with proper
301 mkdir -p -m 0700 "$GNUPGHOME"
306 [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
310 'update-known_hosts'|'update-known-hosts'|'k')
313 # touch the known_hosts file so that the file permission check
314 # below won't fail upon not finding the file
315 (umask 0022 && touch "$KNOWN_HOSTS")
317 # check permissions on the known_hosts file path
318 check_key_file_permissions "$USER" "$KNOWN_HOSTS" || failure
320 # if hosts are specified on the command line, process just
323 update_known_hosts "$@"
326 # otherwise, if no hosts are specified, process every host
327 # in the user's known_hosts file
329 # exit if the known_hosts file does not exist
330 if [ ! -e "$KNOWN_HOSTS" ] ; then
331 log error "known_hosts file '$KNOWN_HOSTS' does not exist."
340 'update-authorized_keys'|'update-authorized-keys'|'a')
341 MODE='authorized_keys'
343 # check permissions on the authorized_user_ids file path
344 check_key_file_permissions "$USER" "$AUTHORIZED_USER_IDS" || failure
346 # check permissions on the authorized_keys file path
347 check_key_file_permissions "$USER" "$AUTHORIZED_KEYS" || failure
349 # exit if the authorized_user_ids file is empty
350 if [ ! -e "$AUTHORIZED_USER_IDS" ] ; then
351 log error "authorized_user_ids file '$AUTHORIZED_USER_IDS' does not exist."
355 # process authorized_user_ids file
356 process_authorized_user_ids "$AUTHORIZED_USER_IDS"
364 'subkey-to-ssh-agent'|'s')
365 subkey_to_ssh_agent "$@"
368 '--help'|'help'|'-h'|'h'|'?')
373 failure "Unknown command: '$COMMAND'
374 Type '$PGRM help' for usage."