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 keyExpire=$(get_gpg_expiration "$keyExpire")
133 # generate the list of commands that will be passed to edit-key
134 editCommands=$(cat <<EOF
147 log "generating subkey..."
149 (umask 077 && mkfifo "$fifoDir/pass")
150 echo "$editCommands" | gpg --passphrase-fd 3 3< "$fifoDir/pass" --expert --command-fd 0 --edit-key "$keyID" &
152 passphrase_prompt "Please enter your passphrase for $keyID: " "$fifoDir/pass"
159 function subkey_to_ssh_agent() {
160 # try to add all authentication subkeys to the agent:
171 if ! test_gnu_dummy_s2k_extension ; then
172 failure "Your version of GnuTLS does not seem capable of using with gpg's exported subkeys.
173 You may want to consider patching or upgrading.
175 For more details, see:
176 http://lists.gnu.org/archive/html/gnutls-devel/2008-08/msg00005.html"
179 # if there's no agent running, don't bother:
180 if [ -z "$SSH_AUTH_SOCK" ] || ! which ssh-add >/dev/null ; then
181 failure "No ssh-agent available."
184 # and if it looks like it's running, but we can't actually talk to
186 ssh-add -l >/dev/null
188 if [ "$sshaddresponse" = "2" ]; then
189 failure "Could not connect to ssh-agent"
192 # get list of secret keys (to work around https://bugs.g10code.com/gnupg/issue945):
193 secretkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode --fingerprint | \
194 grep '^fpr:' | cut -f10 -d: | awk '{ print "0x" $1 "!" }')
196 if [ -z "$secretkeys" ]; then
197 failure "You have no secret keys in your keyring!
198 You might want to run 'gpg --gen-key'."
201 authsubkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode \
202 --fingerprint --fingerprint $secretkeys | \
203 cut -f1,5,10,12 -d: | grep -A1 '^ssb:[^:]*::[^:]*a[^:]*$' | \
204 grep '^fpr::' | cut -f3 -d: | sort -u)
206 if [ -z "$authsubkeys" ]; then
207 failure "no authentication-capable subkeys available.
208 You might want to 'monkeysphere gen-subkey'"
211 workingdir=$(mktemp -d)
213 mkfifo "$workingdir/passphrase"
216 # FIXME: we're currently allowing any other options to get passed
217 # through to ssh-add. should we limit it to known ones? For
218 # example: -d or -c and/or -t <lifetime>
220 for subkey in $authsubkeys; do
221 # choose a label by which this key will be known in the agent:
222 # we are labelling the key by User ID instead of by
223 # fingerprint, but filtering out all / characters to make sure
224 # the filename is legit.
226 primaryuid=$(gpg --with-colons --list-key "0x${subkey}!" | grep '^pub:' | cut -f10 -d: | tr -d /)
228 #kname="[monkeysphere] $primaryuid"
231 if [ "$1" = '-d' ]; then
232 # we're removing the subkey:
233 gpg --export "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname"
234 (cd "$workingdir" && ssh-add -d "$kname")
236 # we're adding the subkey:
237 mkfifo "$workingdir/$kname"
238 gpg --quiet --passphrase-fd 3 3<"$workingdir/passphrase" \
239 --export-options export-reset-subkey-passwd,export-minimal,no-export-attributes \
240 --export-secret-subkeys "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname" &
241 (cd "$workingdir" && DISPLAY=nosuchdisplay SSH_ASKPASS=/bin/false ssh-add "$@" "$kname" </dev/null )&
243 passphrase_prompt "Enter passphrase for key $kname: " "$workingdir/passphrase"
248 rm -f "$workingdir/$kname"
253 # FIXME: sort out the return values: we're just returning the
254 # success or failure of the final authentication subkey in this
255 # case. What if earlier ones failed?
259 ########################################################################
261 ########################################################################
263 # unset variables that should be defined only in config file
265 unset CHECK_KEYSERVER
267 unset HASH_KNOWN_HOSTS
268 unset AUTHORIZED_KEYS
271 [ -r "${ETC}/monkeysphere.conf" ] && . "${ETC}/monkeysphere.conf"
273 # set monkeysphere home directory
274 MONKEYSPHERE_HOME=${MONKEYSPHERE_HOME:="${HOME}/.config/monkeysphere"}
275 mkdir -p -m 0700 "$MONKEYSPHERE_HOME"
278 [ -e ${MONKEYSPHERE_CONFIG:="${MONKEYSPHERE_HOME}/monkeysphere.conf"} ] && . "$MONKEYSPHERE_CONFIG"
280 # set empty config variables with ones from the environment, or from
281 # config file, or with defaults
282 GNUPGHOME=${MONKEYSPHERE_GNUPGHOME:=${GNUPGHOME:="${HOME}/.gnupg"}}
283 KEYSERVER=${MONKEYSPHERE_KEYSERVER:="$KEYSERVER"}
284 # if keyserver not specified in env or monkeysphere.conf,
286 if [ -z "$KEYSERVER" ] ; then
287 if [ -f "${GNUPGHOME}/gpg.conf" ] ; then
288 KEYSERVER=$(grep -e "^[[:space:]]*keyserver " "${GNUPGHOME}/gpg.conf" | tail -1 | awk '{ print $2 }')
291 # if it's still not specified, use the default
292 KEYSERVER=${KEYSERVER:="subkeys.pgp.net"}
293 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}}
294 KNOWN_HOSTS=${MONKEYSPHERE_KNOWN_HOSTS:=${KNOWN_HOSTS:="${HOME}/.ssh/known_hosts"}}
295 HASH_KNOWN_HOSTS=${MONKEYSPHERE_HASH_KNOWN_HOSTS:=${HASH_KNOWN_HOSTS:="true"}}
296 AUTHORIZED_KEYS=${MONKEYSPHERE_AUTHORIZED_KEYS:=${AUTHORIZED_KEYS:="${HOME}/.ssh/authorized_keys"}}
298 # other variables not in config file
299 AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:="${MONKEYSPHERE_HOME}/authorized_user_ids"}
300 REQUIRED_HOST_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_HOST_KEY_CAPABILITY:="a"}
301 REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
303 # export GNUPGHOME and make sure gpg home exists with proper
306 mkdir -p -m 0700 "$GNUPGHOME"
310 [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
314 'update-known_hosts'|'update-known-hosts'|'k')
317 # check permissions on the known_hosts file path
318 if ! check_key_file_permissions "$USER" "$KNOWN_HOSTS" ; then
319 failure "Improper permissions on known_hosts file path."
322 # if hosts are specified on the command line, process just
325 update_known_hosts "$@"
328 # otherwise, if no hosts are specified, process every host
329 # in the user's known_hosts file
331 # exit if the known_hosts file does not exist
332 if [ ! -e "$KNOWN_HOSTS" ] ; then
333 log "known_hosts file '$KNOWN_HOSTS' does not exist."
342 'update-authorized_keys'|'update-authorized-keys'|'a')
343 MODE='authorized_keys'
345 # check permissions on the authorized_user_ids file path
346 if ! check_key_file_permissions "$USER" "$AUTHORIZED_USER_IDS" ; then
347 failure "Improper permissions on authorized_user_ids file path."
350 # check permissions on the authorized_keys file path
351 if ! check_key_file_permissions "$USER" "$AUTHORIZED_KEYS" ; then
352 failure "Improper permissions on authorized_keys file path."
355 # exit if the authorized_user_ids file is empty
356 if [ ! -e "$AUTHORIZED_USER_IDS" ] ; then
357 log "authorized_user_ids file '$AUTHORIZED_USER_IDS' does not exist."
361 # process authorized_user_ids file
362 process_authorized_user_ids "$AUTHORIZED_USER_IDS"
370 'subkey-to-ssh-agent'|'s')
371 subkey_to_ssh_agent "$@"
374 '--help'|'help'|'-h'|'h'|'?')
379 failure "Unknown command: '$COMMAND'
380 Type '$PGRM help' for usage."