buildd-add-keys 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. #!/bin/bash
  2. # No way I try to deal with a crippled sh just for POSIX foo.
  3. # Copyright (C) 2011,2012 Joerg Jaspert <joerg@debian.org>
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License as
  7. # published by the Free Software Foundation; version 2.
  8. #
  9. # This program is distributed in the hope that it will be useful, but
  10. # WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. # General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  17. # exit on errors
  18. set -e
  19. # make sure to only use defined variables
  20. set -u
  21. # ERR traps should be inherited from functions too.
  22. set -E
  23. # import the general variable set.
  24. export SCRIPTVARS=/srv/ftp-master.debian.org/dak/config/debian/vars
  25. . $SCRIPTVARS
  26. umask 027
  27. # And use one locale, no matter what the caller has set
  28. export LANG=C
  29. export LC_ALL=C
  30. PROGRAM="buildd-add-keys"
  31. # common functions are "outsourced"
  32. . "${configdir}/common"
  33. function cleanup() {
  34. ERRVAL=$?
  35. trap - ERR EXIT TERM HUP INT QUIT
  36. for TEMPFILE in GPGSTATUS GPGLOGS GPGOUTF TEMPKEYDATA; do
  37. DELF=${!TEMPFILE:-""}
  38. if [ -n "${DELF}" ] && [ -f "${DELF}" ]; then
  39. rm -f "${DELF}"
  40. fi
  41. done
  42. exit $ERRVAL
  43. }
  44. base="${base}/scripts/builddkeyrings"
  45. INCOMING="${base}/incoming"
  46. ERRORS="${base}/errors"
  47. ADMINS="${base}/adminkeys.gpg"
  48. ARCHADMINS="${base}/archadminkeys"
  49. STAMPFILE="${base}/updatedkeyring"
  50. # Default options for our gpg calls
  51. DEFGPGOPT="--no-default-keyring --batch --no-tty --no-options --exit-on-status-write-error --no-greeting"
  52. if ! [ -d "${INCOMING}" ]; then
  53. log "Missing incoming dir, nothing to do"
  54. exit 1
  55. fi
  56. cd "${INCOMING}"
  57. KEYS=$(find . -maxdepth 1 -mindepth 1 -type f -name \*.key | sed -e "s,./,," | xargs)
  58. if [ -z "${KEYS}" ]; then
  59. exit 0
  60. fi
  61. trap cleanup ERR EXIT TERM HUP INT QUIT
  62. # Tell prepare-dir that there is an update and it can run
  63. touch "${STAMPFILE}"
  64. # Whenever something goes wrong, its put in there.
  65. mkdir -p "${ERRORS}"
  66. # We process all new files in our incoming directory
  67. for file in ${KEYS}; do
  68. file=${file##*/}
  69. # First we want to see if we recognize the filename. The buildd people have
  70. # to follow a certain schema:
  71. # architecture_builddname.YEAR-MONTH-DAY_HOURMINUTE.key
  72. if [[ $file =~ (.*)_(.*).([0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}[0-9]{2}).key ]]; then
  73. ARCH=${BASH_REMATCH[1]}
  74. BUILDD=${BASH_REMATCH[2]}
  75. # Right now timestamp is unused
  76. TIMESTAMP=${BASH_REMATCH[3]}
  77. else
  78. log "Unknown file ${file}, not processing"
  79. mv "${INCOMING}/${file}" "${ERRORS}/unknown.${file}.$(date -Is)"
  80. continue
  81. fi
  82. # Do we know the architecture?
  83. found=0
  84. for carch in ${archs}; do
  85. if [ "${ARCH}" == "${carch}" ]; then
  86. log "Known arch ${ARCH}, buildd ${BUILDD}"
  87. found=1
  88. break
  89. fi
  90. done
  91. if [ ${found} -eq 0 ]; then
  92. log "Unknown architecture ${ARCH}"
  93. mv "${INCOMING}/${file}" "${ERRORS}/unknownarch.${file}.$(date -Is)"
  94. continue
  95. fi
  96. # If we did have a file with this name already somethings wrong
  97. if [ -f "${base}/${ARCH}/${file}" ]; then
  98. log "Already processed this file"
  99. mv "${INCOMING}/${file}" "${ERRORS}/duplicate.${file}.$(date -Is)"
  100. continue
  101. fi
  102. # Where we want the status-fd from gpgv turn up
  103. GPGSTATUS=$(mktemp -p "${TMPDIR}" GPGSTATUS.XXXXXX)
  104. # Same for the loggger-fd
  105. GPGLOGS=$(mktemp -p "${TMPDIR}" GPGLOGS.XXXXXX)
  106. # And "decrypt" gives us output, the key without the pgp sig around it
  107. GPGOUTF=$(mktemp -p "${TMPDIR}" GPGOUTF.XXXXXX)
  108. # Open the filehandles, assigning them to the two files, so we can let gpg use them
  109. exec 4> "${GPGSTATUS}"
  110. exec 5> "${GPGLOGS}"
  111. KEYRINGS="--keyring ${ADMINS}"
  112. if [ -f "${ARCHADMINS}/${ARCH}.gpg" ]; then
  113. KEYRINGS="${KEYRINGS} --keyring ${ARCHADMINS}/${ARCH}.gpg"
  114. fi
  115. # So lets run gpg, status/logger into the two files, to "decrypt" the keyfile
  116. if ! gpg ${DEFGPGOPT} ${KEYRINGS} --status-fd 4 --logger-fd 5 --decrypt "${INCOMING}/${file}" > "${GPGOUTF}"; then
  117. ret=$?
  118. log "gpg returned with ${ret}, not adding key from file ${file}"
  119. DATE=$(date -Is)
  120. mv "${INCOMING}/${file}" "${ERRORS}/gpgerror.${file}.${DATE}"
  121. mv "${GPGSTATUS}" "${ERRORS}/gpgerror.${file}.gpgstatus.${DATE}"
  122. mv "${GPGLOGS}" "${ERRORS}/gpgerror.${file}.gpglogs.${DATE}"
  123. rm -f "${GPGOUTF}"
  124. continue
  125. fi # gpg broke
  126. # Read in the status output
  127. GPGSTAT=$(cat "${GPGSTATUS}")
  128. # And check if we like the sig. It has to be both, GOODISG and VALIDSIG or we don't accept it
  129. if [[ ${GPGSTAT} =~ "GOODSIG" ]] && [[ ${GPGSTAT} =~ "VALIDSIG" ]]; then
  130. log "Signature for ${file} accepted"
  131. else
  132. log "We are missing one of GOODSIG or VALIDSIG"
  133. DATE=$(date -Is)
  134. mv "${INCOMING}/${file}" "${ERRORS}/badsig.${file}.${DATE}"
  135. mv "${GPGSTATUS}" "${ERRORS}/badsig.${file}.gpgstatus.${DATE}"
  136. mv "${GPGLOGS}" "${ERRORS}/badsig.${file}.gpglogs.${DATE}"
  137. rm -f "${GPGOUTF}"
  138. continue
  139. fi
  140. # So at this point we know we accepted the signature of the file as valid,
  141. # that is it is from a key allowed for this architecture. Which only
  142. # leaves us with the task of checking if the key fulfills the requirements
  143. # before we add it to the architectures keyring.
  144. # Those currently are:
  145. # - keysize 4096 or larger
  146. # - RSA key, no encryption capability
  147. # - UID matching "buildd autosigning key BUILDDNAME <buildd_ARCH-BUILDDNAME@buildd.debian.org>
  148. # - expire within a 360 days
  149. # - maximum 2 keys per architecture and buildd
  150. TEMPKEYDATA=$(mktemp -p "${TMPDIR}" BDKEYS.XXXXXX)
  151. # We also need to ensure this works, otherwise manually mangled files can break us here
  152. if ! gpg ${DEFGPGOPT} --with-colons "${GPGOUTF}" > "${TEMPKEYDATA}"; then
  153. log "For some reason we could validate the sig but failed on getting key details"
  154. DATE=$(date -Is)
  155. mv "${INCOMING}/${file}" "${ERRORS}/badsig.${file}.${DATE}"
  156. mv "${GPGSTATUS}" "${ERRORS}/badsig.${file}.gpgstatus.${DATE}"
  157. mv "${GPGLOGS}" "${ERRORS}/badsig.${file}.gpglogs.${DATE}"
  158. rm -f "${GPGOUTF}"
  159. rm -f "${TMPKEYDATA}"
  160. continue
  161. fi
  162. # Read in the TEMPKEYDATAFILE, but avoid using a subshell like a
  163. # while read line otherwise would do
  164. exec 4<> "${TEMPKEYDATA}"
  165. KEYUID=""
  166. #pub:-:4096:1:FAB983612A6554FA:2011-03-24:2011-07-22::-:buildd autosigning key poulenc <buildd_powerpc-poulenc@buildd.debian.org>:
  167. # Of course this sucky gpg crapshit of an "interface" does give you different things depending on how people
  168. # created their keys. And of course the buildd people created the test keys differently to what they now do
  169. # which just means extra work for nothing. So as they now do other steps, the thing we get back suddenly looks like
  170. #pub:-:4096:1:99595DC7865BEAD2:2011-03-26:2011-07-24::-:
  171. #uid:::::::::buildd autosigning key corelli <buildd_mips-corelli@buildd.debian.org>:
  172. # Besides fiddling out the data we need to check later, this regex also check:
  173. # - the keytype (:1:, 1 there means RSA)
  174. # - the UID
  175. # - that the key does have an expiration date (or it wont match, the second date
  176. # field would be empty
  177. regex="^pub:-:([0-9]{4}):1:([0-9A-F]{16}):([0-9]{4}-[0-9]{2}-[0-9]{2}):([0-9]{4}-[0-9]{2}-[0-9]{2})::-:(buildd autosigning key ${BUILDD} <buildd_${ARCH}-${BUILDD}@buildd.debian.org>):$"
  178. regex2="^pub:-:([0-9]{4}):1:([0-9A-F]{16}):([0-9]{4}-[0-9]{2}-[0-9]{2}):([0-9]{4}-[0-9]{2}-[0-9]{2})::-:$"
  179. regex3="^uid:::::::::(buildd autosigning key ${BUILDD} <buildd_${ARCH}-${BUILDD}@buildd.debian.org>):$"
  180. while read line <&4; do
  181. if [[ $line =~ $regex ]]; then
  182. KEYSIZE=${BASH_REMATCH[1]}
  183. KEYID=${BASH_REMATCH[2]}
  184. KEYCREATE=${BASH_REMATCH[3]}
  185. KEYEXPIRE=${BASH_REMATCH[4]}
  186. KEYUID=${BASH_REMATCH[5]}
  187. elif [[ $line =~ $regex2 ]]; then
  188. KEYSIZE=${BASH_REMATCH[1]}
  189. KEYID=${BASH_REMATCH[2]}
  190. KEYCREATE=${BASH_REMATCH[3]}
  191. KEYEXPIRE=${BASH_REMATCH[4]}
  192. elif [[ $line =~ $regex3 ]]; then
  193. KEYUID=${BASH_REMATCH[1]}
  194. else
  195. log "Didn't recognize the key. Go kiss gpg"
  196. DATE=$(date -Is)
  197. mv "${INCOMING}/${file}" "${ERRORS}/badkey.${file}.${DATE}"
  198. mv "${GPGSTATUS}" "${ERRORS}/badkey.${file}.gpgstatus.${DATE}"
  199. mv "${GPGLOGS}" "${ERRORS}/badkey.${file}.gpglogs.${DATE}"
  200. rm -f "${GPGOUTF}"
  201. continue
  202. fi
  203. done
  204. if [ -z "${KEYUID}" ]; then
  205. log "Did not recognize the UID format"
  206. DATE=$(date -Is)
  207. mv "${INCOMING}/${file}" "${ERRORS}/keyuid.${file}.${DATE}"
  208. mv "${GPGSTATUS}" "${ERRORS}/keyuid.${file}.gpgstatus.${DATE}"
  209. mv "${GPGLOGS}" "${ERRORS}/keyuid.${file}.gpglogs.${DATE}"
  210. rm -f "${GPGOUTF}"
  211. continue
  212. fi
  213. # We do want 4096 or anything above
  214. if [ ${KEYSIZE} -lt 4096 ]; then
  215. log "Keysize ${KEYSIZE} too small"
  216. DATE=$(date -Is)
  217. mv "${INCOMING}/${file}" "${ERRORS}/keysize.${file}.${DATE}"
  218. mv "${GPGSTATUS}" "${ERRORS}/keysize.${file}.gpgstatus.${DATE}"
  219. mv "${GPGLOGS}" "${ERRORS}/keysize.${file}.gpglogs.${DATE}"
  220. rm -f "${GPGOUTF}"
  221. continue
  222. fi
  223. # We want a maximum lifetime of 365 days, so check that.
  224. # Easiest to compare in epoch, so lets see, 365 days midnight from now,
  225. # compared with their set expiration date at midnight
  226. # maxdate should turn out higher. just in case we make it 366 for this check
  227. maxdate=$(date -d '366 day 00:00:00' +%s)
  228. theirexpire=$(date -d "${KEYEXPIRE} 00:00:00" +%s)
  229. if [ ${theirexpire} -gt ${maxdate} ]; then
  230. log "Key expiry ${KEYEXPIRE} wrong"
  231. DATE=$(date -Is)
  232. mv "${INCOMING}/${file}" "${ERRORS}/keyexpire.${file}.${DATE}"
  233. mv "${GPGSTATUS}" "${ERRORS}/keyexpire.${file}.gpgstatus.${DATE}"
  234. mv "${GPGLOGS}" "${ERRORS}/keyexpire.${file}.gpglogs.${DATE}"
  235. rm -f "${GPGOUTF}"
  236. continue
  237. fi
  238. # And now lets check how many keys this buildd already has. 2 is the maximum, so key
  239. # rollover works. 3 won't, they have to rm one first
  240. # We need to check for the amount of keys
  241. ARCHKEYRING="${base}/${ARCH}/keyring.gpg"
  242. KEYNO=$(gpg ${DEFGPGOPT} --keyring "${ARCHKEYRING}" --with-colons --list-keys "buildd_${ARCH}-${BUILDD}@buildd.debian.org" 2>/dev/null | grep -c '^pub:' || /bin/true )
  243. if [ ${KEYNO} -gt 2 ]; then
  244. log "Too many keys for ${ARCH} buildd ${BUILDD}"
  245. DATE=$(date -Is)
  246. mv "${INCOMING}/${file}" "${ERRORS}/toomany.${file}.${DATE}"
  247. mv "${GPGSTATUS}" "${ERRORS}/toomany.${file}.gpgstatus.${DATE}"
  248. mv "${GPGLOGS}" "${ERRORS}/toomany.${file}.gpglogs.${DATE}"
  249. rm -f "${GPGOUTF}"
  250. continue
  251. fi
  252. # Right. At this point everything should be in order, which means we should put the key into
  253. # the keyring
  254. KEYSUBMITTER=$(cat "${GPGSTATUS}"|grep GOODSIG)
  255. KEYSUBMITTER=${KEYSUBMITTER##*GOODSIG}
  256. log "${KEYSUBMITTER} added key ${KEYID} for ${ARCH} buildd ${BUILDD}, expire ${KEYEXPIRE}"
  257. gpg ${DEFGPGOPT} --status-fd 4 --logger-fd 5 --keyring "${ARCHKEYRING}" --import "${GPGOUTF}" 2>/dev/null
  258. mv "${INCOMING}/${file}" "${base}/${ARCH}"
  259. done