generate-archive-key 6.1 KB

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