123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- #! /bin/bash
- #
- # usage: generate-archive-key <configuration> <output-directory>
- #
- # generate a new archive key
- #
- # Required packages:
- # gnupg libgfshare-bin pinentry-tty
- set -e
- set -u
- set -o pipefail
- conf=${1:-""}
- output=${2:-""}
- err=0
- for package in gnupg libgfshare-bin pinentry-curses; do
- if ! dpkg -l ${package} >/dev/null 2>&1; then
- echo "Missing package ${package}"
- err=1
- fi
- done
- if [[ ${err} -ne 0 ]]; then
- exit 8
- fi
- if ! [ -w "$(tty)" ]; then
- echo "E: No access to tty; required for passphrase input"
- exit 1
- fi
- # designated revokers
- revokers=(
- 80E976F14A508A48E9CA3FE9BC372252CA1CF964 # Ansgar Burchardt <ansgar@debian.org>
- FBFABDB541B5DC955BD9BA6EDB16CF5BB12525C4 # Joerg Jaspert <joerg@debian.org>
- 8C823DED10AA8041639E12105ACE8D6E0C14A470 # Luke Faraone <lfaraone@debian.org>
- 309911BEA966D0613053045711B4E5FF15B0FD82 # Mark Hymers <mhy@debian.org>
- C74F6AC9E933B3067F52F33FA459EC6715B0705F # Thorsten Alteholz <alteholz@debian.org>
- )
- # holders of revocation shares and number of required shares
- revocation_holders=(
- )
- revocation_shares=0
- # holders of backup shares and number of required shares
- backup_holders=(
- ${revokers[@]}
- )
- backup_shares=3
- keyring=/usr/share/keyrings/debian-keyring.gpg
- if [[ -f /srv/keyring.debian.org/keyrings/debian-keyring.gpg ]]; then
- keyring=/srv/keyring.debian.org/keyrings/debian-keyring.gpg
- fi
- if [[ -z ${conf} ]] || [[ ! -e ${conf} ]]; then
- echo "Configuration file '${conf}' does not exist" >&2
- exit 1
- fi
- if [[ -z ${output} ]] || [[ -e ${output} ]]; then
- echo "No output directory given - or given directory ${output} exists already" >&2
- exit 2
- fi
- . ${conf}
- for option in \
- revokers \
- revocation_shares \
- backup_shares \
- name_real name_email \
- ; do
- if [[ ! -v ${option} ]]; then
- echo "Option '${option}' is not set" >&2
- exit 3
- fi
- done
- umask 077
- show-file() {
- echo "/---[ ${1} ]"
- cat -- "${1}"
- echo "\---[ ${1} ]"
- }
- split-into-encrypted-shares() {
- local input="${1}"
- local output="${2}"
- local -n holders="${3}"
- local shares="${4}"
- local description="${5:-}"
- gfsplit -n ${shares} -m ${#holders[@]} ${input}
- local i=0
- for share in ${input}.*; do
- local holder=${holders[$i]}
- i=$((i + 1))
- {
- echo "${description}"
- echo "Share name: ${share}"
- echo
- echo "To recombine:"
- echo " - Store this share as ${share}.asc"
- echo " - Run: apt install libgfshare-bin"
- echo " - Run:"
- echo " for f in ${input}.???.asc; do gpg < \$f > \${f%.asc}; done"
- echo " gfcombine ${input}.???"
- echo
- gpg --armor --store < ${share}
- } |
- gpg --armor --encrypt --trust-model=always --keyring ${keyring} -r ${holder} > ${output}.${holder}
- done
- }
- if [[ ${#revocation_holders[@]} -lt ${revocation_shares} ]]; then
- echo "There are fewer revocation share holders than the number of required shares" >&2
- exit 4
- fi
- if [[ ${#backup_holders[@]} -lt ${backup_shares} ]]; then
- echo "There are fewer backup share holders than the number of required shares" >&2
- exit 5
- fi
- gpghome=$(mktemp -d)
- export GNUPGHOME="${gpghome}"
- trap 'rm -rf -- "${gpghome:?}"' EXIT
- if [[ $(stat --file-system -c "%T" -- "${gpghome}") != tmpfs ]]; then
- echo "This script refuses to work with temporary files not on a tmpfs!" >&2
- exit 6
- fi
- pushd "${gpghome}"
- cat > gpg.conf <<EOF
- personal-digest-preferences SHA512
- cert-digest-algo SHA512
- default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
- EOF
- cat > gpg-agent.conf <<EOF
- pinentry-program /usr/bin/pinentry-curses
- EOF
- cat > generate-key.conf <<EOF
- Key-Type: RSA
- Key-Length: 4096
- Key-Usage: sign
- Subkey-Type: RSA
- Subkey-Length: 4096
- Subkey-Usage: sign
- Name-Real: ${name_real:?}
- Name-Email: ${name_email:?}
- Name-Comment: ${name_comment:-}
- Expire-Date: 8y
- EOF
- show-file generate-key.conf
- # The exported secret key shares must be without a passphrase.
- # So we only set the passphrase at the end.
- gpg --batch --pinentry-mode loopback --passphrase "" --full-generate-key generate-key.conf
- key=$(gpg --with-colon --list-secret-keys | awk -F: '$1 == "fpr" { print $10; exit 0; }')
- key_uid=$(gpg --with-colon --list-secret-keys | awk -F: '$1 == "uid" { print $10; exit 0; }')
- if [[ ${#key} -ne 40 ]]; then
- echo "Unexpected length of key id: ${#key} (expected: 40)" >&2
- exit 7
- fi
- echo "Secret key is ${key} (${key_uid})"
- if [[ ${#revokers[@]} -gt 0 ]]; then
- {
- for revoker in ${revokers[@]}; do
- echo addrevoker
- echo ${revoker}
- echo y
- done
- echo save
- echo quit
- } > edit-key
- show-file edit-key
- gpg --command-file=edit-key --status-fd=2 --batch --keyring ${keyring} --edit-key ${key}
- fi
- cp openpgp-revocs.d/${key}.rev revoke-${key}
- if [[ ${#revocation_holders[@]} -gt 0 ]]; then
- description="This is a share of the REVOCATION CERTIFICATE for
- the key: ${key}
- uid: ${key_uid}
- "
- split-into-encrypted-shares revoke-${key} revoke-${key}-share revocation_holders ${revocation_shares} "${description}"
- fi
- if [[ ${#backup_holders[@]} -gt 0 ]]; then
- gpg --export-secret-key ${key} > backup-${key}
- description="This is a share of the PRIVATE KEY for
- the key: ${key}
- uid: ${key_uid}
- "
- split-into-encrypted-shares backup-${key} backup-${key}-share backup_holders ${backup_shares} "${description}"
- rm -f -- backup-${key} backup-${key}.???
- fi
- gpg --change-passphrase ${key}
- gpg -a --export ${key} > public-${key}.asc
- gpg -a --export-secret-key ${key} > private-key-${key}.asc
- gpg -a --export-secret-subkeys ${key} > private-subkey-${key}.asc
- popd
- mkdir -- ${output}
- cp -t ${output} -- ${gpghome}/public-${key}.asc ${gpghome}/private-key-${key}.asc ${gpghome}/private-subkey-${key}.asc ${gpghome}/revoke-${key}
- if [[ ${#revocation_holders[@]} -gt 0 ]]; then
- cp -t ${output} -- ${gpghome}/revoke-${key}-share.*
- fi
- if [[ ${#backup_holders[@]} -gt 0 ]]; then
- cp -t ${output} -- ${gpghome}/backup-${key}-share.*
- fi
- echo "All done."
|