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):
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 [ debug ]
225 debugUID = FORMAT:HEX,OCT:0000000000000000000000000000000000000000000000000000000000000000
226 debugType = INTEGER:4
227 coreDbgEn = INTEGER:0
228 coreDbgSecEn = INTEGER:0
229''', file=outf)
230        args = ['req', '-new', '-x509', '-key', key_fname, '-nodes',
231                '-outform', 'DER', '-out', cert_fname, '-config', config_fname,
232                '-sha512']
233        return self.run_cmd(*args)
234
235    def x509_cert_rom_combined(self, cert_fname, input_fname, key_fname, sw_rev,
236                  config_fname, req_dist_name_dict, load_addr, sha, total_size, num_comps,
237                  sysfw_inner_cert_ext_boot_sequence_string, dm_data_ext_boot_sequence_string,
238                  imagesize_sbl, hashval_sbl, load_addr_sysfw, imagesize_sysfw,
239                  hashval_sysfw, load_addr_sysfw_data, imagesize_sysfw_data,
240                  hashval_sysfw_data, sysfw_inner_cert_ext_boot_block,
241                  dm_data_ext_boot_block, bootcore_opts):
242        """Create a certificate
243
244        Args:
245            cert_fname (str): Filename of certificate to create
246            input_fname (str): Filename containing data to sign
247            key_fname (str): Filename of .pem file
248            sw_rev (int): Software revision
249            config_fname (str): Filename to write fconfig into
250            req_dist_name_dict (dict): Dictionary containing key-value pairs of
251            req_distinguished_name section extensions, must contain extensions for
252            C, ST, L, O, OU, CN and emailAddress
253            cert_type (int): Certification type
254            bootcore (int): Booting core
255            load_addr (int): Load address of image
256            sha (int): Hash function
257            bootcore_opts (int): Booting core option, lockstep (0) or split (2) mode
258
259        Returns:
260            str: Tool output
261        """
262        indata = tools.read_file(input_fname)
263        hashval = hashlib.sha512(indata).hexdigest()
264        sha_type = SHA_OIDS[sha]
265        with open(config_fname, 'w', encoding='utf-8') as outf:
266            print(f'''
267[ req ]
268distinguished_name     = req_distinguished_name
269x509_extensions        = v3_ca
270prompt                 = no
271dirstring_type         = nobmp
272
273[ req_distinguished_name ]
274C                      = {req_dist_name_dict['C']}
275ST                     = {req_dist_name_dict['ST']}
276L                      = {req_dist_name_dict['L']}
277O                      = {req_dist_name_dict['O']}
278OU                     = {req_dist_name_dict['OU']}
279CN                     = {req_dist_name_dict['CN']}
280emailAddress           = {req_dist_name_dict['emailAddress']}
281
282[ v3_ca ]
283basicConstraints = CA:true
2841.3.6.1.4.1.294.1.3=ASN1:SEQUENCE:swrv
2851.3.6.1.4.1.294.1.9=ASN1:SEQUENCE:ext_boot_info
2861.3.6.1.4.1.294.1.8=ASN1:SEQUENCE:debug
287
288[swrv]
289swrv=INTEGER:{sw_rev}
290
291[ext_boot_info]
292extImgSize=INTEGER:{total_size}
293numComp=INTEGER:{num_comps}
294sbl=SEQUENCE:sbl
295sysfw=SEQUENCE:sysfw
296sysfw_data=SEQUENCE:sysfw_data
297{sysfw_inner_cert_ext_boot_sequence_string}
298{dm_data_ext_boot_sequence_string}
299
300[sbl]
301compType = INTEGER:1
302bootCore = INTEGER:16
303compOpts = INTEGER:{bootcore_opts}
304destAddr = FORMAT:HEX,OCT:{load_addr:08x}
305compSize = INTEGER:{imagesize_sbl}
306shaType  = OID:{sha_type}
307shaValue = FORMAT:HEX,OCT:{hashval_sbl}
308
309[sysfw]
310compType = INTEGER:2
311bootCore = INTEGER:0
312compOpts = INTEGER:0
313destAddr = FORMAT:HEX,OCT:{load_addr_sysfw:08x}
314compSize = INTEGER:{imagesize_sysfw}
315shaType  = OID:{sha_type}
316shaValue = FORMAT:HEX,OCT:{hashval_sysfw}
317
318[sysfw_data]
319compType = INTEGER:18
320bootCore = INTEGER:0
321compOpts = INTEGER:0
322destAddr = FORMAT:HEX,OCT:{load_addr_sysfw_data:08x}
323compSize = INTEGER:{imagesize_sysfw_data}
324shaType  = OID:{sha_type}
325shaValue = FORMAT:HEX,OCT:{hashval_sysfw_data}
326
327[ debug ]
328debugUID = FORMAT:HEX,OCT:0000000000000000000000000000000000000000000000000000000000000000
329debugType = INTEGER:4
330coreDbgEn = INTEGER:0
331coreDbgSecEn = INTEGER:0
332
333{sysfw_inner_cert_ext_boot_block}
334
335{dm_data_ext_boot_block}
336        ''', file=outf)
337        args = ['req', '-new', '-x509', '-key', key_fname, '-nodes',
338                '-outform', 'DER', '-out', cert_fname, '-config', config_fname,
339                '-sha512']
340        return self.run_cmd(*args)
341
342    def fetch(self, method):
343        """Fetch handler for openssl
344
345        This installs the openssl package using the apt utility.
346
347        Args:
348            method (FETCH_...): Method to use
349
350        Returns:
351            True if the file was fetched and now installed, None if a method
352            other than FETCH_BIN was requested
353
354        Raises:
355            Valuerror: Fetching could not be completed
356        """
357        if method != bintool.FETCH_BIN:
358            return None
359        return self.apt_install('openssl')
360