1# SPDX-License-Identifier: GPL-2.0+
2# Copyright 2022 Google LLC
3#
4"""Bintool implementation for openssl
5
6openssl provides a number of features useful for signing images
7
8Documentation is at https://www.coreboot.org/CBFS
9
10Source code is at https://www.openssl.org/
11"""
12
13import hashlib
14
15from binman import bintool
16from u_boot_pylib import tools
17
18
19VALID_SHAS = [256, 384, 512, 224]
20SHA_OIDS = {256:'2.16.840.1.101.3.4.2.1',
21            384:'2.16.840.1.101.3.4.2.2',
22            512:'2.16.840.1.101.3.4.2.3',
23            224:'2.16.840.1.101.3.4.2.4'}
24
25class Bintoolopenssl(bintool.Bintool):
26    """openssl tool
27
28    This bintool supports creating new openssl certificates.
29
30    It also supports fetching a binary openssl
31
32    Documentation about openssl is at https://www.openssl.org/
33    """
34    def __init__(self, name):
35        super().__init__(
36            name, 'openssl cryptography toolkit',
37            version_regex=r'OpenSSL (.*) \(', version_args='version')
38
39    def x509_cert(self, cert_fname, input_fname, key_fname, cn, revision,
40                  config_fname):
41        """Create a certificate
42
43        Args:
44            cert_fname (str): Filename of certificate to create
45            input_fname (str): Filename containing data to sign
46            key_fname (str): Filename of .pem file
47            cn (str): Common name
48            revision (int): Revision number
49            config_fname (str): Filename to write fconfig into
50
51        Returns:
52            str: Tool output
53        """
54        indata = tools.read_file(input_fname)
55        hashval = hashlib.sha512(indata).hexdigest()
56        with open(config_fname, 'w', encoding='utf-8') as outf:
57            print(f'''[ req ]
58distinguished_name     = req_distinguished_name
59x509_extensions        = v3_ca
60prompt                 = no
61dirstring_type         = nobmp
62
63[ req_distinguished_name ]
64CN                     = {cert_fname}
65
66[ v3_ca ]
67basicConstraints       = CA:true
681.3.6.1.4.1.294.1.3    = ASN1:SEQUENCE:swrv
691.3.6.1.4.1.294.1.34   = ASN1:SEQUENCE:sysfw_image_integrity
70
71[ swrv ]
72swrv = INTEGER:{revision}
73
74[ sysfw_image_integrity ]
75shaType                = OID:2.16.840.1.101.3.4.2.3
76shaValue               = FORMAT:HEX,OCT:{hashval}
77imageSize              = INTEGER:{len(indata)}
78''', file=outf)
79        args = ['req', '-new', '-x509', '-key', key_fname, '-nodes',
80                '-outform', 'DER', '-out', cert_fname, '-config', config_fname,
81                '-sha512']
82        return self.run_cmd(*args)
83
84    def x509_cert_sysfw(self, cert_fname, input_fname, key_fname, sw_rev,
85                  config_fname, req_dist_name_dict, firewall_cert_data):
86        """Create a certificate to be booted by system firmware
87
88        Args:
89            cert_fname (str): Filename of certificate to create
90            input_fname (str): Filename containing data to sign
91            key_fname (str): Filename of .pem file
92            sw_rev (int): Software revision
93            config_fname (str): Filename to write fconfig into
94            req_dist_name_dict (dict): Dictionary containing key-value pairs of
95            req_distinguished_name section extensions, must contain extensions for
96            C, ST, L, O, OU, CN and emailAddress
97            firewall_cert_data (dict):
98              - auth_in_place (int): The Priv ID for copying as the
99                specific host in firewall protected region
100              - num_firewalls (int): The number of firewalls in the
101                extended certificate
102              - certificate (str): Extended firewall certificate with
103                the information for the firewall configurations.
104
105        Returns:
106            str: Tool output
107        """
108        indata = tools.read_file(input_fname)
109        hashval = hashlib.sha512(indata).hexdigest()
110        with open(config_fname, 'w', encoding='utf-8') as outf:
111            print(f'''[ req ]
112distinguished_name     = req_distinguished_name
113x509_extensions        = v3_ca
114prompt                 = no
115dirstring_type         = nobmp
116
117[ req_distinguished_name ]
118C                      = {req_dist_name_dict['C']}
119ST                     = {req_dist_name_dict['ST']}
120L                      = {req_dist_name_dict['L']}
121O                      = {req_dist_name_dict['O']}
122OU                     = {req_dist_name_dict['OU']}
123CN                     = {req_dist_name_dict['CN']}
124emailAddress           = {req_dist_name_dict['emailAddress']}
125
126[ v3_ca ]
127basicConstraints       = CA:true
1281.3.6.1.4.1.294.1.3    = ASN1:SEQUENCE:swrv
1291.3.6.1.4.1.294.1.34   = ASN1:SEQUENCE:sysfw_image_integrity
1301.3.6.1.4.1.294.1.35   = ASN1:SEQUENCE:sysfw_image_load
1311.3.6.1.4.1.294.1.37   = ASN1:SEQUENCE:firewall
132
133[ swrv ]
134swrv = INTEGER:{sw_rev}
135
136[ sysfw_image_integrity ]
137shaType                = OID:2.16.840.1.101.3.4.2.3
138shaValue               = FORMAT:HEX,OCT:{hashval}
139imageSize              = INTEGER:{len(indata)}
140
141[ sysfw_image_load ]
142destAddr = FORMAT:HEX,OCT:00000000
143authInPlace = INTEGER:{hex(firewall_cert_data['auth_in_place'])}
144
145[ firewall ]
146numFirewallRegions = INTEGER:{firewall_cert_data['num_firewalls']}
147{firewall_cert_data['certificate']}
148''', file=outf)
149        args = ['req', '-new', '-x509', '-key', key_fname, '-nodes',
150                '-outform', 'DER', '-out', cert_fname, '-config', config_fname,
151                '-sha512']
152        return self.run_cmd(*args)
153
154    def x509_cert_rom(self, cert_fname, input_fname, key_fname, sw_rev,
155                  config_fname, req_dist_name_dict, cert_type, bootcore,
156                  bootcore_opts, load_addr, sha, debug):
157        """Create a certificate
158
159        Args:
160            cert_fname (str): Filename of certificate to create
161            input_fname (str): Filename containing data to sign
162            key_fname (str): Filename of .pem file
163            sw_rev (int): Software revision
164            config_fname (str): Filename to write fconfig into
165            req_dist_name_dict (dict): Dictionary containing key-value pairs of
166            req_distinguished_name section extensions, must contain extensions for
167            C, ST, L, O, OU, CN and emailAddress
168            cert_type (int): Certification type
169            bootcore (int): Booting core
170            bootcore_opts(int): Booting core option, lockstep (0) or split (2) mode
171            load_addr (int): Load address of image
172            sha (int): Hash function
173
174        Returns:
175            str: Tool output
176        """
177        indata = tools.read_file(input_fname)
178        hashval = hashlib.sha512(indata).hexdigest()
179        with open(config_fname, 'w', encoding='utf-8') as outf:
180            print(f'''
181[ req ]
182 distinguished_name     = req_distinguished_name
183 x509_extensions        = v3_ca
184 prompt                 = no
185 dirstring_type         = nobmp
186
187 [ req_distinguished_name ]
188C                      = {req_dist_name_dict['C']}
189ST                     = {req_dist_name_dict['ST']}
190L                      = {req_dist_name_dict['L']}
191O                      = {req_dist_name_dict['O']}
192OU                     = {req_dist_name_dict['OU']}
193CN                     = {req_dist_name_dict['CN']}
194emailAddress           = {req_dist_name_dict['emailAddress']}
195
196 [ v3_ca ]
197 basicConstraints = CA:true
198 1.3.6.1.4.1.294.1.1 = ASN1:SEQUENCE:boot_seq
199 1.3.6.1.4.1.294.1.2 = ASN1:SEQUENCE:image_integrity
200 1.3.6.1.4.1.294.1.3 = ASN1:SEQUENCE:swrv
201# 1.3.6.1.4.1.294.1.4 = ASN1:SEQUENCE:encryption
202 1.3.6.1.4.1.294.1.8 = ASN1:SEQUENCE:debug
203
204 [ boot_seq ]
205 certType = INTEGER:{cert_type}
206 bootCore = INTEGER:{bootcore}
207 bootCoreOpts = INTEGER:{bootcore_opts}
208 destAddr = FORMAT:HEX,OCT:{load_addr:08x}
209 imageSize = INTEGER:{len(indata)}
210
211 [ image_integrity ]
212 shaType = OID:{SHA_OIDS[sha]}
213 shaValue = FORMAT:HEX,OCT:{hashval}
214
215 [ swrv ]
216 swrv = INTEGER:{sw_rev}
217
218# [ encryption ]
219# initalVector = FORMAT:HEX,OCT:TEST_IMAGE_ENC_IV
220# randomString = FORMAT:HEX,OCT:TEST_IMAGE_ENC_RS
221# iterationCnt = INTEGER:TEST_IMAGE_KEY_DERIVE_INDEX
222# salt = FORMAT:HEX,OCT:TEST_IMAGE_KEY_DERIVE_SALT
223
224 # When debugging low level boot firmware it can be useful to have ROM or TIFS
225 # unlock JTAG access to the misbehaving CPUs. However in a production setting
226 # this can lead to code modification by outside parties after it's been
227 # authenticated. To gain JTAG access add the 'debug' flag to the binman config
228 [ debug ]
229 debugUID = FORMAT:HEX,OCT:0000000000000000000000000000000000000000000000000000000000000000
230 debugType = INTEGER:{ "4" if debug else "0" }
231 coreDbgEn = INTEGER:0
232 coreDbgSecEn = INTEGER:0
233''', file=outf)
234        args = ['req', '-new', '-x509', '-key', key_fname, '-nodes',
235                '-outform', 'DER', '-out', cert_fname, '-config', config_fname,
236                '-sha512']
237        return self.run_cmd(*args)
238
239    def x509_cert_rom_combined(self, cert_fname, input_fname, key_fname, sw_rev,
240                  config_fname, req_dist_name_dict, load_addr, sha, total_size, num_comps,
241                  sysfw_inner_cert_ext_boot_sequence_string, dm_data_ext_boot_sequence_string,
242                  imagesize_sbl, hashval_sbl, load_addr_sysfw, imagesize_sysfw,
243                  hashval_sysfw, load_addr_sysfw_data, imagesize_sysfw_data,
244                  hashval_sysfw_data, sysfw_inner_cert_ext_boot_block,
245                  dm_data_ext_boot_block, bootcore_opts, debug):
246        """Create a certificate
247
248        Args:
249            cert_fname (str): Filename of certificate to create
250            input_fname (str): Filename containing data to sign
251            key_fname (str): Filename of .pem file
252            sw_rev (int): Software revision
253            config_fname (str): Filename to write fconfig into
254            req_dist_name_dict (dict): Dictionary containing key-value pairs of
255            req_distinguished_name section extensions, must contain extensions for
256            C, ST, L, O, OU, CN and emailAddress
257            cert_type (int): Certification type
258            bootcore (int): Booting core
259            load_addr (int): Load address of image
260            sha (int): Hash function
261            bootcore_opts (int): Booting core option, lockstep (0) or split (2) mode
262
263        Returns:
264            str: Tool output
265        """
266        indata = tools.read_file(input_fname)
267        hashval = hashlib.sha512(indata).hexdigest()
268        sha_type = SHA_OIDS[sha]
269        with open(config_fname, 'w', encoding='utf-8') as outf:
270            print(f'''
271[ req ]
272distinguished_name     = req_distinguished_name
273x509_extensions        = v3_ca
274prompt                 = no
275dirstring_type         = nobmp
276
277[ req_distinguished_name ]
278C                      = {req_dist_name_dict['C']}
279ST                     = {req_dist_name_dict['ST']}
280L                      = {req_dist_name_dict['L']}
281O                      = {req_dist_name_dict['O']}
282OU                     = {req_dist_name_dict['OU']}
283CN                     = {req_dist_name_dict['CN']}
284emailAddress           = {req_dist_name_dict['emailAddress']}
285
286[ v3_ca ]
287basicConstraints = CA:true
2881.3.6.1.4.1.294.1.3=ASN1:SEQUENCE:swrv
2891.3.6.1.4.1.294.1.9=ASN1:SEQUENCE:ext_boot_info
2901.3.6.1.4.1.294.1.8=ASN1:SEQUENCE:debug
291
292[swrv]
293swrv=INTEGER:{sw_rev}
294
295[ext_boot_info]
296extImgSize=INTEGER:{total_size}
297numComp=INTEGER:{num_comps}
298sbl=SEQUENCE:sbl
299sysfw=SEQUENCE:sysfw
300sysfw_data=SEQUENCE:sysfw_data
301{sysfw_inner_cert_ext_boot_sequence_string}
302{dm_data_ext_boot_sequence_string}
303
304[sbl]
305compType = INTEGER:1
306bootCore = INTEGER:16
307compOpts = INTEGER:{bootcore_opts}
308destAddr = FORMAT:HEX,OCT:{load_addr:08x}
309compSize = INTEGER:{imagesize_sbl}
310shaType  = OID:{sha_type}
311shaValue = FORMAT:HEX,OCT:{hashval_sbl}
312
313[sysfw]
314compType = INTEGER:2
315bootCore = INTEGER:0
316compOpts = INTEGER:0
317destAddr = FORMAT:HEX,OCT:{load_addr_sysfw:08x}
318compSize = INTEGER:{imagesize_sysfw}
319shaType  = OID:{sha_type}
320shaValue = FORMAT:HEX,OCT:{hashval_sysfw}
321
322[sysfw_data]
323compType = INTEGER:18
324bootCore = INTEGER:0
325compOpts = INTEGER:0
326destAddr = FORMAT:HEX,OCT:{load_addr_sysfw_data:08x}
327compSize = INTEGER:{imagesize_sysfw_data}
328shaType  = OID:{sha_type}
329shaValue = FORMAT:HEX,OCT:{hashval_sysfw_data}
330
331# When debugging low level boot firmware it can be useful to have ROM or TIFS
332# unlock JTAG access to the misbehaving CPUs. However in a production setting
333# this can lead to code modification by outside parties after it's been
334# authenticated. To gain JTAG access add the 'debug' flag to the binman config
335[ debug ]
336debugUID = FORMAT:HEX,OCT:0000000000000000000000000000000000000000000000000000000000000000
337debugType = INTEGER:{ "4" if debug else "0" }
338coreDbgEn = INTEGER:0
339coreDbgSecEn = INTEGER:0
340
341{sysfw_inner_cert_ext_boot_block}
342
343{dm_data_ext_boot_block}
344        ''', file=outf)
345        args = ['req', '-new', '-x509', '-key', key_fname, '-nodes',
346                '-outform', 'DER', '-out', cert_fname, '-config', config_fname,
347                '-sha512']
348        return self.run_cmd(*args)
349
350    def fetch(self, method):
351        """Fetch handler for openssl
352
353        This installs the openssl package using the apt utility.
354
355        Args:
356            method (FETCH_...): Method to use
357
358        Returns:
359            True if the file was fetched and now installed, None if a method
360            other than FETCH_BIN was requested
361
362        Raises:
363            Valuerror: Fetching could not be completed
364        """
365        if method != bintool.FETCH_BIN:
366            return None
367        return self.apt_install('openssl')
368