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