]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
binman: Support generation of x509 certificates
authorSimon Glass <sjg@chromium.org>
Fri, 3 Mar 2023 00:02:45 +0000 (17:02 -0700)
committerSimon Glass <sjg@chromium.org>
Wed, 8 Mar 2023 21:15:15 +0000 (13:15 -0800)
And a new entry type which supports generation of x509 certificates.
This uses a new 'openssl' btool with just one operation so far.

Signed-off-by: Simon Glass <sjg@chromium.org>
tools/binman/btool/openssl.py [new file with mode: 0644]
tools/binman/entries.rst
tools/binman/etype/x509_cert.py [new file with mode: 0644]
tools/binman/ftest.py
tools/binman/test/279_x509_cert.dts [new file with mode: 0644]
tools/binman/test/key.key [new file with mode: 0644]
tools/binman/test/key.pem [new file with mode: 0644]

diff --git a/tools/binman/btool/openssl.py b/tools/binman/btool/openssl.py
new file mode 100644 (file)
index 0000000..3a4dbdd
--- /dev/null
@@ -0,0 +1,94 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright 2022 Google LLC
+#
+"""Bintool implementation for openssl
+
+openssl provides a number of features useful for signing images
+
+Documentation is at https://www.coreboot.org/CBFS
+
+Source code is at https://www.openssl.org/
+"""
+
+import hashlib
+
+from binman import bintool
+from u_boot_pylib import tools
+
+class Bintoolopenssl(bintool.Bintool):
+    """openssl tool
+
+    This bintool supports creating new openssl certificates.
+
+    It also supports fetching a binary openssl
+
+    Documentation about openssl is at https://www.openssl.org/
+    """
+    def __init__(self, name):
+        super().__init__(
+            name, 'openssl cryptography toolkit',
+            version_regex=r'OpenSSL (.*) \(', version_args='version')
+
+    def x509_cert(self, cert_fname, input_fname, key_fname, cn, revision,
+                  config_fname):
+        """Create a certificate
+
+        Args:
+            cert_fname (str): Filename of certificate to create
+            input_fname (str): Filename containing data to sign
+            key_fname (str): Filename of .pem file
+            cn (str): Common name
+            revision (int): Revision number
+            config_fname (str): Filename to write fconfig into
+
+        Returns:
+            str: Tool output
+        """
+        indata = tools.read_file(input_fname)
+        hashval = hashlib.sha512(indata).hexdigest()
+        with open(config_fname, 'w', encoding='utf-8') as outf:
+            print(f'''[ req ]
+distinguished_name     = req_distinguished_name
+x509_extensions        = v3_ca
+prompt                 = no
+dirstring_type         = nobmp
+
+[ req_distinguished_name ]
+CN                     = {cert_fname}
+
+[ v3_ca ]
+basicConstraints       = CA:true
+1.3.6.1.4.1.294.1.3    = ASN1:SEQUENCE:swrv
+1.3.6.1.4.1.294.1.34   = ASN1:SEQUENCE:sysfw_image_integrity
+
+[ swrv ]
+swrv = INTEGER:{revision}
+
+[ sysfw_image_integrity ]
+shaType                = OID:2.16.840.1.101.3.4.2.3
+shaValue               = FORMAT:HEX,OCT:{hashval}
+imageSize              = INTEGER:{len(indata)}
+''', file=outf)
+        args = ['req', '-new', '-x509', '-key', key_fname, '-nodes',
+                '-outform', 'DER', '-out', cert_fname, '-config', config_fname,
+                '-sha512']
+        return self.run_cmd(*args)
+
+    def fetch(self, method):
+        """Fetch handler for openssl
+
+        This installs the openssl package using the apt utility.
+
+        Args:
+            method (FETCH_...): Method to use
+
+        Returns:
+            True if the file was fetched and now installed, None if a method
+            other than FETCH_BIN was requested
+
+        Raises:
+            Valuerror: Fetching could not be completed
+        """
+        if method != bintool.FETCH_BIN:
+            return None
+        return self.apt_install('openssl')
index 19659247cf02b49d0e69a4ed9c2f160fc9c1330d..9a52b225a935417d9a1a6ce8339383028da92392 100644 (file)
@@ -2276,6 +2276,24 @@ and kernel are genuine.
 
 
 
+.. _etype_x509_cert:
+
+Entry: x509-cert: An entry which contains an X509 certificate
+-------------------------------------------------------------
+
+Properties / Entry arguments:
+    - content: List of phandles to entries to sign
+
+Output files:
+    - input.<unique_name> - input file passed to openssl
+    - cert.<unique_name> - output file generated by openssl (which is
+        used as the entry contents)
+
+openssl signs the provided data, writing the signature in this entry. This
+allows verification that the data is genuine
+
+
+
 .. _etype_x86_reset16:
 
 Entry: x86-reset16: x86 16-bit reset code for U-Boot
diff --git a/tools/binman/etype/x509_cert.py b/tools/binman/etype/x509_cert.py
new file mode 100644 (file)
index 0000000..f80a6ec
--- /dev/null
@@ -0,0 +1,92 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright 2023 Google LLC
+# Written by Simon Glass <sjg@chromium.org>
+#
+
+# Support for an X509 certificate, used to sign a set of entries
+
+from collections import OrderedDict
+import os
+
+from binman.entry import EntryArg
+from binman.etype.collection import Entry_collection
+
+from dtoc import fdt_util
+from u_boot_pylib  import tools
+
+class Entry_x509_cert(Entry_collection):
+    """An entry which contains an X509 certificate
+
+    Properties / Entry arguments:
+        - content: List of phandles to entries to sign
+
+    Output files:
+        - input.<unique_name> - input file passed to openssl
+        - cert.<unique_name> - output file generated by openssl (which is
+            used as the entry contents)
+
+    openssl signs the provided data, writing the signature in this entry. This
+    allows verification that the data is genuine
+    """
+    def __init__(self, section, etype, node):
+        super().__init__(section, etype, node)
+        self.openssl = None
+
+    def ReadNode(self):
+        super().ReadNode()
+        self._cert_ca = fdt_util.GetString(self._node, 'cert-ca')
+        self._cert_rev = fdt_util.GetInt(self._node, 'cert-revision-int', 0)
+        self.key_fname = self.GetEntryArgsOrProps([
+            EntryArg('keyfile', str)], required=True)[0]
+
+    def GetCertificate(self, required):
+        """Get the contents of this entry
+
+        Args:
+            required: True if the data must be present, False if it is OK to
+                return None
+
+        Returns:
+            bytes content of the entry, which is the signed vblock for the
+                provided data
+        """
+        # Join up the data files to be signed
+        input_data = self.GetContents(required)
+        if input_data is None:
+            return None
+
+        uniq = self.GetUniqueName()
+        output_fname = tools.get_output_filename('cert.%s' % uniq)
+        input_fname = tools.get_output_filename('input.%s' % uniq)
+        config_fname = tools.get_output_filename('config.%s' % uniq)
+        tools.write_file(input_fname, input_data)
+        stdout = self.openssl.x509_cert(
+            cert_fname=output_fname,
+            input_fname=input_fname,
+            key_fname=self.key_fname,
+            cn=self._cert_ca,
+            revision=self._cert_rev,
+            config_fname=config_fname)
+        if stdout is not None:
+            data = tools.read_file(output_fname)
+        else:
+            # Bintool is missing; just use 4KB of zero data
+            self.record_missing_bintool(self.openssl)
+            data = tools.get_bytes(0, 4096)
+        return data
+
+    def ObtainContents(self):
+        data = self.GetCertificate(False)
+        if data is None:
+            return False
+        self.SetContents(data)
+        return True
+
+    def ProcessContents(self):
+        # The blob may have changed due to WriteSymbols()
+        data = self.GetCertificate(True)
+        return self.ProcessContentsUpdate(data)
+
+    def AddBintools(self, btools):
+        super().AddBintools(btools)
+        self.openssl = self.AddBintool(btools, 'openssl')
index 76445969201d543f57068a9ab5f2a20f346f6fd2..f1e14c6b3dc8bdc1e51c111c9366e7eae52fbb67 100644 (file)
@@ -6539,6 +6539,32 @@ fdt         fdtmap                Extract the devicetree blob from the fdtmap
         finally:
             shutil.rmtree(tmpdir)
 
+    def testX509Cert(self):
+        """Test creating an X509 certificate"""
+        keyfile = self.TestFile('key.key')
+        entry_args = {
+            'keyfile': keyfile,
+        }
+        data = self._DoReadFileDtb('279_x509_cert.dts',
+                                   entry_args=entry_args)[0]
+        cert = data[:-4]
+        self.assertEqual(U_BOOT_DATA, data[-4:])
+
+        # TODO: verify the signature
+
+    def testX509CertMissing(self):
+        """Test that binman still produces an image if openssl is missing"""
+        keyfile = self.TestFile('key.key')
+        entry_args = {
+            'keyfile': 'keyfile',
+        }
+        with test_util.capture_sys_output() as (_, stderr):
+            self._DoTestFile('279_x509_cert.dts',
+                             force_missing_bintools='openssl',
+                             entry_args=entry_args)
+        err = stderr.getvalue()
+        self.assertRegex(err, "Image 'image'.*missing bintools.*: openssl")
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tools/binman/test/279_x509_cert.dts b/tools/binman/test/279_x509_cert.dts
new file mode 100644 (file)
index 0000000..7123817
--- /dev/null
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+       #address-cells = <1>;
+       #size-cells = <1>;
+
+       binman {
+               x509-cert {
+                       cert-ca = "IOT2050 Firmware Signature";
+                       cert-revision-int = <0>;
+                       content = <&u_boot>;
+               };
+
+               u_boot: u-boot {
+               };
+       };
+};
diff --git a/tools/binman/test/key.key b/tools/binman/test/key.key
new file mode 100644 (file)
index 0000000..9de3be1
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+wR0+2mDpx+HDyu76q3M+KxXG2U8sJg==
+-----END PRIVATE KEY-----
diff --git a/tools/binman/test/key.pem b/tools/binman/test/key.pem
new file mode 100644 (file)
index 0000000..7a7b84a
--- /dev/null
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFcTCCA1kCFB/17qhcvpyKhG+jfS2c0qG1yjruMA0GCSqGSIb3DQEBCwUAMHUx
+CzAJBgNVBAYTAk5aMRMwEQYDVQQIDApDYW50ZXJidXJ5MRUwEwYDVQQHDAxDaHJp
+c3RjaHVyY2gxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUG
+A1UEAwwOTXkgQ29tbW9uIE5hbWUwHhcNMjMwMjEzMDM1MzMzWhcNMjQwMjEzMDM1
+MzMzWjB1MQswCQYDVQQGEwJOWjETMBEGA1UECAwKQ2FudGVyYnVyeTEVMBMGA1UE
+BwwMQ2hyaXN0Y2h1cmNoMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBM
+dGQxFzAVBgNVBAMMDk15IENvbW1vbiBOYW1lMIICIjANBgkqhkiG9w0BAQEFAAOC
+Ag8AMIICCgKCAgEAkgyzB6tScN1Phh9sLrUhq0+F4bOWMu7oUf6wzorik1t85FXX
+0iJYsntzZ8K5f5zVBcxCfuLfwIg2iUJOSnXUwCZzpMOUmAbxbEw4TEeBx+gMn82m
+lZfgABjoOC18GcxmCp3IDmObumcjAUsx8aMyZpqKmGgiIuCL9lWUWcNWDwaE+rL9
+/EUEhE+XIv4PvPUn2/OWTxz7ujy5c2U7nxvsswISA7CLzabdmJQp0NU6qGcV7M7c
+TdWyiD+vMb5QEB0gAfPseMtLWDxNSFE9QytthTIM+YsgaepLBWqiNOnyE7SYzyht
+d8/LF14G+P6yeSNJYbJ9ckrB0NMy8Pwl+C2tv1TANAcOhixUYbv6zsnKin5sC5tu
+WxMVfMS+pcBaIcW8wUIGK/d7oxhckAFwQzqYb2XHVz9whwHBFYlUY67AVjc/MMn+
+1/IdhSFNepAHd9FrrKY5pxrr0Wehp0jQ1TtrWxCr8JIOpYmFsuCF7VnL6KDLZIWM
+rxbnXMUT4MRFE0NNG4mHyefbpKg+yIck+6atW2QwlZwLenVFD1mbra2wWnLMiE7N
+NYcoj8bwAEO10aCJ5ltQy6h2foAiVGCdE2ogCPAJp48qTFkVKNfZdMyID3b2vBCf
+ps/pChbGbM6NZ8LsUC+6FElAm9IXwdpN1sTbYQYt0KbKVCj7SsHiIPSE7SMCAwEA
+ATANBgkqhkiG9w0BAQsFAAOCAgEAJAJoia6Vq4vXP/0bCgW3o9TOMmFYhI/xPxoh
+Gd7was9R7BOrMGO+/3E7DZtjycZYL0r9nOtr9S/BBreuZ4vkk/PSoGaSnG8ST4jC
+Ajk7ew/32RGOgA/oIzgKj1SPkBtvW+x+76sjUkGKsxmABBUhycIY7K0U8McTTfJ7
+gJ164VXmdG7qFMWmRy4Ry9QGXkDsbMSOZ485X7zbphjK5OZXEujP7GMUgg1lP479
+NqC1g+1m/A3PIB767lVYA7APQsrckHdRqOTkK9TYRQ3mvyE2wruhqE6lx8G/UyFh
+RZjZ3lh2bx07UWIlyMabnGDMrM4FCnesqVyVAc8VAbkdXkeJI9r6DdFw+dzIY0P1
+il+MlYpZNwRyNv2W5SCPilyuhuPOSrSnsSHx64puCIvwG/4xA30Jw8nviJuyGSef
+7uE+W7SD9E/hQHi/S9KRsYVoo7a6X9ADiwNsRNzVnuqc7K3mv/C5E9s6uFTNoObe
+fUBA7pL3Fmvc5pYatxTFI85ajBpe/la6AA+7HX/8PXEphmp6GhFCcfsq+DL03vTM
+DqIJL1i/JXggwqvvdcfaSeMDIOIzO89yUGGwwuj9rqMeEY99qDtljgy1EljjrB5i
+0j4Jg4O0OEd2KIOD7nz4do1tLNlRcpysDZeXIiwAI7Dd3wWMsgpOQxs0zqWyqDVq
+mCKa5Tw=
+-----END CERTIFICATE-----