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
48 version (v) show version number
54 # generate a subkey with the 'a' usage flags set
62 # set default key parameter values
78 if [ "$(echo "$1" | cut -c 1)" = '-' ] ; then
79 failure "Unknown option '$1'.
80 Type '$PGRM help' for usage."
88 # find all secret keys
89 keyID=$(gpg --with-colons --list-secret-keys | grep ^sec | cut -f5 -d: | sort -u)
90 # if multiple sec keys exist, fail
91 if (( $(echo "$keyID" | wc -l) > 1 )) ; then
92 echo "Multiple secret keys found:"
94 failure "Please specify which primary key to use."
99 if [ -z "$keyID" ] ; then
100 failure "You have no secret key available. You should create an OpenPGP
101 key before joining the monkeysphere. You can do this with:
105 # get key output, and fail if not found
106 gpgOut=$(gpg --quiet --fixed-list-mode --list-secret-keys --with-colons \
109 # fail if multiple sec lines are returned, which means the id
110 # given is not unique
111 if [ $(echo "$gpgOut" | grep -c '^sec:') -gt '1' ] ; then
112 failure "Key ID '$keyID' is not unique."
115 # prompt if an authentication subkey already exists
116 if echo "$gpgOut" | egrep "^(sec|ssb):" | cut -d: -f 12 | grep -q a ; then
117 echo "An authentication subkey already exists for key '$keyID'."
118 read -p "Are you sure you would like to generate another one? (y/N) " OK; OK=${OK:N}
119 if [ "${OK/y/Y}" != 'Y' ] ; then
124 # set subkey defaults
125 # prompt about key expiration if not specified
126 keyExpire=$(get_gpg_expiration "$keyExpire")
128 # generate the list of commands that will be passed to edit-key
129 editCommands=$(cat <<EOF
142 log verbose "generating subkey..."
143 fifoDir=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
144 (umask 077 && mkfifo "$fifoDir/pass")
145 echo "$editCommands" | gpg --passphrase-fd 3 3< "$fifoDir/pass" --expert --command-fd 0 --edit-key "$keyID" &
147 passphrase_prompt "Please enter your passphrase for $keyID: " "$fifoDir/pass"
154 subkey_to_ssh_agent() {
155 # try to add all authentication subkeys to the agent:
166 if ! test_gnu_dummy_s2k_extension ; then
167 failure "Your version of GnuTLS does not seem capable of using with gpg's exported subkeys.
168 You may want to consider patching or upgrading to GnuTLS 2.6 or later.
170 For more details, see:
171 http://lists.gnu.org/archive/html/gnutls-devel/2008-08/msg00005.html"
174 # if there's no agent running, don't bother:
175 if [ -z "$SSH_AUTH_SOCK" ] || ! which ssh-add >/dev/null ; then
176 failure "No ssh-agent available."
179 # and if it looks like it's running, but we can't actually talk to
181 ssh-add -l >/dev/null
183 if [ "$sshaddresponse" = "2" ]; then
184 failure "Could not connect to ssh-agent"
187 # get list of secret keys (to work around https://bugs.g10code.com/gnupg/issue945):
188 secretkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode --fingerprint | \
189 grep '^fpr:' | cut -f10 -d: | awk '{ print "0x" $1 "!" }')
191 if [ -z "$secretkeys" ]; then
192 failure "You have no secret keys in your keyring!
193 You might want to run 'gpg --gen-key'."
196 authsubkeys=$(gpg --list-secret-keys --with-colons --fixed-list-mode \
197 --fingerprint --fingerprint $secretkeys | \
198 cut -f1,5,10,12 -d: | grep -A1 '^ssb:[^:]*::[^:]*a[^:]*$' | \
199 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 ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
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"
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 $kname: " "$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 "${SYSCONFIGDIR}/monkeysphere.conf" ] && . "${SYSCONFIGDIR}/monkeysphere.conf"
268 # set monkeysphere home directory
269 MONKEYSPHERE_HOME=${MONKEYSPHERE_HOME:="${HOME}/.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 LOG_LEVEL=${MONKEYSPHERE_LOG_LEVEL:=${LOG_LEVEL:="INFO"}}
278 GNUPGHOME=${MONKEYSPHERE_GNUPGHOME:=${GNUPGHOME:="${HOME}/.gnupg"}}
279 KEYSERVER=${MONKEYSPHERE_KEYSERVER:="$KEYSERVER"}
280 # if keyserver not specified in env or monkeysphere.conf,
282 if [ -z "$KEYSERVER" ] ; then
283 if [ -f "${GNUPGHOME}/gpg.conf" ] ; then
284 KEYSERVER=$(grep -e "^[[:space:]]*keyserver " "${GNUPGHOME}/gpg.conf" | tail -1 | awk '{ print $2 }')
287 # if it's still not specified, use the default
288 KEYSERVER=${KEYSERVER:="subkeys.pgp.net"}
289 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}}
290 KNOWN_HOSTS=${MONKEYSPHERE_KNOWN_HOSTS:=${KNOWN_HOSTS:="${HOME}/.ssh/known_hosts"}}
291 HASH_KNOWN_HOSTS=${MONKEYSPHERE_HASH_KNOWN_HOSTS:=${HASH_KNOWN_HOSTS:="true"}}
292 AUTHORIZED_KEYS=${MONKEYSPHERE_AUTHORIZED_KEYS:=${AUTHORIZED_KEYS:="${HOME}/.ssh/authorized_keys"}}
294 # other variables not in config file
295 AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:="${MONKEYSPHERE_HOME}/authorized_user_ids"}
296 REQUIRED_HOST_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_HOST_KEY_CAPABILITY:="a"}
297 REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
299 # export GNUPGHOME and make sure gpg home exists with proper
302 mkdir -p -m 0700 "$GNUPGHOME"
307 [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
311 'update-known_hosts'|'update-known-hosts'|'k')
314 # touch the known_hosts file so that the file permission check
315 # below won't fail upon not finding the file
316 (umask 0022 && touch "$KNOWN_HOSTS")
318 # check permissions on the known_hosts file path
319 check_key_file_permissions "$USER" "$KNOWN_HOSTS" || failure
321 # if hosts are specified on the command line, process just
324 update_known_hosts "$@"
327 # otherwise, if no hosts are specified, process every host
328 # in the user's known_hosts file
330 # exit if the known_hosts file does not exist
331 if [ ! -e "$KNOWN_HOSTS" ] ; then
332 log error "known_hosts file '$KNOWN_HOSTS' does not exist."
341 'update-authorized_keys'|'update-authorized-keys'|'a')
342 MODE='authorized_keys'
344 # check permissions on the authorized_user_ids file path
345 check_key_file_permissions "$USER" "$AUTHORIZED_USER_IDS" || failure
347 # check permissions on the authorized_keys file path
348 check_key_file_permissions "$USER" "$AUTHORIZED_KEYS" || failure
350 # exit if the authorized_user_ids file is empty
351 if [ ! -e "$AUTHORIZED_USER_IDS" ] ; then
352 log error "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 "$@"
373 '--help'|'help'|'-h'|'h'|'?')
378 failure "Unknown command: '$COMMAND'
379 Type '$PGRM help' for usage."