04a3344d0a72e5ae00a189764f0f173f25a37351
[mirror/dsa-puppet.git] / modules / roles / files / signing / secure-boot-code-sign.py
1 #!/usr/bin/python3
2
3 # Copyright (C) 2017 Collabora Ltd
4 # 2017 Helen Koike <helen.koike@collabora.com>
5 #
6 # Ported from bash to python3 by Julien Cristau <jcristau@debian.org>
7 #
8 # This program is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 # Lesser General Public License for more details.
17
18 import argparse
19 import configparser
20 import os
21 import subprocess
22 import sys
23 import tarfile
24 import tempfile
25
26
27 config = {}
28
29
30 def sign(extract_dir, signed_dir):
31     for dirpath, dirnames, filenames in os.walk(extract_dir):
32         assert dirpath.startswith(extract_dir)
33         out_dir = signed_dir + dirpath[len(extract_dir):]
34         os.makedirs(out_dir)
35         for filename in filenames:
36             #print(os.path.join(dirpath, filename), file=sys.stderr)
37             if filename.endswith('.efi') or filename.startswith('vmlinuz-'):
38                 sign_efi(os.path.join(dirpath, filename), os.path.join(out_dir, filename + '.sig'))
39             elif filename.endswith('.ko'):
40                 sign_kmod(os.path.join(dirpath, filename), os.path.join(out_dir, filename + '.sig'))
41             else:
42                 print("ignoring %s" % os.path.join(dirpath, filename), file=sys.stderr)
43
44
45 def sign_kmod(module_path, signature_path):
46     assert 'linux_sign_file' in config
47     assert 'pkcs11_uri' in config
48     assert 'cert_path' in config
49     assert 'pin' in config
50
51     env = os.environ.copy()
52     env['KBUILD_SIGN_PIN'] = config['pin']
53     # use check_output instead of check_call as sign-file seems to send random
54     # stuff to stderr even when it succeeds
55     subprocess.check_output(
56         [config['linux_sign_file'], '-d', 'sha256', config['pkcs11_uri'],
57          config['cert_path'], module_path],
58         env=env, stderr=subprocess.STDOUT)
59     os.rename(module_path + '.p7s', signature_path)
60
61
62 def sign_efi(efi_path, signature_path):
63     assert 'sign-efi' in config
64     assert 'certdir' in config
65     assert 'token' in config
66     assert 'certname' in config
67     assert 'pin' in config
68
69     env = os.environ.copy()
70     env['PESIGN_PIN'] = config['pin']
71     with open(signature_path, 'wb') as out:
72         subprocess.check_call(
73             [config['sign-efi'], config['certdir'], config['token'],
74              config['certname'], efi_path],
75             env=env, stdout=out)
76
77
78 def extract(tar_file, extract_dir):
79     with tarfile.TarFile.open(fileobj=tar_file, mode="r:xz") as f:
80         f.extractall(extract_dir)
81
82
83 def repack(signed_dir, fileobj):
84     def cleanup_tarinfo(tarinfo):
85         tarinfo.path = os.path.relpath('/' + tarinfo.path, signed_dir)
86         tarinfo.gid = 0
87         tarinfo.gname = 'root'
88         tarinfo.uid = 0
89         tarinfo.uname = 'root'
90         return tarinfo
91
92     with tarfile.TarFile.open(mode='w:xz', fileobj=fileobj) as f:
93         f.add(signed_dir, filter=cleanup_tarinfo)
94
95
96 def main():
97     parser = argparse.ArgumentParser(
98             description='sign files in a tarball')
99     parser.add_argument('input_tar', metavar='input', type=argparse.FileType('rb'),
100             help='tarball containing files to be signed')
101     parser.add_argument('--config', '-c', type=str,
102             default='/etc/codesign.ini', help='configuration file')
103
104     args = parser.parse_args()
105
106     cp = configparser.RawConfigParser()
107     cp.read(args.config)
108
109     # path to the sign-file command from Linux
110     config['linux_sign_file'] = cp.get('commands', 'sign-kmod',
111             fallback='/usr/lib/linux-kbuild-4.9/scripts/sign-file')
112     # pkcs11 uri from `p11tool --list-token-urls`
113     config['pkcs11_uri'] = cp.get('efi', 'pkcs11_uri')
114     # path to the PEM or DER-format certificate
115     config['cert_path'] = cp.get('efi', 'cert_path')
116
117     # path to our pesign wrapper script
118     config['sign-efi'] = cp.get('commands', 'sign-efi', fallback='/usr/local/bin/pesign-wrap')
119     # path to the nss store
120     config['certdir'] = cp.get('efi', 'certdir', fallback='/srv/codesign/pki')
121     # name of the token in the nss store
122     config['token'] = cp.get('efi','token', fallback='PIV_II (PIV Card Holder pin)')
123     # name of the cert in the nss store
124     config['certname'] = cp.get('efi', 'cert', fallback='Certificate for Digital Signature')
125
126     config['pin'] = cp.get('efi', 'pin')
127
128     workdir = tempfile.TemporaryDirectory()
129     with workdir:
130         extract_dir = os.path.join(workdir.name, 'in')
131         signed_dir = os.path.join(workdir.name, 'out')
132         extract(args.input_tar, extract_dir)
133         sign(extract_dir, signed_dir)
134         repack(signed_dir, sys.stdout.buffer)
135
136
137 if __name__ == '__main__':
138     sys.exit(main())