3 # monkeysphere: MonkeySphere client tool
5 # The monkeysphere scripts are written by:
6 # Jameson Rollins <jrollins@fifthhorseman.net>
8 # They are Copyright 2008, and are all released under the GPL, version 3
11 ########################################################################
14 SHARE=${MONKEYSPHERE_SHARE:-"/usr/share/monkeysphere"}
16 . "${SHARE}/common" || exit 1
18 # date in UTF format if needed
19 DATE=$(date -u '+%FT%T')
21 # unset some environment variables that could screw things up
27 # set the file creation mask to be only owner rw
30 ########################################################################
32 ########################################################################
36 usage: $PGRM <subcommand> [options] [args]
37 MonkeySphere client tool.
40 update-known_hosts (k) [HOST]... update known_hosts file
41 update-authorized_keys (a) update authorized_keys file
42 gen-subkey (g) [KEYID] generate an authentication subkey
43 --length (-l) BITS key length in bits (2048)
44 --expire (-e) EXPIRE date to expire
45 subkey-to-ssh-agent (s) store authentication subkey in ssh-agent
51 # generate a subkey with the 'a' usage flags set
59 # set default key parameter values
64 TEMP=$(getopt -o l:e: -l length:,expire: -n "$PGRM" -- "$@")
70 # Note the quotes around `$TEMP': they are essential!
93 # find all secret keys
94 keyID=$(gpg --with-colons --list-secret-keys | grep ^sec | cut -f5 -d:)
95 # if multiple sec keys exist, fail
96 if (( $(echo "$keyID" | wc -l) > 1 )) ; then
97 echo "Multiple secret keys found:"
99 failure "Please specify which primary key to use."
104 if [ -z "$keyID" ] ; then
105 failure "You have no secret key available. You should create an OpenPGP
106 key before joining the monkeysphere. You can do this with:
110 # get key output, and fail if not found
111 gpgOut=$(gpg --quiet --fixed-list-mode --list-secret-keys --with-colons \
114 # fail if multiple sec lines are returned, which means the id
115 # given is not unique
116 if [ $(echo "$gpgOut" | grep '^sec:' | wc -l) -gt '1' ] ; then
117 failure "Key ID '$keyID' is not unique."
120 # prompt if an authentication subkey already exists
121 if echo "$gpgOut" | egrep "^(sec|ssb):" | cut -d: -f 12 | grep -q a ; then
122 echo "An authentication subkey already exists for key '$keyID'."
123 read -p "Are you sure you would like to generate another one? (y/N) " OK; OK=${OK:N}
124 if [ "${OK/y/Y}" != 'Y' ] ; then
129 # set subkey defaults
130 # prompt about key expiration if not specified
131 if [ -z "$keyExpire" ] ; then
133 Please specify how long the key should be valid.
134 0 = key does not expire
135 <n> = key expires in n days
136 <n>w = key expires in n weeks
137 <n>m = key expires in n months
138 <n>y = key expires in n years
140 while [ -z "$keyExpire" ] ; do
141 read -p "Key is valid for? (0) " keyExpire
142 if ! test_gpg_expire ${keyExpire:=0} ; then
147 elif ! test_gpg_expire "$keyExpire" ; then
148 failure "invalid key expiration value '$keyExpire'."
151 # generate the list of commands that will be passed to edit-key
152 editCommands=$(cat <<EOF
165 log "generating subkey..."
167 (umask 077 && mkfifo "$fifoDir/pass")
168 echo "$editCommands" | gpg --passphrase-fd 3 3< "$fifoDir/pass" --expert --command-fd 0 --edit-key "$keyID" &
170 passphrase_prompt "Please enter your passphrase for $keyID: " "$fifoDir/pass"
177 function subkey_to_ssh_agent() {
178 # try to add all authentication subkeys to the agent:
188 if ! test_gnu_dummy_s2k_extension ; then
189 failure "Your version of GnuTLS does not seem capable of using with gpg's exported subkeys.
190 You may want to consider patching or upgrading.
192 For more details, see:
193 http://lists.gnu.org/archive/html/gnutls-devel/2008-08/msg00005.html"
196 # if there's no agent running, don't bother:
197 if [ -z "$SSH_AUTH_SOCK" ] || ! which ssh-add >/dev/null ; then
198 failure "No ssh-agent available."
201 # and if it looks like it's running, but we can't actually talk to
203 ssh-add -l >/dev/null
205 if [ "$sshaddresponse" = "2" ]; then
206 failure "Could not connect to ssh-agent"
209 # get list of secret keys (to work around https://bugs.g10code.com/gnupg/issue945):
210 secretkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode --fingerprint | grep '^fpr:' | cut -f10 -d: | awk '{ print "0x" $1 "!" }')
212 if [ -z "$secretkeys" ]; then
213 failure "You have no secret keys in your keyring!
214 You might want to run 'gpg --gen-key'."
217 authsubkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode --fingerprint --fingerprint $secretkeys | cut -f1,5,10,12 -d: | grep -A1 '^ssb:[^:]*::[^:]*a[^:]*$' | 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)
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"
242 kname="'$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 for $primaryuid: " "$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 "${ETC}/monkeysphere.conf" ] && . "${ETC}/monkeysphere.conf"
286 # set monkeysphere home directory
287 MONKEYSPHERE_HOME=${MONKEYSPHERE_HOME:="${HOME}/.config/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 GNUPGHOME=${MONKEYSPHERE_GNUPGHOME:=${GNUPGHOME:="${HOME}/.gnupg"}}
296 KEYSERVER=${MONKEYSPHERE_KEYSERVER:="$KEYSERVER"}
297 # if keyserver not specified in env or monkeysphere.conf,
299 if [ -z "$KEYSERVER" ] ; then
300 if [ -f "${GNUPGHOME}/gpg.conf" ] ; then
301 KEYSERVER=$(grep -e "^[[:space:]]*keyserver " "${GNUPGHOME}/gpg.conf" | tail -1 | awk '{ print $2 }')
304 # if it's still not specified, use the default
305 KEYSERVER=${KEYSERVER:="subkeys.pgp.net"}
306 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}}
307 KNOWN_HOSTS=${MONKEYSPHERE_KNOWN_HOSTS:=${KNOWN_HOSTS:="${HOME}/.ssh/known_hosts"}}
308 HASH_KNOWN_HOSTS=${MONKEYSPHERE_HASH_KNOWN_HOSTS:=${HASH_KNOWN_HOSTS:="true"}}
309 AUTHORIZED_KEYS=${MONKEYSPHERE_AUTHORIZED_KEYS:=${AUTHORIZED_KEYS:="${HOME}/.ssh/authorized_keys"}}
311 # other variables not in config file
312 AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:="${MONKEYSPHERE_HOME}/authorized_user_ids"}
313 REQUIRED_HOST_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_HOST_KEY_CAPABILITY:="a"}
314 REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
316 # export GNUPGHOME and make sure gpg home exists with proper
319 mkdir -p -m 0700 "$GNUPGHOME"
323 [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
327 'update-known_hosts'|'update-known-hosts'|'k')
330 # check permissions on the known_hosts file path
331 if ! check_key_file_permissions "$USER" "$KNOWN_HOSTS" ; then
332 failure "Improper permissions on known_hosts file path."
335 # if hosts are specified on the command line, process just
338 update_known_hosts "$@"
341 # otherwise, if no hosts are specified, process every host
342 # in the user's known_hosts file
344 # exit if the known_hosts file does not exist
345 if [ ! -e "$KNOWN_HOSTS" ] ; then
346 log "known_hosts file '$KNOWN_HOSTS' does not exist."
355 'update-authorized_keys'|'update-authorized-keys'|'a')
356 MODE='authorized_keys'
358 # check permissions on the authorized_user_ids file path
359 if ! check_key_file_permissions "$USER" "$AUTHORIZED_USER_IDS" ; then
360 failure "Improper permissions on authorized_user_ids file path."
363 # check permissions on the authorized_keys file path
364 if ! check_key_file_permissions "$USER" "$AUTHORIZED_KEYS" ; then
365 failure "Improper permissions on authorized_keys file path."
368 # exit if the authorized_user_ids file is empty
369 if [ ! -e "$AUTHORIZED_USER_IDS" ] ; then
370 log "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 "$@"
387 '--help'|'help'|'-h'|'h'|'?')
392 failure "Unknown command: '$COMMAND'
393 Type '$PGRM help' for usage."