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>
9 # Micah Anderson <micah@riseup.net>
11 # They are Copyright 2008-2009, and are all released under the GPL, version 3
14 ########################################################################
17 SYSSHAREDIR=${MONKEYSPHERE_SYSSHAREDIR:-"/usr/share/monkeysphere"}
19 . "${SYSSHAREDIR}/common" || exit 1
21 # UTC date in ISO 8601 format if needed
22 DATE=$(date -u '+%FT%T')
24 # unset some environment variables that could screw things up
30 # set the file creation mask to be only owner rw
33 ########################################################################
35 ########################################################################
39 usage: $PGRM <subcommand> [options] [args]
40 Monkeysphere client tool.
43 update-known_hosts (k) [HOST]... update known_hosts file
44 update-authorized_keys (a) update authorized_keys file
45 import-subkey (i) import existing ssh key as gpg subkey
46 --keyfile (-f) FILE key file to import
47 --expire (-e) EXPIRE date to expire
48 gen-subkey (g) [KEYID] generate an authentication subkey
49 --length (-l) BITS key length in bits (2048)
50 --expire (-e) EXPIRE date to expire
51 ssh-proxycommand ssh proxycommand
52 subkey-to-ssh-agent (s) store authentication subkey in ssh-agent
53 version (v) show version number
59 # import an existing ssh key as a gpg subkey
61 local keyFile="~/.ssh/id_rsa"
79 if [ "$(echo "$1" | cut -c 1)" = '-' ] ; then
80 failure "Unknown option '$1'.
81 Type '$PGRM help' for usage."
88 log verbose "importing ssh key..."
89 fifoDir=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
90 (umask 077 && mkfifo "$fifoDir/pass")
91 ssh2openpgp | gpg --passphrase-fd 3 3< "$fifoDir/pass" --expert --command-fd 0 --import &
93 passphrase_prompt "Please enter your passphrase for $keyID: " "$fifoDir/pass"
100 # generate a subkey with the 'a' usage flags set
120 if [ "$(echo "$1" | cut -c 1)" = '-' ] ; then
121 failure "Unknown option '$1'.
122 Type '$PGRM help' for usage."
131 gpgSecOut=$(gpg --quiet --fixed-list-mode --list-secret-keys --with-colons 2>/dev/null | egrep '^sec:')
134 gpgSecOut=$(gpg --quiet --fixed-list-mode --list-secret-keys --with-colons "$1" | egrep '^sec:') || failure
137 failure "You must specify only a single primary key ID."
141 # check that only a single secret key was found
142 case $(echo "$gpgSecOut" | grep -c '^sec:') in
144 failure "No secret keys found. Create an OpenPGP key with the following command:
148 keyID=$(echo "$gpgSecOut" | cut -d: -f5)
151 echo "Multiple primary secret keys found:"
152 echo "$gpgSecOut" | cut -d: -f5
153 failure "Please specify which primary key to use."
157 # check that a valid authentication key does not already exist
159 for line in $(gpg --quiet --fixed-list-mode --list-keys --with-colons "$keyID") ; do
160 type=$(echo "$line" | cut -d: -f1)
161 validity=$(echo "$line" | cut -d: -f2)
162 usage=$(echo "$line" | cut -d: -f12)
165 if [ "$type" != 'pub' -a "$type" != 'sub' ] ; then
168 # check for authentication capability
169 if ! check_capability "$usage" 'a' ; then
172 # if authentication key is valid, prompt to continue
173 if [ "$validity" = 'u' ] ; then
174 echo "A valid authentication key already exists for primary key '$keyID'."
175 read -p "Are you sure you would like to generate another one? (y/N) " OK; OK=${OK:N}
176 if [ "${OK/y/Y}" != 'Y' ] ; then
183 # set subkey defaults
184 # prompt about key expiration if not specified
185 keyExpire=$(get_gpg_expiration "$keyExpire")
187 # generate the list of commands that will be passed to edit-key
188 editCommands=$(cat <<EOF
201 log verbose "generating subkey..."
202 fifoDir=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
203 (umask 077 && mkfifo "$fifoDir/pass")
204 echo "$editCommands" | gpg --passphrase-fd 3 3< "$fifoDir/pass" --expert --command-fd 0 --edit-key "$keyID" &
206 # FIXME: this needs to fail more gracefully if the passphrase is incorrect
207 passphrase_prompt "Please enter your passphrase for $keyID: " "$fifoDir/pass"
214 subkey_to_ssh_agent() {
215 # try to add all authentication subkeys to the agent:
226 if ! test_gnu_dummy_s2k_extension ; then
227 failure "Your version of GnuTLS does not seem capable of using with gpg's exported subkeys.
228 You may want to consider patching or upgrading to GnuTLS 2.6 or later.
230 For more details, see:
231 http://lists.gnu.org/archive/html/gnutls-devel/2008-08/msg00005.html"
234 # if there's no agent running, don't bother:
235 if [ -z "$SSH_AUTH_SOCK" ] || ! which ssh-add >/dev/null ; then
236 failure "No ssh-agent available."
239 # and if it looks like it's running, but we can't actually talk to
241 ssh-add -l >/dev/null
243 if [ "$sshaddresponse" = "2" ]; then
244 failure "Could not connect to ssh-agent"
247 # get list of secret keys (to work around https://bugs.g10code.com/gnupg/issue945):
248 secretkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode --fingerprint | \
249 grep '^fpr:' | cut -f10 -d: | awk '{ print "0x" $1 "!" }')
251 if [ -z "$secretkeys" ]; then
252 failure "You have no secret keys in your keyring!
253 You might want to run 'gpg --gen-key'."
256 authsubkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode \
257 --fingerprint --fingerprint $secretkeys | \
258 cut -f1,5,10,12 -d: | grep -A1 '^ssb:[^:]*::[^:]*a[^:]*$' | \
259 grep '^fpr::' | cut -f3 -d: | sort -u)
261 if [ -z "$authsubkeys" ]; then
262 failure "no authentication-capable subkeys available.
263 You might want to 'monkeysphere gen-subkey'"
266 workingdir=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
268 mkfifo "$workingdir/passphrase"
271 # FIXME: we're currently allowing any other options to get passed
272 # through to ssh-add. should we limit it to known ones? For
273 # example: -d or -c and/or -t <lifetime>
275 for subkey in $authsubkeys; do
276 # choose a label by which this key will be known in the agent:
277 # we are labelling the key by User ID instead of by
278 # fingerprint, but filtering out all / characters to make sure
279 # the filename is legit.
281 primaryuid=$(gpg --with-colons --list-key "0x${subkey}!" | grep '^pub:' | cut -f10 -d: | tr -d /)
283 #kname="[monkeysphere] $primaryuid"
286 if [ "$1" = '-d' ]; then
287 # we're removing the subkey:
288 gpg --export "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname"
289 (cd "$workingdir" && ssh-add -d "$kname")
291 # we're adding the subkey:
292 mkfifo "$workingdir/$kname"
293 gpg --quiet --passphrase-fd 3 3<"$workingdir/passphrase" \
294 --export-options export-reset-subkey-passwd,export-minimal,no-export-attributes \
295 --export-secret-subkeys "0x${subkey}!" | openpgp2ssh "$subkey" > "$workingdir/$kname" &
296 (cd "$workingdir" && DISPLAY=nosuchdisplay SSH_ASKPASS=/bin/false ssh-add "$@" "$kname" </dev/null )&
298 passphrase_prompt "Enter passphrase for key $kname: " "$workingdir/passphrase"
303 rm -f "$workingdir/$kname"
308 # FIXME: sort out the return values: we're just returning the
309 # success or failure of the final authentication subkey in this
310 # case. What if earlier ones failed?
314 ########################################################################
316 ########################################################################
318 # unset variables that should be defined only in config file
320 unset CHECK_KEYSERVER
322 unset HASH_KNOWN_HOSTS
323 unset AUTHORIZED_KEYS
326 [ -r "${SYSCONFIGDIR}/monkeysphere.conf" ] && . "${SYSCONFIGDIR}/monkeysphere.conf"
328 # set monkeysphere home directory
329 MONKEYSPHERE_HOME=${MONKEYSPHERE_HOME:="${HOME}/.monkeysphere"}
330 mkdir -p -m 0700 "$MONKEYSPHERE_HOME"
333 [ -e ${MONKEYSPHERE_CONFIG:="${MONKEYSPHERE_HOME}/monkeysphere.conf"} ] && . "$MONKEYSPHERE_CONFIG"
335 # set empty config variables with ones from the environment, or from
336 # config file, or with defaults
337 LOG_LEVEL=${MONKEYSPHERE_LOG_LEVEL:=${LOG_LEVEL:="INFO"}}
338 GNUPGHOME=${MONKEYSPHERE_GNUPGHOME:=${GNUPGHOME:="${HOME}/.gnupg"}}
339 KEYSERVER=${MONKEYSPHERE_KEYSERVER:="$KEYSERVER"}
340 # if keyserver not specified in env or monkeysphere.conf,
342 if [ -z "$KEYSERVER" ] ; then
343 if [ -f "${GNUPGHOME}/gpg.conf" ] ; then
344 KEYSERVER=$(grep -e "^[[:space:]]*keyserver " "${GNUPGHOME}/gpg.conf" | tail -1 | awk '{ print $2 }')
347 # if it's still not specified, use the default
348 KEYSERVER=${KEYSERVER:="subkeys.pgp.net"}
349 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}}
350 KNOWN_HOSTS=${MONKEYSPHERE_KNOWN_HOSTS:=${KNOWN_HOSTS:="${HOME}/.ssh/known_hosts"}}
351 HASH_KNOWN_HOSTS=${MONKEYSPHERE_HASH_KNOWN_HOSTS:=${HASH_KNOWN_HOSTS:="true"}}
352 AUTHORIZED_KEYS=${MONKEYSPHERE_AUTHORIZED_KEYS:=${AUTHORIZED_KEYS:="${HOME}/.ssh/authorized_keys"}}
354 # other variables not in config file
355 AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:="${MONKEYSPHERE_HOME}/authorized_user_ids"}
356 REQUIRED_HOST_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_HOST_KEY_CAPABILITY:="a"}
357 REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
359 # export GNUPGHOME and make sure gpg home exists with proper
362 mkdir -p -m 0700 "$GNUPGHOME"
367 [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
371 'update-known_hosts'|'update-known-hosts'|'k')
374 # touch the known_hosts file so that the file permission check
375 # below won't fail upon not finding the file
376 (umask 0022 && touch "$KNOWN_HOSTS")
378 # check permissions on the known_hosts file path
379 check_key_file_permissions "$USER" "$KNOWN_HOSTS" || failure
381 # if hosts are specified on the command line, process just
384 update_known_hosts "$@"
387 # otherwise, if no hosts are specified, process every host
388 # in the user's known_hosts file
390 # exit if the known_hosts file does not exist
391 if [ ! -e "$KNOWN_HOSTS" ] ; then
392 log error "known_hosts file '$KNOWN_HOSTS' does not exist."
401 'update-authorized_keys'|'update-authorized-keys'|'a')
402 MODE='authorized_keys'
404 # check permissions on the authorized_user_ids file path
405 check_key_file_permissions "$USER" "$AUTHORIZED_USER_IDS" || failure
407 # check permissions on the authorized_keys file path
408 check_key_file_permissions "$USER" "$AUTHORIZED_KEYS" || failure
410 # exit if the authorized_user_ids file is empty
411 if [ ! -e "$AUTHORIZED_USER_IDS" ] ; then
412 log error "authorized_user_ids file '$AUTHORIZED_USER_IDS' does not exist."
416 # process authorized_user_ids file
417 process_authorized_user_ids "$AUTHORIZED_USER_IDS"
429 'ssh-proxycommand'|'p')
430 ssh-proxycommand "$@"
433 'subkey-to-ssh-agent'|'s')
434 subkey_to_ssh_agent "$@"
441 '--help'|'help'|'-h'|'h'|'?')
446 failure "Unknown command: '$COMMAND'
447 Type '$PGRM help' for usage."