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:
170 if ! test_gnu_dummy_s2k_extension ; then
171 failure "Your version of GnuTLS does not seem capable of using with gpg's exported subkeys.
172 You may want to consider patching or upgrading.
174 For more details, see:
175 http://lists.gnu.org/archive/html/gnutls-devel/2008-08/msg00005.html"
178 # if there's no agent running, don't bother:
179 if [ -z "$SSH_AUTH_SOCK" ] || ! which ssh-add >/dev/null ; then
180 failure "No ssh-agent available."
183 # and if it looks like it's running, but we can't actually talk to
185 ssh-add -l >/dev/null
187 if [ "$sshaddresponse" = "2" ]; then
188 failure "Could not connect to ssh-agent"
191 # get list of secret keys (to work around https://bugs.g10code.com/gnupg/issue945):
192 secretkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode --fingerprint | grep '^fpr:' | cut -f10 -d: | awk '{ print "0x" $1 "!" }')
194 if [ -z "$secretkeys" ]; then
195 failure "You have no secret keys in your keyring!
196 You might want to run 'gpg --gen-key'."
199 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)
201 if [ -z "$authsubkeys" ]; then
202 failure "no authentication-capable subkeys available.
203 You might want to 'monkeysphere gen-subkey'"
206 workingdir=$(mktemp -d)
208 mkfifo "$workingdir/passphrase"
211 # FIXME: we're currently allowing any other options to get passed
212 # through to ssh-add. should we limit it to known ones? For
213 # example: -d or -c and/or -t <lifetime>
215 for subkey in $authsubkeys; do
216 # choose a label by which this key will be known in the agent:
217 # we are labelling the key by User ID instead of by
218 # fingerprint, but filtering out all / characters to make sure
219 # the filename is legit.
221 primaryuid=$(gpg --with-colons --list-key "0x${subkey}!" | grep '^pub:' | cut -f10 -d: | tr -d /)
223 #kname="[monkeysphere] $primaryuid"
224 kname="'$primaryuid'"
226 if [ "$1" = '-d' ]; then
227 # we're removing the subkey:
228 gpg --export "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname"
229 (cd "$workingdir" && ssh-add -d "$kname")
231 # we're adding the subkey:
232 mkfifo "$workingdir/$kname"
233 gpg --quiet --passphrase-fd 3 3<"$workingdir/passphrase" \
234 --export-options export-reset-subkey-passwd,export-minimal,no-export-attributes \
235 --export-secret-subkeys "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname" &
236 (cd "$workingdir" && DISPLAY=nosuchdisplay SSH_ASKPASS=/bin/false ssh-add "$@" "$kname" </dev/null )&
238 passphrase_prompt "Enter passphrase for key for $primaryuid: " "$workingdir/passphrase"
243 rm -f "$workingdir/$kname"
248 # FIXME: sort out the return values: we're just returning the
249 # success or failure of the final authentication subkey in this
250 # case. What if earlier ones failed?
254 ########################################################################
256 ########################################################################
258 # unset variables that should be defined only in config file
260 unset CHECK_KEYSERVER
262 unset HASH_KNOWN_HOSTS
263 unset AUTHORIZED_KEYS
266 [ -r "${ETC}/monkeysphere.conf" ] && . "${ETC}/monkeysphere.conf"
268 # set monkeysphere home directory
269 MONKEYSPHERE_HOME=${MONKEYSPHERE_HOME:="${HOME}/.config/monkeysphere"}
270 mkdir -p -m 0700 "$MONKEYSPHERE_HOME"
273 [ -e ${MONKEYSPHERE_CONFIG:="${MONKEYSPHERE_HOME}/monkeysphere.conf"} ] && . "$MONKEYSPHERE_CONFIG"
275 # set empty config variables with ones from the environment, or from
276 # config file, or with defaults
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"
305 [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
309 'update-known_hosts'|'update-known-hosts'|'k')
312 # check permissions on the known_hosts file path
313 if ! check_key_file_permissions "$USER" "$KNOWN_HOSTS" ; then
314 failure "Improper permissions on known_hosts file path."
317 # if hosts are specified on the command line, process just
320 update_known_hosts "$@"
323 # otherwise, if no hosts are specified, process every host
324 # in the user's known_hosts file
326 # exit if the known_hosts file does not exist
327 if [ ! -e "$KNOWN_HOSTS" ] ; then
328 log "known_hosts file '$KNOWN_HOSTS' does not exist."
337 'update-authorized_keys'|'update-authorized-keys'|'a')
338 MODE='authorized_keys'
340 # check permissions on the authorized_user_ids file path
341 if ! check_key_file_permissions "$USER" "$AUTHORIZED_USER_IDS" ; then
342 failure "Improper permissions on authorized_user_ids file path."
345 # check permissions on the authorized_keys file path
346 if ! check_key_file_permissions "$USER" "$AUTHORIZED_KEYS" ; then
347 failure "Improper permissions on authorized_keys file path."
350 # exit if the authorized_user_ids file is empty
351 if [ ! -e "$AUTHORIZED_USER_IDS" ] ; then
352 log "authorized_user_ids file '$AUTHORIZED_USER_IDS' does not exist."
356 # process authorized_user_ids file
357 process_authorized_user_ids "$AUTHORIZED_USER_IDS"
365 'subkey-to-ssh-agent'|'s')
366 subkey_to_ssh_agent "$@"
369 '--help'|'help'|'-h'|'h'|'?')
374 failure "Unknown command: '$COMMAND'
375 Type '$PGRM help' for usage."