1# Tests for espsecure.py (esp_hsm_sign.py) using the pytest framework
2#
3# Assumes openssl binary is in the PATH
4
5import configparser
6import os
7import os.path
8import sys
9import tempfile
10from collections import namedtuple
11
12from conftest import need_to_install_package_err
13
14try:
15    import espsecure
16    import pkcs11
17except ImportError:
18    need_to_install_package_err()
19
20TEST_DIR = os.path.abspath(os.path.dirname(__file__))
21
22TOKEN_PIN = "1234"
23TOKEN_PIN_SO = "123456"
24
25
26class EspSecureHSMTestCase:
27    @classmethod
28    def setup_class(self):
29        self.cleanup_files = []  # keep a list of files _open()ed by each test case
30
31    @classmethod
32    def teardown_class(self):
33        for f in self.cleanup_files:
34            f.close()
35
36    def _open(self, image_file):
37        f = open(os.path.join(TEST_DIR, "secure_images", image_file), "rb")
38        self.cleanup_files.append(f)
39        return f
40
41    def get_pkcs11lib(self):
42        if sys.maxsize > 2**32:
43            # 64-bits
44            WINDOWS_SOFTHSM = "c:/SoftHSM2/lib/softhsm2-x64.dll"
45        else:
46            # 32-bits
47            WINDOWS_SOFTHSM = "c:/SoftHSM2/lib/softhsm2.dll"
48        # use SoftHSM2
49        LIBS = [
50            "/usr/local/lib/softhsm/libsofthsm2.so",  # macOS or local build
51            "/usr/lib/softhsm/libsofthsm2.so",  # Debian
52            "/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so",  # Ubuntu 16.04
53            WINDOWS_SOFTHSM,  # Windows
54        ]
55
56        for lib in LIBS:
57            if os.path.isfile(lib):
58                print("Using lib:", lib)
59                return lib
60
61        return None
62
63    # RSA-PSS token
64    def softhsm_setup_token(self, filename, token_label):
65        self.pkcs11_lib = self.get_pkcs11lib()
66        if self.pkcs11_lib is None:
67            print("PKCS11 lib does not exist")
68            sys.exit(-1)
69        lib = pkcs11.lib(self.pkcs11_lib)
70        token = lib.get_token(token_label=token_label)
71        slot = token.slot.slot_id
72        session = token.open(rw=True, user_pin=TOKEN_PIN)
73
74        keyID = (0x0,)
75        label = "Private Key for Digital Signature"
76        label_pubkey = "Public Key for Digital Signature"
77        pubTemplate = [
78            (pkcs11.Attribute.CLASS, pkcs11.constants.ObjectClass.PUBLIC_KEY),
79            (pkcs11.Attribute.TOKEN, True),
80            (pkcs11.Attribute.PRIVATE, False),
81            (pkcs11.Attribute.MODULUS_BITS, 0x0C00),
82            (pkcs11.Attribute.PUBLIC_EXPONENT, (0x01, 0x00, 0x01)),
83            (pkcs11.Attribute.ENCRYPT, True),
84            (pkcs11.Attribute.VERIFY, True),
85            (pkcs11.Attribute.VERIFY_RECOVER, True),
86            (pkcs11.Attribute.WRAP, True),
87            (pkcs11.Attribute.LABEL, label_pubkey),
88            (pkcs11.Attribute.ID, keyID),
89        ]
90
91        privTemplate = [
92            (pkcs11.Attribute.CLASS, pkcs11.constants.ObjectClass.PRIVATE_KEY),
93            (pkcs11.Attribute.TOKEN, True),
94            (pkcs11.Attribute.PRIVATE, True),
95            (pkcs11.Attribute.DECRYPT, True),
96            (pkcs11.Attribute.SIGN, True),
97            (pkcs11.Attribute.SENSITIVE, True),
98            (pkcs11.Attribute.SIGN_RECOVER, True),
99            (pkcs11.Attribute.LABEL, label),
100            (pkcs11.Attribute.UNWRAP, True),
101            (pkcs11.Attribute.ID, keyID),
102        ]
103        session.generate_keypair(
104            pkcs11.KeyType.RSA,
105            3072,
106            private_template=privTemplate,
107            public_template=pubTemplate,
108        )
109
110        # Generate HSM config file
111        configfile = os.path.join(TEST_DIR, "secure_images", filename)
112        config = configparser.ConfigParser()
113
114        section = "hsm_config"
115        config.add_section(section)
116
117        config.set(section, "pkcs11_lib", self.pkcs11_lib)
118        config.set(section, "credentials", TOKEN_PIN)
119        config.set(section, "slot", str(slot))
120        config.set(section, "label", label)
121        config.set(section, "label_pubkey", label_pubkey)
122
123        with open(configfile, "w") as c:
124            config.write(c)
125
126        session.close()
127
128
129class TestSigning(EspSecureHSMTestCase):
130    VerifyArgs = namedtuple(
131        "verify_signature_args", ["version", "hsm", "hsm_config", "keyfile", "datafile"]
132    )
133
134    SignArgs = namedtuple(
135        "sign_data_args",
136        [
137            "version",
138            "keyfile",
139            "output",
140            "append_signatures",
141            "hsm",
142            "hsm_config",
143            "pub_key",
144            "signature",
145            "datafile",
146        ],
147    )
148
149    def test_sign_v2_hsm(self):
150        # Sign using SoftHSMv2 + Verify
151        self.softhsm_setup_token("softhsm_v2.ini", "softhsm-test-token")
152        with tempfile.NamedTemporaryFile() as output_file:
153            args = self.SignArgs(
154                "2",
155                None,
156                output_file.name,
157                False,
158                True,
159                os.path.join(TEST_DIR, "secure_images", "softhsm_v2.ini"),
160                None,
161                None,
162                self._open("bootloader_unsigned_v2.bin"),
163            )
164            espsecure.sign_data(args)
165
166            args = self.VerifyArgs(
167                "2",
168                True,
169                os.path.join(TEST_DIR, "secure_images", "softhsm_v2.ini"),
170                None,
171                output_file,
172            )
173            espsecure.verify_signature(args)
174
175    def test_sign_v2_hsm_append_signatures_multiple_steps(self):
176        # Append signatures using HSM + Verify with an appended key
177        self.softhsm_setup_token("softhsm_v2_1.ini", "softhsm-test-token-1")
178        with tempfile.NamedTemporaryFile() as output_file1:
179            args = self.SignArgs(
180                "2",
181                None,
182                output_file1.name,
183                True,
184                True,
185                os.path.join(TEST_DIR, "secure_images", "softhsm_v2_1.ini"),
186                None,
187                None,
188                self._open("bootloader_unsigned_v2.bin"),
189            )
190            espsecure.sign_data(args)
191
192            self.softhsm_setup_token("softhsm_v2_2.ini", "softhsm-test-token-2")
193            with tempfile.NamedTemporaryFile() as output_file2:
194                args = self.SignArgs(
195                    "2",
196                    None,
197                    output_file2.name,
198                    True,
199                    True,
200                    os.path.join(TEST_DIR, "secure_images", "softhsm_v2_2.ini"),
201                    None,
202                    None,
203                    self._open(output_file1.name),
204                )
205                espsecure.sign_data(args)
206
207                self.softhsm_setup_token("softhsm_v2_3.ini", "softhsm-test-token-3")
208                with tempfile.NamedTemporaryFile() as output_file3:
209                    args = self.SignArgs(
210                        "2",
211                        None,
212                        output_file3.name,
213                        True,
214                        True,
215                        os.path.join(TEST_DIR, "secure_images", "softhsm_v2_3.ini"),
216                        None,
217                        None,
218                        self._open(output_file2.name),
219                    )
220                    espsecure.sign_data(args)
221
222                    args = self.VerifyArgs(
223                        "2",
224                        True,
225                        os.path.join(TEST_DIR, "secure_images", "softhsm_v2_1.ini"),
226                        None,
227                        output_file3,
228                    )
229                    espsecure.verify_signature(args)
230                    output_file3.seek(0)
231
232                    args = self.VerifyArgs(
233                        "2",
234                        True,
235                        os.path.join(TEST_DIR, "secure_images", "softhsm_v2_2.ini"),
236                        None,
237                        output_file3,
238                    )
239                    espsecure.verify_signature(args)
240                    output_file3.seek(0)
241
242                    args = self.VerifyArgs(
243                        "2",
244                        True,
245                        os.path.join(TEST_DIR, "secure_images", "softhsm_v2_3.ini"),
246                        None,
247                        output_file3,
248                    )
249                    espsecure.verify_signature(args)
250