3 # Copyright (C) 2017 Collabora Ltd
4 # 2017 Helen Koike <helen.koike@collabora.com>
6 # Ported from bash to python3 by Julien Cristau <jcristau@debian.org>
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.
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.
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):]
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'))
42 print("ignoring %s" % os.path.join(dirpath, filename), file=sys.stderr)
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
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)
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
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],
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)
83 def repack(signed_dir, fileobj):
84 def cleanup_tarinfo(tarinfo):
85 tarinfo.path = os.path.relpath('/' + tarinfo.path, signed_dir)
87 tarinfo.gname = 'root'
89 tarinfo.uname = 'root'
92 with tarfile.TarFile.open(mode='w:xz', fileobj=fileobj) as f:
93 f.add(signed_dir, filter=cleanup_tarinfo)
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')
104 args = parser.parse_args()
106 cp = configparser.RawConfigParser()
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')
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')
126 config['pin'] = cp.get('efi', 'pin')
128 workdir = tempfile.TemporaryDirectory()
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)
137 if __name__ == '__main__':