#!/bin/sh -e # rhesus: monkeysphere authorized_keys/known_hosts generating script # # Written by # Jameson Rollins # # Copyright 2008, released under the GPL, version 3 or later CMD=$(basename $0) ######################################################################## # FUNCTIONS ######################################################################## usage() { cat <&2 exit ${2:-'1'} } log() { echo -n "ms: " echo "$@" } # cut out all comments(#) and blank lines from standard input meat() { grep -v -e "^[[:space:]]*#" -e '^$' } # cut a specified line from standard input cutline() { head --line="$1" | tail -1 } # retrieve all keys with given user id from keyserver # FIXME: need to figure out how to retrieve all matching keys # (not just first 5) gpg_fetch_keys() { local id="$1" echo 1,2,3,4,5 | \ gpg --quiet --batch --command-fd 0 --with-colons \ --keyserver "$KEYSERVER" \ --search ="$id" >/dev/null 2>&1 } # convert escaped characters from gpg output back into original # character # FIXME: undo all escape character translation in with-colons gpg output unescape() { echo "$1" | sed 's/\\x3a/:/' } # stand in until we get dkg's gpg2ssh program gpg2ssh_tmp() { local mode local keyID mode="$1" keyID="$2" userID="$3" if [ "$mode" = '--authorized_keys' -o "$mode" = '-a' ] ; then gpgkey2ssh "$keyID" | sed -e "s/COMMENT/$userID/" elif [ "$mode" = '--known_hosts' -o "$mode" = '-k' ] ; then echo -n "$userID "; gpgkey2ssh "$keyID" | sed -e 's/ COMMENT//' fi } # userid and key policy checking # the following checks policy on the returned keys # - checks that full key has appropriate valididy (u|f) # - checks key has appropriate capability (E|A) # - checks that particular desired user id has appropriate validity # see /usr/share/doc/gnupg/DETAILS.gz # FIXME: add some more status output # expects global variable: "mode" process_user_id() { local userID local cacheDir local keyOK local keyCapability local keyFingerprint local userIDHash userID="$1" cacheDir="$2" # fetch all keys from keyserver # if none found, break if ! gpg_fetch_keys "$userID" ; then echo " no keys found." return fi # some crazy piping here that takes the output of gpg and # pipes it into a "while read" loop that reads each line # of standard input one-by-one. gpg --fixed-list-mode --list-key --with-colons \ --with-fingerprint ="$userID" 2> /dev/null | \ cut -d : -f 1,2,5,10,12 | \ while IFS=: read -r type validity keyid uidfpr capability ; do # process based on record type case $type in 'pub') # new key, wipe the slate keyOK= keyCapability= keyFingerprint= # check primary key validity if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then continue fi # check capability is not Disabled... if echo "$capability" | grep -q 'D' ; then continue fi # check capability is Encryption and Authentication # FIXME: make more flexible capability specification # (ie. in conf file) if echo "$capability" | grep -q -v 'E' ; then if echo "$capability" | grep -q -v 'A' ; then continue fi fi keyCapability="$capability" keyOK=true keyID="$keyid" ;; 'fpr') # if key ok, get fingerprint if [ "$keyOK" ] ; then keyFingerprint="$uidfpr" fi ;; 'uid') # check key ok and we have key fingerprint if [ -z "$keyOK" -o -z "$keyFingerprint" ] ; then continue fi # check key validity if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then continue fi # check the uid matches if [ "$(unescape "$uidfpr")" != "$userID" ] ; then continue fi # convert the key # FIXME: needs to apply extra options if specified echo -n " valid key found; generating ssh key(s)... " userIDHash=$(echo "$userID" | sha1sum | awk '{ print $1 }') # export the key with gpg2ssh #gpg --export "$keyFingerprint" | gpg2ssh "$mode" > "$cacheDir"/"$userIDHash"."$keyFingerprint" # stand in until we get dkg's gpg2ssh program gpg2ssh_tmp "$mode" "$keyID" "$userID" > "$cacheDir"/"$userIDHash"."$keyFingerprint" if [ "$?" = 0 ] ; then echo "done." else echo "error." fi ;; esac done } # process the auth_*_ids file # go through line-by-line, extracting and processing each user id # expects global variable: "mode" process_auth_file() { local authIDsFile local cacheDir local nLines local line local userID authIDsFile="$1" cacheDir="$2" # find number of user ids in auth_user_ids file nLines=$(meat <"$authIDsFile" | wc -l) # make sure gpg home exists with proper permissions mkdir -p -m 0700 "$GNUPGHOME" # clean out keys file and remake keys directory rm -rf "$cacheDir" mkdir -p "$cacheDir" # loop through all user ids for line in $(seq 1 $nLines) ; do # get user id # FIXME: needs to handle extra options if necessary userID=$(meat <"$authIDsFile" | cutline "$line" ) # process the user id and extract keys log "processing user id: '$userID'" process_user_id "$userID" "$cacheDir" done } ######################################################################## # MAIN ######################################################################## if [ -z "$1" ] ; then usage exit 1 fi # check mode mode="$1" shift 1 # check user if ! id -u "$USER" > /dev/null 2>&1 ; then failure "invalid user '$USER'." fi # set user home directory HOME=$(getent passwd "$USER" | cut -d: -f6) # get ms home directory MS_HOME=${MS_HOME:-"$HOME"/.config/monkeysphere} # load configuration file MS_CONF=${MS_CONF:-"$MS_HOME"/monkeysphere.conf} [ -e "$MS_CONF" ] && . "$MS_CONF" # set config variable defaults STAGING_AREA=${STAGING_AREA:-"$MS_HOME"} AUTH_HOST_FILE=${AUTH_HOST_FILE:-"$MS_HOME"/auth_host_ids} AUTH_USER_FILE=${AUTH_USER_FILE:-"$MS_HOME"/auth_user_ids} GNUPGHOME=${GNUPGHOME:-"$HOME"/.gnupg} KEYSERVER=${KEYSERVER:-subkeys.pgp.net} USER_KNOW_HOSTS="$HOME"/.ssh/known_hosts USER_AUTHORIZED_KEYS="$HOME"/.ssh/authorized_keys # export USER and GNUPGHOME variables, since they are used by gpg export USER export GNUPGHOME # stagging locations hostKeysCacheDir="$STAGING_AREA"/host_keys userKeysCacheDir="$STAGING_AREA"/user_keys msKnownHosts="$STAGING_AREA"/known_hosts msAuthorizedKeys="$STAGING_AREA"/authorized_keys # set mode variables if [ "$mode" = '--known_hosts' -o "$mode" = '-k' ] ; then fileType=known_hosts authIDsFile="$AUTH_HOST_FILE" outFile="$msKnownHosts" cacheDir="$hostKeysCacheDir" userFile="$USER_KNOWN_HOSTS" elif [ "$mode" = '--authorized_keys' -o "$mode" = '-a' ] ; then fileType=authorized_keys authIDsFile="$AUTH_USER_FILE" outFile="$msAuthorizedKeys" cacheDir="$userKeysCacheDir" userFile="$USER_AUTHORIZED_KEYS" else failure "unknown command '$mode'." fi # check auth ids file if [ ! -s "$authIDsFile" ] ; then echo $(basename "$authIDsFile") "file is empty or does not exist." exit fi log "user '$USER': monkeysphere $fileType generation..." # process the auth file process_auth_file "$authIDsFile" "$cacheDir" # write output key file log "writing ms $fileType file... " > "$outFile" if [ "$(ls "$cacheDir")" ] ; then log -n "adding gpg keys... " cat "$cacheDir"/* > "$outFile" echo "done." else log "no gpg keys to add." fi if [ -s "$userFile" ] ; then log -n "adding user $fileType file... " cat "$userFile" >> "$outFile" echo "done." fi log "ms $fileType file generated:" log "$outFile"