#!/usr/bin/env bash # monkeysphere: Monkeysphere client tool # # The monkeysphere scripts are written by: # Jameson Rollins # Jamie McClelland # Daniel Kahn Gillmor # Micah Anderson # # They are Copyright 2008-2009, and are all released under the GPL, version 3 # or later. ######################################################################## set -e PGRM=$(basename $0) SYSSHAREDIR=${MONKEYSPHERE_SYSSHAREDIR:-"__SYSSHAREDIR_PREFIX__/share/monkeysphere"} export SYSSHAREDIR . "${SYSSHAREDIR}/defaultenv" . "${SYSSHAREDIR}/common" # sharedir for host functions MSHAREDIR="${SYSSHAREDIR}/m" # UTC date in ISO 8601 format if needed DATE=$(date -u '+%FT%T') # unset some environment variables that could screw things up unset GREP_OPTIONS # set the file creation mask to be only owner rw umask 077 ######################################################################## # FUNCTIONS ######################################################################## usage() { cat <&2 usage: $PGRM [options] [args] Monkeysphere client tool. subcommands: update-known_hosts (k) [HOST]... update known_hosts file update-authorized_keys (a) update authorized_keys file ssh-proxycommand HOST [PORT] monkeysphere ssh ProxyCommand --no-connect do not make TCP connection to host subkey-to-ssh-agent (s) store authentication subkey in ssh-agent keys-for-userid (u) USERID output valid ssh keys for given user id sshfprs-for-userid USERID output ssh fingerprints for given user id gen-subkey (g) [KEYID] generate an authentication subkey --length (-l) BITS key length in bits (2048) version (v) show version number help (h,?) this help EOF } # user gpg command to define common options gpg_user() { LC_ALL=C gpg --fixed-list-mode --no-greeting --quiet --no-tty "$@" } # output the ssh fingerprint of a gpg key gpg_ssh_fingerprint() { keyid="$1" gpg_user --export "$keyid" --no-armor | "$SYSSHAREDIR/keytrans" openpgp2sshfpr "$keyid" } # take a secret key ID and check that only zero or one ID is provided, # and that it corresponds to only a single secret key ID check_gpg_sec_key_id() { local gpgSecOut case "$#" in 0) gpgSecOut=$(gpg_user --list-secret-keys --with-colons 2>/dev/null | egrep '^sec:') ;; 1) gpgSecOut=$(gpg_user --list-secret-keys --with-colons "$1" | egrep '^sec:') || failure ;; *) failure "You must specify only a single primary key ID." ;; esac # check that only a single secret key was found case $(echo "$gpgSecOut" | grep -c '^sec:') in 0) failure "No secret keys found. Create an OpenPGP key with the following command: gpg --gen-key" ;; 1) echo "$gpgSecOut" | cut -d: -f5 ;; *) local seckeys=$(echo "$gpgSecOut" | cut -d: -f5) failure "Multiple primary secret keys found: $seckeys Please specify which primary key to use." ;; esac } # check that a valid authentication subkey does not already exist check_gpg_authentication_subkey() { local keyID local IFS local line local type local validity local usage keyID="$1" # check that a valid authentication key does not already exist IFS=$'\n' for line in $(gpg_user --list-keys --with-colons "$keyID") ; do type=$(echo "$line" | cut -d: -f1) validity=$(echo "$line" | cut -d: -f2) usage=$(echo "$line" | cut -d: -f12) # look at keys only if [ "$type" != 'pub' -a "$type" != 'sub' ] ; then continue fi # check for authentication capability if ! check_capability "$usage" 'a' ; then continue fi # if authentication key is valid, prompt to continue if [ "$validity" = 'u' ] ; then echo "A valid authentication key already exists for primary key '$keyID'." 1>&2 if [ "$PROMPT" != "false" ] ; then printf "Are you sure you would like to generate another one? (y/N) " >&2 read OK; OK=${OK:N} if [ "${OK/y/Y}" != 'Y' ] ; then failure "aborting." fi break else failure "aborting." fi fi done } ######################################################################## # MAIN ######################################################################## # set unset default variables GNUPGHOME=${GNUPGHOME:="${HOME}/.gnupg"} KNOWN_HOSTS="${HOME}/.ssh/known_hosts" HASH_KNOWN_HOSTS="false" AUTHORIZED_KEYS="${HOME}/.ssh/authorized_keys" # unset the check keyserver variable, since that needs to have # different defaults for the different functions unset CHECK_KEYSERVER # load global config [ -r "${SYSCONFIGDIR}/monkeysphere.conf" ] \ && . "${SYSCONFIGDIR}/monkeysphere.conf" # set monkeysphere home directory MONKEYSPHERE_HOME=${MONKEYSPHERE_HOME:="${HOME}/.monkeysphere"} mkdir -p -m 0700 "$MONKEYSPHERE_HOME" # load local config [ -e ${MONKEYSPHERE_CONFIG:="${MONKEYSPHERE_HOME}/monkeysphere.conf"} ] \ && . "$MONKEYSPHERE_CONFIG" # set empty config variables with ones from the environment GNUPGHOME=${MONKEYSPHERE_GNUPGHOME:=$GNUPGHOME} LOG_LEVEL=${MONKEYSPHERE_LOG_LEVEL:=$LOG_LEVEL} KEYSERVER=${MONKEYSPHERE_KEYSERVER:=$KEYSERVER} # if keyserver not specified in env or conf, then look in gpg.conf if [ -z "$KEYSERVER" ] ; then if [ -f "${GNUPGHOME}/gpg.conf" ] ; then KEYSERVER=$(grep -e "^[[:space:]]*keyserver " "${GNUPGHOME}/gpg.conf" | tail -1 | awk '{ print $2 }') fi fi PROMPT=${MONKEYSPHERE_PROMPT:=$PROMPT} KNOWN_HOSTS=${MONKEYSPHERE_KNOWN_HOSTS:=$KNOWN_HOSTS} HASH_KNOWN_HOSTS=${MONKEYSPHERE_HASH_KNOWN_HOSTS:=$HASH_KNOWN_HOSTS} AUTHORIZED_KEYS=${MONKEYSPHERE_AUTHORIZED_KEYS:=$AUTHORIZED_KEYS} STRICT_MODES=${MONKEYSPHERE_STRICT_MODES:=$STRICT_MODES} # other variables not in config file AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:="${MONKEYSPHERE_HOME}/authorized_user_ids"} REQUIRED_HOST_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_HOST_KEY_CAPABILITY:="a"} REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"} # note that only using '=' instead of ':=' tests only if the variable # in unset, not if it's "null" LOG_PREFIX=${MONKEYSPHERE_LOG_PREFIX='ms: '} # export GNUPGHOME and make sure gpg home exists with proper # permissions export GNUPGHOME mkdir -p -m 0700 "$GNUPGHOME" export LOG_LEVEL export LOG_PREFIX if [ "$#" -eq 0 ] ; then usage failure "Please supply a subcommand." fi # get subcommand COMMAND="$1" shift case $COMMAND in 'update-known_hosts'|'update-known-hosts'|'k') # whether or not to check keyservers CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}} source "${MSHAREDIR}/update_known_hosts" # if hosts are specified on the command line, process just # those hosts if [ "$1" ] ; then update_known_hosts "$@" # otherwise, if no hosts are specified, process every host # in the user's known_hosts file else process_known_hosts fi ;; 'update-authorized_keys'|'update-authorized-keys'|'a') # whether or not to check keyservers CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}} source "${MSHAREDIR}/update_authorized_keys" update_authorized_keys ;; 'import-subkey'|'import'|'i') source "${MSHAREDIR}/import_subkey" import_subkey "$@" ;; 'gen-subkey'|'g') source "${MSHAREDIR}/gen_subkey" gen_subkey "$@" ;; 'ssh-proxycommand'|'p') source "${MSHAREDIR}/ssh_proxycommand" ssh_proxycommand "$@" ;; 'subkey-to-ssh-agent'|'s') source "${MSHAREDIR}/subkey_to_ssh_agent" subkey_to_ssh_agent "$@" ;; 'sshfpr') echo "Warning: 'sshfpr' is deprecated. Please use 'sshfprs-for-userid' instead." >&2 gpg_ssh_fingerprint "$@" ;; 'keys-for-userid'|'u') CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}} source "${MSHAREDIR}/keys_for_userid" keys_for_userid "$@" ;; 'sshfprs-for-userid') CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}} source "${MSHAREDIR}/keys_for_userid" keys_for_userid "$@" | "$SYSSHAREDIR/keytrans" sshfpr ;; 'keys-from-userid') echo "Warning: 'keys-from-userid' is deprecated. Please use 'keys-for-userid' instead." >&2 CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:=${CHECK_KEYSERVER:="true"}} source "${MSHAREDIR}/keys_for_userid" keys_for_userid "$@" ;; 'version'|'--version'|'v') version ;; 'help'|'--help'|'-h'|'h'|'?') usage ;; *) failure "Unknown command: '$COMMAND' Try '$PGRM help' for usage." ;; esac