forked from openSUSE/pesign-obs-integration
-
Notifications
You must be signed in to change notification settings - Fork 0
/
dh_signobs
executable file
·336 lines (306 loc) · 15.3 KB
/
dh_signobs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0+
# Copyright: 2018 Luca Boccassi <bluca@debian.org>
set -e
# Build-Depend on dh-signobs.
# Enable by using dh --with=signobs.
# If the certificate is necessary at build time, for example to include in the kernel or shim,
# annotate the Source stanza of debian/control with:
# XS-Obs: needssslcertforbuild
# Then it can be fetched in PEM format with:
# dh_signobs_getcert DESTINATION
# Append DER to retrieve it in binary format instead.
#
# This debhelper will run in 2 steps - first after the dh_install phase, when the source
# package will have built and installed the unsigned binaries. The second step during the
# followup build after OBS has signed the binaries, before dh_auto_install runs.
#
# During the first step dh_signobs_pack will be called to get the hashes (or kernel modules)
# and pack them into a cpio archive in ../OTHER that OBS can use. At the same time a new
# source package for the OBS followup build will be generated, either from a source-template
# that can be specified by the package being signed or automatically generated by this debhelper.
#
# The source template can be in any form it wants, as long as it is placed in a directory
# called "source-template" in the debian/ directory (can be a subdirectory, and it can be
# static or generated). Also a files.json list of the binaries to sign must be placed in the
# same PARENT directory with the following format:
# {"unsigned-binary-package": {
# "files": [
# {"sig_type": "efi", "file": "usr/lib/foo/bar.efi"},
# {"sig_type": "linux-module", "file": "lib/modules/1.2.3/baz.ko"}
# ]
# }}
# The advantage of the templated build is that it can sign only a subset of files - for
# example, a kernel build with CONFIG_MODULE_SIG_ALL=y does not need to sign the modules and
# can save a lot of time by just signing the vmlinuz.
#
# The autogenerated package will simply look for any .efi, .ko or vmlinuz- file in the debian
# directory (excluding debian/tmp) and considers the first subdirectory to be the package name
# (IOW: it covers the common case where built files are first installed in debian/tmp/path and
# then moved by dh_install into debian/pkg/path), and then generates a new source package that
# unpacks the packages that contain them and repacks them after signing the binaries.
# The files.json can be used also with the autogenerated package, simply to restrict the set
# of files that get signed, or in case there is an uncommon build directory structure.
#
# During the second step dh_signobs_unpack will be called and it will unpack the signed
# binaries that OBS places in the cpio archive in ../SOURCES and it will copy them to
# debian/tmp/ respecting the subdirectories path they where picked from (eg: debian/tmp/boot/vmlinuz).
#
# Following common conventions, if an EFI binary has an .efi suffix, a .signed suffix will be
# appended. Kernel modules and images will not be renamed.
if [ "$(basename "$0")" = "dh_signobs_pack" ] && [ -d ../OTHER ] && ! ls ../SOURCES/*cpio.rsasign.sig &> /dev/null
then
# remove any dsc and other source artefacts, obs-build might place them there
rm -rf ../OTHER/*
# if a template is provided, create the directory tree and copy the sources
# otherwise generate it on-the-fly
SOURCE_TEMPLATE="$(find debian -type d -name source-template)"
if [ -d "$SOURCE_TEMPLATE/debian" ]
then
SOURCE_PKG=$(cd "$SOURCE_TEMPLATE"; dpkg-parsechangelog -SSource)
mkdir -p "../OTHER/$SOURCE_PKG/debian/signatures"
cp -r "$SOURCE_TEMPLATE"/debian/* "../OTHER/$SOURCE_PKG/debian/"
# inject dh_signobs if not present
if ! grep -qs signobs "../OTHER/$SOURCE_PKG/debian/rules"
then
sed -i "s/dh \$@/dh \$@ --with signobs/" "../OTHER/$SOURCE_PKG/debian/rules"
fi
else
SOURCE_PKG="$(dpkg-parsechangelog -S Source)-signed"
distribution="$(dpkg-parsechangelog -S Distribution)"
urgency="$(dpkg-parsechangelog -S Urgency)"
# lintian will complain that the date is the same, so bump by 1 second,
# we want to avoid build time dates to keep the build reproducible
# requires dpkg-dev >= 1.18.8 for "-S Timestamp"
date="$(date --rfc-2822 --date=@$(($(dpkg-parsechangelog -S Timestamp) + 1)))"
version_binary="$(dpkg-parsechangelog -S Version)"
# make the source package native by removing the "-" separator
version_mangled="$(dpkg-parsechangelog -S Version | tr '-' '+')+signed"
mkdir -p "../OTHER/$SOURCE_PKG/debian/signatures"
mkdir -p "../OTHER/$SOURCE_PKG/debian/source"
echo "3.0 (native)" > "../OTHER/$SOURCE_PKG/debian/source/format"
# lintian will complain that there is no source, but it's in the previous build
echo "$SOURCE_PKG: source-is-missing" > "../OTHER/$SOURCE_PKG/debian/source/lintian-overrides"
echo "9" > "../OTHER/$SOURCE_PKG/debian/compat"
cp debian/copyright "../OTHER/$SOURCE_PKG/debian/"
# copy the changelog and add a new entry, changing the version to native
cp debian/changelog "../OTHER/$SOURCE_PKG/debian/"
sed -i "1i\\ " "../OTHER/$SOURCE_PKG/debian/changelog"
sed -i "1i\\ -- OBS signing service <obssign@obs.service> $date" "../OTHER/$SOURCE_PKG/debian/changelog"
sed -i "1i\\ " "../OTHER/$SOURCE_PKG/debian/changelog"
sed -i "1i\\ * Sign version $version_binary" "../OTHER/$SOURCE_PKG/debian/changelog"
sed -i "1i\\ " "../OTHER/$SOURCE_PKG/debian/changelog"
sed -i "1i\\$SOURCE_PKG ($version_mangled) $distribution; urgency=$urgency" \
"../OTHER/$SOURCE_PKG/debian/changelog"
# the dummy binary package is necessary otherwise dpkg-source will error out,
# it will be removed during the followup build
cat > "../OTHER/$SOURCE_PKG/debian/control" << EOF
Source: $SOURCE_PKG
Section: admin
Priority: optional
Maintainer: OBS signing service <obssign@obs.service>
Standards-Version: 3.9.8
Build-Depends: debhelper (>= 9~), dh-signobs
Package: $SOURCE_PKG-dummy
Architecture: all
EOF
# the dh_auto_install override is there so that more actions
# can be appended later after the install phase, when
# the signatures get unpacked and reattached to the binaries
# in order to generate the binary packages and remove the dummy
# only when doing a binary build, rather than a source build,
# per-signed-binary-package rules will be dynamically added later
# so place a marker for easier substitution
#
# the changelog and copyright files will be copied from the unsigned package
cat > "../OTHER/$SOURCE_PKG/debian/rules" << EOF
#!/usr/bin/make -f
SHELL := bash -e
%:
if [ -d ../SOURCES ] && ls ../SOURCES/*cpio.rsasign.sig &> /dev/null && grep -qs $SOURCE_PKG-dummy debian/control; then \\
sed -i "/Package:/d" debian/control; \\
sed -i "/Architecture:/d" debian/control; \\
fi #CONTROLMARKER
dh \$@ --with signobs
override_dh_gencontrol:
#GENCONTROLMARKER
override_dh_installchangelogs:
override_dh_installdocs:
override_dh_auto_install:
dh_auto_install
EOF
chmod +x "../OTHER/$SOURCE_PKG/debian/rules"
fi
# pesign wants an empty certutil DB - use sql as legacy mode fails often
nss_db="$PWD/nss-db"
# certutil will fail if it's called twice
rm -rf "$nss_db"
mkdir -p "$nss_db" hashes
certutil -N -d sql:"$nss_db" --empty-password
# get hashes via pesign from kernel image and EFI binaries
# store into directories named after the packages, and OBS will respect
# the directory structure when we unpack after the signing
declare -a UNSIGNED
# if the source describes what files to sign, parse it, otherwise do everything
JSON="$(find debian -type f -name files.json)"
if [ -f "$JSON" ]
then
for PKG in $(jq --raw-output 'to_entries[]? | .key' < "$JSON")
do
for f in $(jq --raw-output ".\"$PKG\".files[]? | .file" < "$JSON")
do
UNSIGNED+=("$PKG/$f")
done
done
else
# shellcheck disable=SC2207
UNSIGNED=( $(find debian -path debian/tmp -prune -o -type f \( -name "*.efi" -o -name "vmlinuz-*" -o -name "*.ko" \) -printf '%P\n') )
fi
for f in "${UNSIGNED[@]}"
do
mkdir -p "hashes/$(dirname "$f")"
if [[ $(basename "$f") = *.ko ]]
then
# kernel modules have to be copied wholesale, cannot get just the hash
cp "debian/$f" "hashes/$f"
else
pesign --force -n sql:"$nss_db" -i "debian/$f" -E "hashes/$f"
fi
# copy the unsigned binaries to be able to re-attach signatures later
mkdir -p "../OTHER/$SOURCE_PKG/debian/signatures/$(dirname "$f")"
cp "debian/$f" "../OTHER/$SOURCE_PKG/debian/signatures/$f"
# if there is no template, generate the package-specific parts of the signed source
if [ ! -d "$SOURCE_TEMPLATE/debian" ]
then
# generate the new package metadata, but only once, as multiple binaries
# might be in one package
if ! grep -qs "${f%%/*}" "../OTHER/$SOURCE_PKG/debian/rules"
then
# to ensure the signed package is identical to the unsigned one,
# simply extract the content of the unsigned one, the signed
# binaries will simply overwrite the unsigned ones
echo " dpkg -x ../SOURCES/${f%%/*}_*.deb debian/${f%%/*}" >> "../OTHER/$SOURCE_PKG/debian/rules"
echo " dpkg -e ../SOURCES/${f%%/*}_*.deb debian/${f%%/*}/DEBIAN" >> ."./OTHER/$SOURCE_PKG/debian/rules"
echo " for script in debian/${f%%/*}/DEBIAN/*; do mv \$\$script debian/${f%%/*}.\$\${script##*/}; done" >> ."./OTHER/$SOURCE_PKG/debian/rules"
# then delete the unsigned package, which the unpack step will copy
# over
echo " rm -f ../DEBS/${f%%/*}_*.deb" >> "../OTHER/$SOURCE_PKG/debian/rules"
# generate rules to extract unsigned package control files
sed -i "s|fi #CONTROLMARKER|mkdir -p debian/${f%%/*}/DEBIAN; dpkg -e ../SOURCES/${f%%/*}_*.deb debian/${f%%/*}/DEBIAN; \\\\\\n\\tfi #CONTROLMARKER|" "../OTHER/$SOURCE_PKG/debian/rules"
# save version for gencontrol - pkg/DEBIAN will be cleaned up in-between
sed -i "s@fi #CONTROLMARKER@grep 'Version:' debian/${f%%/*}/DEBIAN/control | sed 's/Version:\\\\s*//' > debian/${f%%/*}.version; \\\\\\n\\tfi #CONTROLMARKER@" "../OTHER/$SOURCE_PKG/debian/rules"
# generate rule to prune unwanted metadata from unsigned control file
sed -i "s|fi #CONTROLMARKER|sed -i '/Source/d' debian/${f%%/*}/DEBIAN/control; \\\\\\n\\tfi #CONTROLMARKER|" "../OTHER/$SOURCE_PKG/debian/rules"
sed -i "s|fi #CONTROLMARKER|sed -i '/Version/d' debian/${f%%/*}/DEBIAN/control; \\\\\\n\\tfi #CONTROLMARKER|" "../OTHER/$SOURCE_PKG/debian/rules"
sed -i "s|fi #CONTROLMARKER|sed -i '/Maintainer/d' debian/${f%%/*}/DEBIAN/control; \\\\\\n\\tfi #CONTROLMARKER|" "../OTHER/$SOURCE_PKG/debian/rules"
sed -i "s|fi #CONTROLMARKER|sed -i '/Installed-Size/d' debian/${f%%/*}/DEBIAN/control; \\\\\\n\\tfi #CONTROLMARKER|" "../OTHER/$SOURCE_PKG/debian/rules"
# generate rule to create signed package metadata from the unsigned one
sed -i "s|fi #CONTROLMARKER|cat debian/control debian/${f%%/*}/DEBIAN/control > debian/control.tmp; mv debian/control.tmp debian/control; echo \"\" >> debian/control; \\\\\\n\\tfi #CONTROLMARKER|" "../OTHER/$SOURCE_PKG/debian/rules"
# generate rule set the version from the unsigned one
sed -i "s|#GENCONTROLMARKER|dh_gencontrol -p ${f%%/*} -- -v\$(shell cat debian/${f%%/*}.version); rm -f debian/${f%%/*}.version\\n\\t#GENCONTROLMARKER|" "../OTHER/$SOURCE_PKG/debian/rules"
fi
# special case: the conffiles lists the file in /etc, but it does
# get regenerated and it does not prune duplicates, triggering a
# Lintian error, so remove the old ones
echo " rm -f debian/*.conffiles" >> "../OTHER/$SOURCE_PKG/debian/rules"
# finally copy the signed binary in the package build dir
# note that by convention bootloaders are renamed to .signed but
# kernel modules and images are not
if [[ $(basename "$f") = *.efi ]]
then
# unsigned bootloaders are installed by the unsigned package
# so there must not be a conflict on the filesystem
echo " rm -f debian/$f" >> "../OTHER/$SOURCE_PKG/debian/rules"
echo " cp debian/tmp/${f#*/}.signed debian/${f%/*}" >> "../OTHER/$SOURCE_PKG/debian/rules"
else
echo " cp debian/tmp/${f#*/} debian/${f%/*}" >> "../OTHER/$SOURCE_PKG/debian/rules"
fi
fi
done
rm -rf "$nss_db"
# pack everything into a CPIO archive and place it where OBS expects it
pushd hashes
find . -type f | cpio -H newc -o > "../../OTHER/$SOURCE_PKG.cpio.rsasign"
popd
rm -rf hashes
# assemble the source package that OBS will build on the second pass
pushd "../OTHER/$SOURCE_PKG"
dpkg-buildpackage -uc -us -d -S
popd
elif [ "$(basename "$0")" = "dh_signobs_unpack" ] && [ -d ../SOURCES ] && ls ../SOURCES/*cpio.rsasign.sig &> /dev/null
then
# copy packages built on first pass, if any
cp ../SOURCES/*.deb ../DEBS/ 2>/dev/null || :
cp ../SOURCES/*.tar.* ../DEBS/
cp ../SOURCES/*.dsc ../DEBS/
cp ../SOURCES/*.changes ../DEBS/
# but not the current source package, otherwise build-recipe-dsc will fail
rm -f ../DEBS/"$(dpkg-parsechangelog -SSource)"*
# unpack in debian/signatures and create pesign db, where the template source expects them
pushd debian/signatures
cpio -idm < ../../../SOURCES/*.cpio.rsasign.sig
# OBS signs a hash without certificate informations so it cannot simply be
# attached to the PE binaries, certificate metadata has to be provided separately
# so we need to create a certutil db and import the certificate manually
rm -rf nss-db
mkdir nss-db
nss_db="$PWD/nss-db"
certutil -N -d sql:"$nss_db" --empty-password
certutil -A -d sql:"$nss_db" -n cert -t CT,CT,CT -i ../../../SOURCES/_projectcert.crt
while read -r SIG
do
export infile="${SIG%.sig}"
cpio -i --to-stdout "${infile}" < ../../../SOURCES/*.cpio.rsasign > "$(basename "${infile}").sattrs"
test -s "$(basename "${infile}").sattrs" || exit 1
# install the signed file into debian/tmp so that the package can pick it up
# remove the unsigned package name from the path
DEST="../tmp/${SIG#*/}"
# kernel images do not have the .signed prefix, only bootloaders do
if [[ $(basename "$DEST") = vmlinuz* ]]
then
DEST="${DEST%%.sig}"
else
DEST="${DEST}ned"
fi
mkdir -p "../tmp/$(dirname "${DEST}")"
# ensure the EFI hash matches before and after attaching the signature
old_hash=$(pesign -n sql:"$nss_db" -h -P -i "${SIG%.sig}")
pesign -n sql:"$nss_db" -c cert -i "${SIG%.sig}" -o "$DEST" -d sha256 -I "$(basename "${infile}").sattrs" -R "$SIG"
new_hash=$(pesign -n sql:"$nss_db" -h -i "$DEST")
if [ "$old_hash" != "$new_hash" ]
then
echo "Pesign hash mismatch error: $old_hash $new_hash"
exit 1
fi
rm -f "$(basename "${infile}").sattrs" "$SIG" "${SIG%.sig}"
done < <(find . -type f \( -name '*efi.sig' -o -name 'vmlinuz*.sig' \) -printf '%P\n')
rm -rf nss-db
# the kernel-sign-file script wants DER format
openssl x509 -in ../../../SOURCES/_projectcert.crt -inform pem -outform der -out _projectcert.der
while read -r SIG
do
# install the signed file into debian/tmp so that the package can pick it up
# remove the unsigned package name from the path
DEST="../tmp/${SIG#*/}"
DEST="${DEST%%.sig}"
mkdir -p "../tmp/$(dirname "${DEST}")"
/usr/lib/rpm/pesign/kernel-sign-file -i pkcs7 -s "$SIG" sha256 _projectcert.der "${SIG%.sig}" "${DEST}"
rm -f "$SIG" "${SIG%.sig}"
done < <(find . -type f -name '*ko.sig' -printf '%P\n')
rm -f _projectcert.der
popd
elif [ "$(basename "$0")" = "dh_signobs_getcert" ] && [ -d ../SOURCES ] && [ -e ../SOURCES/_projectcert.crt ]
then
if [ "$#" -lt 1 ]
then
echo "Missing destination filename parameter!"
exit 1
fi
mkdir -p "$(dirname "$1")"
if [ "$#" -ge 2 ] && [ "$2" = "DER" ]
then
openssl x509 -in ../SOURCES/_projectcert.crt -inform pem -outform der -out "$1"
else
cp ../SOURCES/_projectcert.crt "$1"
fi
fi