generate-archive-key 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. #! /bin/bash
  2. #
  3. # usage: generate-archive-key <configuration> <output-directory>
  4. #
  5. # generate a new archive key
  6. #
  7. # Required packages:
  8. # gnupg libgfshare-bin pinentry-tty
  9. set -e
  10. set -u
  11. set -o pipefail
  12. conf=${1:-""}
  13. output=${2:-""}
  14. err=0
  15. for package in gnupg libgfshare-bin pinentry-tty; do
  16. if ! dpkg -l ${package} >/dev/null 2>&1; then
  17. echo "Missing package ${package}"
  18. err=1
  19. fi
  20. done
  21. if [[ ${err} -ne 0 ]]; then
  22. exit 8
  23. fi
  24. # designated revokers
  25. revokers=(
  26. 80E976F14A508A48E9CA3FE9BC372252CA1CF964 # Ansgar Burchardt <ansgar@debian.org>
  27. FBFABDB541B5DC955BD9BA6EDB16CF5BB12525C4 # Joerg Jaspert <joerg@debian.org>
  28. 309911BEA966D0613053045711B4E5FF15B0FD82 # Mark Hymers <mhy@debian.org>
  29. )
  30. # holders of revocation shares and number of required shares
  31. revocation_holders=(
  32. )
  33. revocation_shares=0
  34. # holders of backup shares and number of required shares
  35. backup_holders=(
  36. ${revokers[@]}
  37. AB41C1C68AFD668CA045EBF8673A03E4C1DB921F # Gunnar Eyal Wolf Iszaevich <gwolf@debian.org>
  38. 7B27A3F1A6E18CD9588B4AE8310180050905E40C # Julien Cristau <jcristau@debian.org>
  39. )
  40. backup_shares=3
  41. keyring=/usr/share/keyrings/debian-keyring.gpg
  42. if [[ -f /srv/keyring.debian.org/keyrings/debian-keyring.gpg ]]; then
  43. keyring=/srv/keyring.debian.org/keyrings/debian-keyring.gpg
  44. fi
  45. if [[ -z ${conf} ]] || [[ ! -e ${conf} ]]; then
  46. echo "Configuration file '${conf}' does not exist" >&2
  47. exit 1
  48. fi
  49. if [[ -z ${output} ]] || [[ -e ${output} ]]; then
  50. echo "No output directory given - or given directory ${output} exists already" >&2
  51. exit 2
  52. fi
  53. . ${conf}
  54. for option in \
  55. revokers \
  56. revocation_shares \
  57. backup_shares \
  58. name_real name_email \
  59. ; do
  60. if [[ ! -v ${option} ]]; then
  61. echo "Option '${option}' is not set" >&2
  62. exit 3
  63. fi
  64. done
  65. umask 077
  66. show-file() {
  67. echo "/---[ ${1} ]"
  68. cat -- "${1}"
  69. echo "\---[ ${1} ]"
  70. }
  71. split-into-encrypted-shares() {
  72. local input="${1}"
  73. local output="${2}"
  74. local -n holders="${3}"
  75. local shares="${4}"
  76. local description="${5:-}"
  77. gfsplit -n ${shares} -m ${#holders[@]} ${input}
  78. local i=0
  79. for share in ${input}.*; do
  80. local holder=${holders[$i]}
  81. i=$((i + 1))
  82. {
  83. echo "${description}"
  84. echo "Share name: ${share}"
  85. echo
  86. echo "To recombine:"
  87. echo " - Store this share as ${share}.asc"
  88. echo " - Run: apt install libgfshare-bin"
  89. echo " - Run:"
  90. echo " for f in ${input}.???.asc; do gpg < \$f > \${f%.asc}; done"
  91. echo " gfcombine ${input}.???"
  92. echo
  93. gpg --armor --store < ${share}
  94. } |
  95. gpg --armor --encrypt --trust-model=always --keyring ${keyring} -r ${holder} > ${output}.${holder}
  96. done
  97. }
  98. if [[ ${#revocation_holders[@]} -lt ${revocation_shares} ]]; then
  99. echo "There are fewer revocation share holders than the number of required shares" >&2
  100. exit 4
  101. fi
  102. if [[ ${#backup_holders[@]} -lt ${backup_shares} ]]; then
  103. echo "There are fewer backup share holders than the number of required shares" >&2
  104. exit 5
  105. fi
  106. gpghome=$(mktemp -d)
  107. export GNUPGHOME="${gpghome}"
  108. trap 'rm -rf -- "${gpghome:?}"' EXIT
  109. if [[ $(stat --file-system -c "%T" -- "${gpghome}") != tmpfs ]]; then
  110. echo "This script refuses to work with temporary files not on a tmpfs!" >&2
  111. exit 6
  112. fi
  113. pushd "${gpghome}"
  114. cat > gpg.conf <<EOF
  115. personal-digest-preferences SHA512
  116. cert-digest-algo SHA512
  117. default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
  118. EOF
  119. cat > gpg-agent.conf <<EOF
  120. pinentry-program /usr/bin/pinentry-tty
  121. EOF
  122. cat > generate-key.conf <<EOF
  123. Key-Type: RSA
  124. Key-Length: 4096
  125. Key-Usage: sign
  126. Subkey-Type: RSA
  127. Subkey-Length: 4096
  128. Subkey-Usage: sign
  129. Name-Real: ${name_real:?}
  130. Name-Email: ${name_email:?}
  131. Name-Comment: ${name_comment:-}
  132. Expire-Date: 8y
  133. EOF
  134. show-file generate-key.conf
  135. # The exported secret key shares must be without a passphrase.
  136. # So we only set the passphrase at the end.
  137. gpg --batch --pinentry-mode loopback --passphrase "" --full-generate-key generate-key.conf
  138. key=$(gpg --with-colon --list-secret-keys | awk -F: '$1 == "fpr" { print $10; exit 0; }')
  139. key_uid=$(gpg --with-colon --list-secret-keys | awk -F: '$1 == "uid" { print $10; exit 0; }')
  140. if [[ ${#key} -ne 40 ]]; then
  141. echo "Unexpected length of key id: ${#key} (expected: 40)" >&2
  142. exit 7
  143. fi
  144. echo "Secret key is ${key} (${key_uid})"
  145. if [[ ${#revokers[@]} -gt 0 ]]; then
  146. {
  147. for revoker in ${revokers[@]}; do
  148. echo addrevoker
  149. echo ${revoker}
  150. echo y
  151. done
  152. echo save
  153. echo quit
  154. } > edit-key
  155. show-file edit-key
  156. gpg --command-file=edit-key --status-fd=2 --batch --keyring ${keyring} --edit-key ${key}
  157. fi
  158. cp openpgp-revocs.d/${key}.rev revoke-${key}
  159. if [[ ${#revocation_holders[@]} -gt 0 ]]; then
  160. description="This is a share of the REVOCATION CERTIFICATE for
  161. the key: ${key}
  162. uid: ${key_uid}
  163. "
  164. split-into-encrypted-shares revoke-${key} revoke-${key}-share revocation_holders ${revocation_shares} "${description}"
  165. fi
  166. if [[ ${#backup_holders[@]} -gt 0 ]]; then
  167. gpg --export-secret-key ${key} > backup-${key}
  168. description="This is a share of the PRIVATE KEY for
  169. the key: ${key}
  170. uid: ${key_uid}
  171. "
  172. split-into-encrypted-shares backup-${key} backup-${key}-share backup_holders ${backup_shares} "${description}"
  173. rm -f -- backup-${key} backup-${key}.???
  174. fi
  175. gpg --change-passphrase ${key}
  176. gpg -a --export ${key} > public-${key}.asc
  177. gpg -a --export-secret-key ${key} > private-key-${key}.asc
  178. gpg -a --export-secret-subkeys ${key} > private-subkey-${key}.asc
  179. popd
  180. mkdir -- ${output}
  181. cp -t ${output} -- ${gpghome}/public-${key}.asc ${gpghome}/private-key-${key}.asc ${gpghome}/private-subkey-${key}.asc ${gpghome}/revoke-${key}
  182. if [[ ${#revocation_holders[@]} -gt 0 ]]; then
  183. cp -t ${output} -- ${gpghome}/revoke-${key}-share.*
  184. fi
  185. if [[ ${#backup_holders[@]} -gt 0 ]]; then
  186. cp -t ${output} -- ${gpghome}/backup-${key}-share.*
  187. fi