# -*-shell-script-*- # Shared sh functions for the monkeysphere # # Written by # Jameson Rollins # # Copyright 2008, released under the GPL, version 3 or later # all-caps variables are meant to be user supplied (ie. from config # file) and are considered global ######################################################################## # managed directories ETC="/etc/monkeysphere" export ETC CACHE="/var/cache/monkeysphere" export CACHE ######################################################################## failure() { echo "$1" >&2 exit ${2:-'1'} } # write output to stderr log() { echo -n "ms: " 1>&2 echo "$@" 1>&2 } # 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_userid() { local userID userID="$1" log "checking keyserver $KEYSERVER..." echo 1,2,3,4,5 | \ gpg --quiet --batch --command-fd 0 --with-colons \ --keyserver "$KEYSERVER" \ --search ="$userID" >/dev/null 2>&1 if [ "$?" = 0 ] ; then log " user ID found on keyserver." return 0 else log " user ID not found on keyserver." return 1 fi } # check that characters are in a string (in an AND fashion). # used for checking key capability # check_capability capability a [b...] check_capability() { local capability local capcheck capability="$1" shift 1 for capcheck ; do if echo "$capability" | grep -q -v "$capcheck" ; then return 1 fi done return 0 } # get the full fingerprint of a key ID get_key_fingerprint() { local keyID keyID="$1" gpg --list-key --with-colons --fixed-list-mode \ --with-fingerprint "$keyID" | grep "$keyID" | \ grep '^fpr:' | cut -d: -f10 } # 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/:/' } # convert key from gpg to ssh known_hosts format gpg2known_hosts() { local keyID local host keyID="$1" host=$(echo "$2" | sed -e "s|ssh://||") # NOTE: it seems that ssh-keygen -R removes all comment fields from # all lines in the known_hosts file. why? # NOTE: just in case, the COMMENT can be matched with the # following regexp: # '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$' echo -n "$host " gpg --export "$keyID" | \ openpgp2ssh "$keyID" | tr -d '\n' echo " MonkeySphere${DATE}" } # convert key from gpg to ssh authorized_keys format gpg2authorized_keys() { local keyID local userID keyID="$1" userID="$2" gpg --export "$keyID" | \ openpgp2ssh "$keyID" | tr -d '\n' echo " MonkeySphere${DATE}: ${userID}" } # 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 specified capability (REQUIRED_*_KEY_CAPABILITY) # - checks that particular desired user id has appropriate validity # see /usr/share/doc/gnupg/DETAILS.gz # expects global variable: "MODE" process_user_id() { local userID local cacheDir local requiredCapability local requiredPubCapability local gpgOut local userIDHash local keyCacheDir local line local type local validity local keyid local uidfpr local usage local keyOK local pubKeyID local uidOK local keyIDs local keyID userID="$1" cacheDir="$2" # set the required key capability based on the mode if [ "$MODE" = 'known_hosts' ] ; then requiredCapability="$REQUIRED_HOST_KEY_CAPABILITY" elif [ "$MODE" = 'authorized_keys' ] ; then requiredCapability="$REQUIRED_USER_KEY_CAPABILITY" fi requiredPubCapability=$(echo "$requiredCapability" | tr "[:lower:]" "[:upper:]") # if CHECK_KEYSERVER variable set, check the keyserver # for the user ID if [ "$CHECK_KEYSERVER" = "true" ] ; then gpg_fetch_userid "$userID" fi # output gpg info for (exact) userid and store gpgOut=$(gpg --list-key --fixed-list-mode --with-colon \ --with-fingerprint --with-fingerprint \ ="$userID" 2>/dev/null) # if the gpg query return code is not 0, return 1 if [ "$?" -ne 0 ] ; then log " key not found." return 1 fi echo "$gpgOut" # loop over all lines in the gpg output and process. # need to do it this way (as opposed to "while read...") so that # variables set in loop will be visible outside of loop echo "$gpgOut" | cut -d: -f1,2,5,10,12 | \ while IFS=: read -r type validity keyid uidfpr usage ; do # process based on record type case $type in 'pub') # primary keys # new key, wipe the slate keyOK= uidOK= pubKeyOK= fingerprint= # if overall key is not valid, skip if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then log " unacceptable primary key validity ($validity)." continue fi # if overall key is disabled, skip if check_capability "$usage" 'D' ; then log " key disabled." continue fi # if overall key capability is not ok, skip if ! check_capability "$usage" $requiredPubCapability ; then log " unacceptable primary key capability ($usage)." continue fi # mark overall key as ok keyOK=true # mark primary key as ok if capability is ok if check_capability "$usage" $requiredCapability ; then pubKeyOK=true fi ;; 'uid') # user ids # if the overall key is not ok, skip if [ -z "$keyOK" ] ; then continue fi # if an acceptable user ID was already found, skip if [ "$uidOK" ] ; then continue fi # if the user ID does not match, skip if [ "$(unescape "$uidfpr")" != "$userID" ] ; then continue fi # if the user ID validity is not ok, skip if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then continue fi # mark user ID acceptable uidOK=true # output a line for the primary key # 0 = ok, 1 = bad if [ "$keyOK" -a "$uidOK" -a "$pubKeyOK" ] ; then log " acceptable key found" echo 0 "$fingerprint" else echo 1 "$fingerprint" fi ;; 'sub') # sub keys # unset acceptability of last key subKeyOK= fingerprint= # if the overall key is not ok, skip if [ -z "$keyOK" ] ; then continue fi # if sub key validity is not ok, skip if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then continue fi # if sub key capability is not ok, skip if ! check_capability "$usage" $requiredCapability ; then continue fi # mark sub key as ok subKeyOK=true ;; 'fpr') # key fingerprint fingerprint="$uidfpr" # output a line for the last subkey # 0 = ok, 1 = bad if [ "$keyOK" -a "$uidOK" -a "$subKeyOK" ] ; then log " acceptable key found" echo 0 "$fingerprint" else echo 1 "$fingerprint" fi ;; esac done } # update the cache for userid, and prompt to add file to # authorized_user_ids file if the userid is found in gpg # and not already in file. update_userid() { local userID local cacheDir local keyCache userID="$1" cacheDir="$2" log "processing userid: '$userID'" # return 1 if there is no output of the user ID processing # ie. no key was found keyCachePath=$(process_user_id "$userID" "$cacheDir") if [ -z "$keyCachePath" ] ; then return 1 fi # check if user ID is in the authorized_user_ids file if ! grep -q "^${userID}\$" "$AUTHORIZED_USER_IDS" ; then read -p "user ID not currently authorized. authorize? [Y|n]: " OK; OK=${OK:=Y} if [ ${OK/y/Y} = 'Y' ] ; then # add if specified log -n "adding user ID to authorized_user_ids file... " echo "$userID" >> "$AUTHORIZED_USER_IDS" echo "done." else # else do nothing log "authorized_user_ids file untouched." fi fi } # remove a userid from the authorized_user_ids file remove_userid() { local userID userID="$1" log "processing userid: '$userID'" # check if user ID is in the authorized_user_ids file if ! grep -q "^${userID}\$" "$AUTHORIZED_USER_IDS" ; then log "user ID not currently authorized." return 1 fi # remove user ID from file log -n "removing user ID '$userID'... " grep -v "$userID" "$AUTHORIZED_USER_IDS" | sponge "$AUTHORIZED_USER_IDS" echo "done." } # remove all keys from specified key cache from known_hosts file remove_known_hosts_host_keys() { local keyCachePath local hosts local type local key local comment keyCachePath="$1" meat "${keyCachePath}/keys" | \ while read -r hosts type key comment ; do grep -v "$key" "$USER_KNOWN_HOSTS" | sponge "$USER_KNOWN_HOSTS" done } # process a host for addition to a known_host file process_host() { local host local cacheDir local keyCachePath host="$1" cacheDir="$2" log "processing host: $host" userID="ssh://${host}" process_user_id "ssh://${host}" exit process_user_id "ssh://${host}" | \ while read -r ok key ; do # remove the old host key line remove_known_hosts_host_keys "$key" # if key OK, add new host line if [ "$ok" -eq '0' ] ; then known_hosts_line "$host" "$key" >> "$USER_KNOWN_HOSTS" fi done } # process known_hosts file # go through line-by-line, extract each host, and process with the # host processing function process_known_hosts() { local cacheDir local hosts local host cacheDir="$1" # take all the hosts from the known_hosts file (first field), # grep out all the hashed hosts (lines starting with '|')... meat "$USER_KNOWN_HOSTS" | cut -d ' ' -f 1 | grep -v '^|.*$' | \ while IFS=, read -r -a hosts ; do # ...and process each host for host in ${hosts[*]} ; do process_host "$host" "$cacheDir" done done } # update an authorized_keys file after first processing the # authorized_user_ids file update_authorized_keys() { local msAuthorizedKeys local userAuthorizedKeys local cacheDir msAuthorizedKeys="$1" userAuthorizedKeys="$2" cacheDir="$3" process_authorized_ids "$AUTHORIZED_USER_IDS" "$cacheDir" # write output key file log "writing monkeysphere authorized_keys file... " touch "$msAuthorizedKeys" if [ "$(ls "$cacheDir")" ] ; then log -n "adding gpg keys... " cat "$cacheDir"/* > "$msAuthorizedKeys" echo "done." else log "no gpg keys to add." fi if [ "$userAuthorizedKeys" != "-" -a -s "$userAuthorizedKeys" ] ; then log -n "adding user authorized_keys file... " cat "$userAuthorizedKeys" >> "$msAuthorizedKeys" echo "done." fi log "monkeysphere authorized_keys file generated:" log "$msAuthorizedKeys" } # process an authorized_*_ids file # go through line-by-line, extract each userid, and process process_authorized_ids() { local authorizedIDs local cacheDir local userID authorizedIDs="$1" cacheDir="$2" process_user_id "$userID" | \ while read -r ok key ; do # remove the old host key line remove_authorized_keys_user_keys "$key" # if key OK, add new host line if [ "$ok" -eq '0' ] ; then authorized_keys_line "$userID" "$key" >> "$USER_AUTHORIZED_KEYS" fi done } # EXPERIMENTAL (unused) process userids found in authorized_keys file # go through line-by-line, extract monkeysphere userids from comment # fields, and process each userid process_authorized_keys() { local authorizedKeys local cacheDir local userID authorizedKeys="$1" cacheDir="$2" # take all the monkeysphere userids from the authorized_keys file # comment field (third field) that starts with "MonkeySphere uid:" # FIXME: needs to handle authorized_keys options (field 0) cat "$authorizedKeys" | \ while read -r options keytype key comment ; do # if the comment field is empty, assume the third field was # the comment if [ -z "$comment" ] ; then comment="$key" fi if ! echo "$comment" | grep '^MonkeySphere userID:.*$' ; then continue fi userID=$(echo "$comment" | sed -e "/^MonkeySphere userID://") if [ -z "$userID" ] ; then continue fi # process the userid log "processing userid: '$userID'" process_user_id "$userID" "$cacheDir" > /dev/null done } # retrieve key from web of trust, and set owner trust to "full" # if key is found. trust_key() { # get the key from the key server if ! gpg --keyserver "$KEYSERVER" --recv-key "$keyID" ; then log "could not retrieve key '$keyID'" return 1 fi # get key fingerprint fingerprint=$(get_key_fingerprint "$keyID") # attach a "non-exportable" signature to the key # this is required for the key to have any validity at all # the 'y's on stdin indicates "yes, i really want to sign" echo -e 'y\ny' | gpg --lsign-key --command-fd 0 "$fingerprint" # import "full" trust for fingerprint into gpg echo ${fingerprint}:5: | gpg --import-ownertrust if [ $? = 0 ] ; then log "owner trust updated." else failure "there was a problem changing owner trust." fi } # publish server key to keyserver publish_server_key() { read -p "really publish key to $KEYSERVER? [y|N]: " OK; OK=${OK:=N} if [ ${OK/y/Y} != 'Y' ] ; then failure "aborting." fi # publish host key # FIXME: need to figure out better way to identify host key # dummy command so as not to publish fakes keys during testing # eventually: #gpg --send-keys --keyserver "$KEYSERVER" $(hostname -f) echo "NOT PUBLISHED: gpg --send-keys --keyserver $KEYSERVER $(hostname -f)" }