summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbill-auger <mr.j.spam.me@gmail.com>2021-01-02 18:13:39 -0500
committerbill-auger <mr.j.spam.me@gmail.com>2021-01-05 22:24:56 -0500
commite255353d8db12f6e694ce298852bbe76c2f00ce1 (patch)
tree43981d80559d566b920d51540919f662b79cb9ea /src
parentc3bfa1dbb5e139c53dd7fe9ac3cac9384708de7e (diff)
[parabola-keys]: refactor - handle sub-keys - display hacker logins
Diffstat (limited to 'src')
-rwxr-xr-xsrc/maintenance-tools/parabola-keys339
1 files changed, 262 insertions, 77 deletions
diff --git a/src/maintenance-tools/parabola-keys b/src/maintenance-tools/parabola-keys
index 76aa221..4ca34bf 100755
--- a/src/maintenance-tools/parabola-keys
+++ b/src/maintenance-tools/parabola-keys
@@ -1,107 +1,292 @@
#!/bin/bash
-readonly KEYS_FILE=/usr/share/pacman/keyrings/parabola-trusted
+
+# parabola-keys - detect problems and inconsistencies
+#
+# This file is part of Parabola Libretools.
+# Copyright 2020-2021 bill-auger <bill-auger@programmer.net>
+#
+# Libretools is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Libretools is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Libretools. If not, see <https://www.gnu.org/licenses/>.
+#
+#
+# DESCRIPTION:
+#
+# this script depends on the 'parabola-hackers' package
+# TODO: it should probably be moved into the 'parabola-hackers' package
+#
+# if any problems are reported, other than 'WarningKeys' or 'OutdatedKeys',
+# the 'parabola-keyring' package should be rebuilt with high-priority
+# 'OutdatedKeys' may or may not indicate a probelm,
+# and require manual inspection, as these have bypassed all other checks
+#
+# the 'parabola-keyring' keys are loaded/refreshed into the local user GPG keyring,
+# directly from a keyserver, independent of the 'pacman-keyring' keyring
+# then compared to the state of the pristine 'pacman-keyring' keyring
+# in that way, edge cases can be detected,
+# which could be resolved with `pacman-key --refresh`,
+# but which may be blocking installs/upgrades otherwise
+# in that way, problems are detected, which would block some installs/upgrades
+# and also certain edge cases, which could be resolved with `pacman-key --refresh`,
+# such as expiry extension after the last 'parabola-keyring' package build,
+# but which may nonetheless be blocking installs/upgrades for some users
+# optimal accuracy depends on a pristine system pacman keyring (/etc/pacman.d/gnupg/)
+# (ie: it _not_ been refreshed explicitly, since the last 'parabola-keyring' upgrade)
+#
+#
+# OPTIONS:
+#
+# --all
+# list all keys, including those without problems ('ValidKeys')
+#
+# --chroot <PATH_TO_CHROOT>
+# check the 'parabola-keyring' of a chroot system
+# the chroot system must be already mounted, or otherwise accessible
+#
+
+
+readonly HACKER_KEYS_SCRIPT=/usr/lib/parabola-hackers/pgp-list-keyids
+readonly KEYSERVER=hkp://hkps.pool.sks-keyservers.net:11371/
readonly WARNING_N_DAYS=30
readonly AUTOBUILDER_KEY='D3EAD7F9D076EB9AF650149DA170D6A0B669E21A'
-readonly SHOULD_SHOW_ALL=$( [[ "$1" == '--all' ]] && echo 1 || echo 0 )
+readonly SHOULD_SHOW_ALL=$( [[ "$1" == '--all' ]] && echo 1 || echo 0 ) ; (( $# > 1 )) && shift ;
readonly CHROOT=$( [[ "$1" == '--chroot' ]] && echo $2 )
-readonly KEYS=$(cat $KEYS_FILE)
-readonly JOIN_CHAR='~'
-# readonly EMAIL_REGEX='.*key \([^ ,]*\), .*'
-# readonly KEY_REGEX='.*key \([^ ,]*\), .*'
-readonly EXPIRY_REGEX='.* expires: \([0-9-]*\).*'
+readonly JOIN_CHAR='='
+readonly INFINITE_EXPIRY='EXPIRY_INFINITE'
+readonly HACKER_KEY_REGEX="([^${JOIN_CHAR}]+)${JOIN_CHAR}([^${JOIN_CHAR}]+)"
+readonly KEYDATA_EXPIRY_1_REGEX="([^=]+)${JOIN_CHAR}([0-9A-F]{40})${JOIN_CHAR}([0-9]{4}-[0-9]{2}-[0-9]{2})"
+readonly KEYDATA_EXPIRY_2_REGEX="([^=]+)${JOIN_CHAR}([0-9A-F]{40})${JOIN_CHAR}(${INFINITE_EXPIRY})"
+readonly EXPIRY_REGEX='^.* \[expire[ds]: ([0-9]{4}-[0-9]{2}-[0-9]{2})\]$'
+readonly TRUSTED_REGEX='^uid \[ full \]'
+readonly GPG_CMD="gpg --keyserver ${KEYSERVER}"
+readonly GPG_LIST_OPTIONS='--list-options show-unusable-subkeys --with-subkey-fingerprint'
+readonly PACMAN_LIST_OPTIONS="--homedir /etc/pacman.d/gnupg ${GPG_LIST_OPTIONS}"
readonly NOW=$(date +%s)
-readonly WARNING_DURATION=$(( 86400 * ${WARNING_N_DAYS} ))
+readonly WARNING_DURATION=$(( 86400 * WARNING_N_DAYS ))
+readonly CHROOT_ERR_MSG="ERROR: specified chroot does not exist: '${CHROOT}'"
+readonly HACKER_KEY_ERR_MSG="ERROR: no key data - check ${HACKER_KEYS_SCRIPT}"
+readonly HACKER_KEYS=( $(${HACKER_KEYS_SCRIPT} | grep -v ${AUTOBUILDER_KEY} | tr ' ' ${JOIN_CHAR}) ) # testing examples below:
+# readonly HACKER_KEYS=("trusted/aurelien${JOIN_CHAR}560B3DEC2F13E822ACED475B2EC52AC76AEEB6A0") # EXPIRY_INFINITE,no-subkey
+# readonly HACKER_KEYS=("trusted/bill-auger${JOIN_CHAR}FBCC5AD7421197B7ABA72853908710913E8C7778") # rolling expiry,subkey
+# readonly HACKER_KEYS=("trusted/oaken-source${JOIN_CHAR}BFA8008A8265677063B11BF47171986E4B745536") # rolling expiry,no-signing-subkey
+# readonly HACKER_KEYS=("trusted/freemor${JOIN_CHAR}17446CB76E250EE809DFE1F2A7DC2FC7EBC8F713")
+
+declare -A OutdatedKeys
+declare -A WarningKeys
+declare -A RevokedKeys
+declare -A ExpiredKeys
+declare -A UntrustedKeys
+declare -A ValidKeys
+
+
+## logging ##
+
+readonly DEBUG=0
+DBG() { (( DEBUG )) && LOG "$@" ; }
+LOG() { echo -e "$@" >&2 ; }
-declare -A all_keys
-declare -A warning_keys
-declare -A valid_keys
-declare -A expired_keys
-declare -A revoked_keys
+## helpers ##
-FetchKey() # (fingerprint)
+ParseExpiry() # (key_data)
{
- gpg --batch --search-keys $1 2> /dev/null | tr "\n" "${JOIN_CHAR}" | sed -E 's|^\([0-9+]\)\s+||'
+ local key_data="$1"
+ local expiry=$(grep -E "${EXPIRY_REGEX}" <<<${key_data} | sed -E "s|${EXPIRY_REGEX}|\1|")
+
+ [[ -n "${expiry}" ]] && echo ${expiry} || echo ${INFINITE_EXPIRY}
}
-ParseExpiry() # (key_data)
+ParseKeyData() # (login key_id key_data)
{
- expiry=$(echo $1 | grep 'expires:' | sed "s|.*${EXPIRY_REGEX}|\1|")
+ local login=$1
+ local key_id=$2
+ local key_data="$3"
+ local master_key_data="$(grep --max-count=1 -A 1 -E '^pub ' <<<${key_data})"
+ local subkey_data="$( grep --max-count=1 -A 1 -E '^sub ' <<<${key_data})"
+ local master_key_id=$(tail -n 1 <<<${master_key_data} | tr -d ' ')
+ local subkey_id=$( tail -n 1 <<<${subkey_data} | tr -d ' ')
+ local master_expiry=$(ParseExpiry "${master_key_data}")
+ local subkey_expiry=$(ParseExpiry "${subkey_data}" )
+
+DBG "\nParseKeyData() key_id='${key_id}'"
+# DBG "ParseKeyData() key_data='${key_data}'"
+# DBG "ParseKeyData() master_key_data='${master_key_data}'"
+# DBG "ParseKeyData() subkey_data='${subkey_data}'"
+DBG "ParseKeyData() master_key_id='${master_key_id}'"
+DBG "ParseKeyData() subkey_id='${subkey_id}'"
+DBG "ParseKeyData() master_expiry='${master_expiry}'"
+DBG "ParseKeyData() subkey_expiry='${subkey_expiry}'"
- [ "${expiry} " ] && echo ${expiry} || echo 'EXPIRY_INFINITE'
+ if [[ -z "${subkey_data}" ]]
+ then echo "${login}${JOIN_CHAR}${master_key_id}${JOIN_CHAR}${master_expiry}"
+ else echo "${login}${JOIN_CHAR}${master_key_id}${JOIN_CHAR}${master_expiry} ${login}${JOIN_CHAR}${subkey_id}${JOIN_CHAR}${subkey_expiry}"
+ fi
}
-IsValid() # (key_data)
+LoadKeyData() # (login key_id "list_options")
+{
+ local login=$1
+ local key_id=$2
+ local list_options="$3"
+
+ [[ "${list_options}" == "${GPG_LIST_OPTIONS}" ]] && ${GPG_CMD} --recv-keys ${key_id} &> /dev/null
+ ParseKeyData ${login} ${key_id} "$(${GPG_CMD} ${list_options} --list-keys ${key_id} 2> /dev/null)"
+}
+
+RawKeysData() # (key_id)
{
- echo $1 | grep -Ev '(expired)|(revoked)' > /dev/null
+ local key_id=$1
+
+ pacman-key --list-keys ${key_id} 2> /dev/null
+}
+
+ShouldWarn() # (expiry)
+{
+ local expiry=$1
+ local expiry_ts=$(date --date ${expiry} +%s 2> /dev/null)
+ local expiry_duration=$(( expiry_ts - NOW ))
+
+ [[ ${expiry_duration} -gt 0 && ${expiry_duration} -lt ${WARNING_DURATION} ]]
}
IsExpired() # (key_data)
{
- echo $1 | grep -E '(expired)' > /dev/null
+ local key_data="$1"
+
+ grep -E '(expired)' <<<${key_data} > /dev/null
}
IsRevoked() # (key_data)
{
- echo $1 | grep -E '(revoked)' > /dev/null
+ local key_data="$1"
+
+ grep -E '(revoked)' <<<${key_data} > /dev/null
}
-# run in chroot
-if [[ -d "$CHROOT" ]]
-then sudo cp ${BASH_SOURCE} $CHROOT/
- sudo chroot $CHROOT/ ./$(basename ${BASH_SOURCE})
- exit
-fi
+IsUntrusted() # (key_data)
+{
+ local key_data="$1"
-# collect results
-echo -n "($(echo $KEYS | wc -w)) keys to consider "
-for key in ${KEYS}
-do [[ "${key%%:*}" != ${AUTOBUILDER_KEY} ]] && echo -n '.' || continue
-
- # fetch and parse key data
- key=${key%%:*}
- key_data="$(FetchKey ${key})"
-
- # detect expiry warning period
- expiry=$(ParseExpiry "${key_data}")
- expiry_ts=$(date --date ${expiry} +%s 2> /dev/null)
- expiry_duration=$(( ${expiry_ts} - $NOW ))
- (( ${expiry_duration} <= ${WARNING_DURATION} )) && \
- (( ${expiry_duration} > 0 )) && should_warn=1 || \
- should_warn=0
-
- # cache key data (mutually exclusive states)
- all_keys[${key}]="${key_data}"
- (( ${should_warn} )) && warning_keys[${key}]="${expiry}" && continue
- IsValid "${key_data}" && valid_keys[${key}]="${expiry}" && continue
- IsExpired "${key_data}" && expired_keys[${key}]="${expiry}" && continue
- IsRevoked "${key_data}" && revoked_keys[${key}]="${expiry}" && continue
-done ; echo ;
-
-# display results
-if (( ${#valid_keys[@]} * ${SHOULD_SHOW_ALL} ))
-then echo -e "\n== valid_keys ==\n"
- for key in "${!valid_keys[@]}"
- do echo ${all_keys[${key}]} | tr "${JOIN_CHAR}" "\n"
- done
-fi
-if (( ${#warning_keys[@]} ))
-then echo -e "\n== warning_keys ==\n"
- for key in "${!warning_keys[@]}"
- do echo ${all_keys[${key}]} | tr "${JOIN_CHAR}" "\n"
- done
-fi
-if (( ${#expired_keys[@]} ))
-then echo -e "\n== expired_keys ==\n"
- for key in "${!expired_keys[@]}"
- do echo ${all_keys[${key}]} | tr "${JOIN_CHAR}" "\n"
- done
-fi
-if (( ${#revoked_keys[@]} ))
-then echo -e "\n== revoked_keys ==\n"
- for key in "${!revoked_keys[@]}"
- do echo ${all_keys[${key}]} | tr "${JOIN_CHAR}" "\n"
- done
+ ! grep -E "${TRUSTED_REGEX}" <<<${key_data} > /dev/null
+}
+
+IsValid() # (key_data)
+{
+ local key_data="$1"
+
+ grep -Ev '(expired)|(revoked)' <<<${key_data} > /dev/null
+}
+
+
+## business ##
+
+ProcessHackerKey() # (hacker_data)
+{
+ ! [[ "$1" =~ ^${HACKER_KEY_REGEX}$ ]] && LOG "${HACKER_KEY_ERR_MSG}" && return 1
+
+ local login=${BASH_REMATCH[1]}
+ local key_id=${BASH_REMATCH[2]}
+
+DBG "\nProcessHackerKey() login=${login} key_id=${key_id}"
+
+ # fetch, parse, and cache key data
+ local gpg_keys_data="$( LoadKeyData ${login} ${key_id} "${GPG_LIST_OPTIONS}" )"
+ local pacman_keys_data="$(LoadKeyData ${login} ${key_id} "${PACMAN_LIST_OPTIONS}")"
+ local raw_keys_data="$( RawKeysData ${key_id} )"
+
+ # process key data
+ if [[ "${gpg_keys_data}" == "${pacman_keys_data}" ]]
+ then # process both master and subkey, if subkey in hackers.git
+# TODO: how to know if subkey in hackers.git
+ local keys_data="${pacman_keys_data}"
+ local key_data
+ local expiry
+ for key_data in ${keys_data}
+ do # load key data
+ if [[ "${key_data}" =~ ^${KEYDATA_EXPIRY_1_REGEX}$ ]] ||
+ [[ "${key_data}" =~ ^${KEYDATA_EXPIRY_2_REGEX}$ ]]
+ then login=${BASH_REMATCH[ 1]}
+ key_id=${BASH_REMATCH[2]}
+ expiry=${BASH_REMATCH[3]}
+ else login=''
+ key_id=''
+ expiry=''
+ LOG "ERROR: parsing key: ${key_id}"
+ fi
+
+[[ -n "${expiry}" ]] && DBG "ProcessHackerKey() keys_data=${keys_data}\nProcessHackerKey() key_id=${key_id}\nProcessHackerKey() expiry=${expiry}" || DBG "ProcessHackerKey() key_data=${key_data}"
+
+ [[ -n "${expiry}" ]] || continue
+
+ # process key data (mutually exclusive states)
+ ShouldWarn ${expiry} && WarningKeys[${login}]="${key_id} ${expiry}" && continue
+ IsRevoked "${keys_data}" && RevokedKeys[${login}]="${key_id} ${expiry}" && continue
+ IsExpired "${keys_data}" && ExpiredKeys[${login}]="${key_id} ${expiry}" && continue
+ IsUntrusted "${raw_keys_data}" && UntrustedKeys[${login}]="${key_id} ${expiry}" && continue
+ IsValid "${keys_data}" && ValidKeys[${login}]="${key_id} ${expiry}"
+ done
+ else OutdatedKeys[${login}]="${gpg_keys_data}\n${pacman_keys_data}"
+
+DBG "ProcessHackerKey() OutdatedKeys gpg_keys_data =${gpg_keys_data}\nProcessHackerKey() OutdatedKeys pacman_keys_data=${pacman_keys_data}"
+ fi
+}
+
+main()
+{
+ # collect results
+ echo -n "(${#HACKER_KEYS[@]}) keys to consider "
+ for hacker_key in ${HACKER_KEYS[@]}
+ do echo -n '.' && ProcessHackerKey ${hacker_key} || :
+ done ; echo ;
+
+ # display results
+ if (( ${#ValidKeys[@]} * SHOULD_SHOW_ALL ))
+ then echo -e "\n\n== ValidKeys ==\n"
+ for login in "${!ValidKeys[@]}" ; do echo "${login} ${ValidKeys[${login}]}" ; done ;
+ fi
+ if (( ${#OutdatedKeys[@]} ))
+ then echo -e "\n\n== OutdatedKeys ==\n"
+ for login in "${!OutdatedKeys[@]}" ; do echo -e "${OutdatedKeys[${login}]}" ; done ;
+ fi
+ if (( ${#WarningKeys[@]} ))
+ then echo -e "\n\n== WarningKeys ==\n"
+ for login in "${!WarningKeys[@]}" ; do echo "${login} ${WarningKeys[${login}]}" ; done ;
+ fi
+ if (( ${#RevokedKeys[@]} ))
+ then echo -e "\n\n== RevokedKeys ==\n"
+ for login in "${!RevokedKeys[@]}" ; do echo "${login} ${RevokedKeys[${login}]}" ; done ;
+ fi
+ if (( ${#ExpiredKeys[@]} ))
+ then echo -e "\n\n== ExpiredKeys ==\n"
+ for login in "${!ExpiredKeys[@]}" ; do echo "${login} ${ExpiredKeys[${login}]}" ; done ;
+ fi
+ if (( ${#UntrustedKeys[@]} ))
+ then echo -e "\n\n== UntrustedKeys ==\n"
+ for login in "${!UntrustedKeys[@]}" ; do echo "${login} ${UntrustedKeys[${login}]}" ; done ;
+ fi
+}
+
+
+## main entry ##
+
+set -e
+
+# re-run in chroot
+if [[ -z "${CHROOT}" ]]
+then main
+elif [[ -d "${CHROOT}" ]]
+then sudo cp ${BASH_SOURCE} ${CHROOT}/
+ sudo chroot ${CHROOT}/ ./$(basename ${BASH_SOURCE})
+else LOG "${CHROOT_ERR_MSG}"
fi