1# SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
2#
3# SPDX-License-Identifier: GPL-2.0-or-later
4# PYTHON_ARGCOMPLETE_OK
5import argparse
6import hashlib
7import operator
8import os
9import struct
10import sys
11import tempfile
12import zlib
13from collections import namedtuple
14from io import IOBase
15
16from cryptography import exceptions
17from cryptography.hazmat.backends import default_backend
18from cryptography.hazmat.primitives import hashes
19from cryptography.hazmat.primitives import serialization
20from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa, utils
21from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
22from cryptography.utils import int_to_bytes
23
24import ecdsa
25
26import esptool
27
28SIG_BLOCK_MAGIC = 0xE7
29
30# Scheme used in Secure Boot V2
31SIG_BLOCK_VERSION_RSA = 0x02
32SIG_BLOCK_VERSION_ECDSA = 0x03
33
34# SHA scheme used in Secure Boot V2 ECDSA signature blocks
35ECDSA_SHA_256 = 0x0
36ECDSA_SHA_384 = 0x1
37
38# Curve IDs used in Secure Boot V2 ECDSA signature blocks
39CURVE_ID_P192 = 1
40CURVE_ID_P256 = 2
41CURVE_ID_P384 = 3
42
43SECTOR_SIZE = 4096
44SIG_BLOCK_SIZE = (
45    1216  # Refer to secure boot v2 signature block format for more details.
46)
47
48
49def get_chunks(source, chunk_len):
50    """Returns an iterator over 'chunk_len' chunks of 'source'"""
51    return (source[i : i + chunk_len] for i in range(0, len(source), chunk_len))
52
53
54def endian_swap_words(source):
55    """Endian-swap each word in 'source' bitstring"""
56    assert len(source) % 4 == 0
57    words = "I" * (len(source) // 4)
58    return struct.pack("<" + words, *struct.unpack(">" + words, source))
59
60
61def swap_word_order(source):
62    """Swap the order of the words in 'source' bitstring"""
63    assert len(source) % 4 == 0
64    words = "I" * (len(source) // 4)
65    return struct.pack(words, *reversed(struct.unpack(words, source)))
66
67
68def _load_hardware_key(keyfile):
69    """Load a 128/256/512-bit key, similar to stored in efuse, from a file
70
71    128-bit keys will be extended to 256-bit using the SHA256 of the key
72    192-bit keys will be extended to 256-bit using the same algorithm used
73    by hardware if 3/4 Coding Scheme is set.
74    """
75    key = keyfile.read()
76    if len(key) not in [16, 24, 32, 64]:
77        raise esptool.FatalError(
78            "Key file contains wrong length (%d bytes), 16, 24, 32 or 64 expected."
79            % len(key)
80        )
81    if len(key) == 16:
82        key = _sha256_digest(key)
83        print("Using 128-bit key (extended)")
84    elif len(key) == 24:
85        key = key + key[8:16]
86        assert len(key) == 32
87        print("Using 192-bit key (extended)")
88    elif len(key) == 32:
89        print("Using 256-bit key")
90    else:
91        print("Using 512-bit key")
92    return key
93
94
95def digest_secure_bootloader(args):
96    """Calculate the digest of a bootloader image, in the same way the hardware
97    secure boot engine would do so. Can be used with a pre-loaded key to update a
98    secure bootloader."""
99    _check_output_is_not_input(args.keyfile, args.output)
100    _check_output_is_not_input(args.image, args.output)
101    _check_output_is_not_input(args.iv, args.output)
102    if args.iv is not None:
103        print("WARNING: --iv argument is for TESTING PURPOSES ONLY")
104        iv = args.iv.read(128)
105    else:
106        iv = os.urandom(128)
107    plaintext_image = args.image.read()
108    args.image.seek(0)
109
110    # secure boot engine reads in 128 byte blocks (ie SHA512 block
111    # size), but also doesn't look for any appended SHA-256 digest
112    fw_image = esptool.bin_image.ESP32FirmwareImage(args.image)
113    if fw_image.append_digest:
114        if len(plaintext_image) % 128 <= 32:
115            # ROM bootloader will read to the end of the 128 byte block, but not
116            # to the end of the SHA-256 digest at the end
117            new_len = len(plaintext_image) - (len(plaintext_image) % 128)
118            plaintext_image = plaintext_image[:new_len]
119
120    # if image isn't 128 byte multiple then pad with 0xFF (ie unwritten flash)
121    # as this is what the secure boot engine will see
122    if len(plaintext_image) % 128 != 0:
123        plaintext_image += b"\xFF" * (128 - (len(plaintext_image) % 128))
124
125    plaintext = iv + plaintext_image
126
127    # Secure Boot digest algorithm in hardware uses AES256 ECB to
128    # produce a ciphertext, then feeds output through SHA-512 to
129    # produce the digest. Each block in/out of ECB is reordered
130    # (due to hardware quirks not for security.)
131
132    key = _load_hardware_key(args.keyfile)
133    backend = default_backend()
134    cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend)
135    encryptor = cipher.encryptor()
136    digest = hashlib.sha512()
137
138    for block in get_chunks(plaintext, 16):
139        block = block[::-1]  # reverse each input block
140
141        cipher_block = encryptor.update(block)
142        # reverse and then byte swap each word in the output block
143        cipher_block = cipher_block[::-1]
144        for block in get_chunks(cipher_block, 4):
145            # Python hashlib can build each SHA block internally
146            digest.update(block[::-1])
147
148    if args.output is None:
149        args.output = os.path.splitext(args.image.name)[0] + "-digest-0x0000.bin"
150    with open(args.output, "wb") as f:
151        f.write(iv)
152        digest = digest.digest()
153        for word in get_chunks(digest, 4):
154            f.write(word[::-1])  # swap word order in the result
155        f.write(b"\xFF" * (0x1000 - f.tell()))  # pad to 0x1000
156        f.write(plaintext_image)
157    print("digest+image written to %s" % args.output)
158
159
160def _generate_ecdsa_signing_key(curve_id, keyfile):
161    sk = ecdsa.SigningKey.generate(curve=curve_id)
162    with open(keyfile, "wb") as f:
163        f.write(sk.to_pem())
164
165
166def generate_signing_key(args):
167    if os.path.exists(args.keyfile):
168        raise esptool.FatalError("ERROR: Key file %s already exists" % args.keyfile)
169    if args.version == "1":
170        if hasattr(args, "scheme"):
171            if args.scheme != "ecdsa256" and args.scheme is not None:
172                raise esptool.FatalError("ERROR: V1 only supports ECDSA256")
173        """
174        Generate an ECDSA signing key for signing secure boot images (post-bootloader)
175        """
176        _generate_ecdsa_signing_key(ecdsa.NIST256p, args.keyfile)
177        print("ECDSA NIST256p private key in PEM format written to %s" % args.keyfile)
178    elif args.version == "2":
179        if args.scheme == "rsa3072" or args.scheme is None:
180            """Generate a RSA 3072 signing key for signing secure boot images"""
181            private_key = rsa.generate_private_key(
182                public_exponent=65537, key_size=3072, backend=default_backend()
183            ).private_bytes(
184                encoding=serialization.Encoding.PEM,
185                format=serialization.PrivateFormat.TraditionalOpenSSL,
186                encryption_algorithm=serialization.NoEncryption(),
187            )
188            with open(args.keyfile, "wb") as f:
189                f.write(private_key)
190            print(f"RSA 3072 private key in PEM format written to {args.keyfile}")
191        elif args.scheme == "ecdsa192":
192            """Generate a ECDSA 192 signing key for signing secure boot images"""
193            _generate_ecdsa_signing_key(ecdsa.NIST192p, args.keyfile)
194            print(f"ECDSA NIST192p private key in PEM format written to {args.keyfile}")
195        elif args.scheme == "ecdsa256":
196            """Generate a ECDSA 256 signing key for signing secure boot images"""
197            _generate_ecdsa_signing_key(ecdsa.NIST256p, args.keyfile)
198            print(f"ECDSA NIST256p private key in PEM format written to {args.keyfile}")
199        elif args.scheme == "ecdsa384":
200            """Generate a ECDSA 384 signing key for signing secure boot images"""
201            _generate_ecdsa_signing_key(ecdsa.NIST384p, args.keyfile)
202            print(f"ECDSA NIST384p private key in PEM format written to {args.keyfile}")
203        else:
204            raise esptool.FatalError("ERROR: Unsupported signing scheme {args.scheme}")
205
206
207def load_ecdsa_signing_key(keyfile):
208    """Load ECDSA signing key"""
209    try:
210        sk = ecdsa.SigningKey.from_pem(keyfile.read())
211    except ValueError:
212        raise esptool.FatalError(
213            "Incorrect ECDSA private key specified. "
214            "Please check algorithm and/or format."
215        )
216    if sk.curve not in [ecdsa.NIST192p, ecdsa.NIST256p]:
217        raise esptool.FatalError("Supports NIST192p and NIST256p keys only")
218    return sk
219
220
221def _load_ecdsa_signing_key(keyfile):
222    """Load ECDSA signing key for Secure Boot V1 only"""
223    sk = load_ecdsa_signing_key(keyfile)
224    if sk.curve != ecdsa.NIST256p:
225        raise esptool.FatalError(
226            "Signing key uses incorrect curve. ESP32 Secure Boot only supports "
227            "NIST256p (openssl calls this curve 'prime256v1')"
228        )
229    return sk
230
231
232def _load_ecdsa_verifying_key(keyfile):
233    """Load ECDSA verifying key for Secure Boot V1 only"""
234    try:
235        vk = ecdsa.VerifyingKey.from_pem(keyfile.read())
236    except ValueError:
237        raise esptool.FatalError(
238            "Incorrect ECDSA public key specified. "
239            "Please check algorithm and/or format."
240        )
241    if vk.curve != ecdsa.NIST256p:
242        raise esptool.FatalError(
243            "Signing key uses incorrect curve. ESP32 Secure Boot only supports "
244            "NIST256p (openssl calls this curve 'prime256v1')"
245        )
246    return vk
247
248
249def _load_sbv2_signing_key(keydata):
250    """
251    Load Secure Boot V2 signing key
252
253    can be rsa.RSAPrivateKey or ec.EllipticCurvePrivateKey
254    """
255    sk = serialization.load_pem_private_key(
256        keydata, password=None, backend=default_backend()
257    )
258    if isinstance(sk, rsa.RSAPrivateKey):
259        if sk.key_size != 3072:
260            raise esptool.FatalError(
261                "Key file has length %d bits. Secure boot v2 only supports RSA-3072."
262                % sk.key_size
263            )
264        return sk
265    if isinstance(sk, ec.EllipticCurvePrivateKey):
266        if not isinstance(sk.curve, (ec.SECP192R1, ec.SECP256R1, ec.SECP384R1)):
267            raise esptool.FatalError(
268                "Key file uses incorrect curve. Secure Boot V2 + ECDSA only supports "
269                "NIST192p, NIST256p, NIST384p (aka prime192v1 / secp192r1, prime256v1 / secp256r1, secp384r1)"
270            )
271        return sk
272
273    raise esptool.FatalError("Unsupported signing key for Secure Boot V2")
274
275
276def _load_sbv2_pub_key(keydata):
277    """
278    Load Secure Boot V2 public key, can be rsa.RSAPublicKey or ec.EllipticCurvePublicKey
279    """
280    vk = serialization.load_pem_public_key(keydata, backend=default_backend())
281    if isinstance(vk, rsa.RSAPublicKey):
282        if vk.key_size != 3072:
283            raise esptool.FatalError(
284                "Key file has length %d bits. Secure boot v2 only supports RSA-3072."
285                % vk.key_size
286            )
287        return vk
288    if isinstance(vk, ec.EllipticCurvePublicKey):
289        if not isinstance(vk.curve, (ec.SECP192R1, ec.SECP256R1, ec.SECP384R1)):
290            raise esptool.FatalError(
291                "Key file uses incorrect curve. Secure Boot V2 + ECDSA only supports "
292                "NIST192p, NIST256p, NIST384p (aka prime192v1 / secp192r1, prime256v1 / secp256r1, secp384r1)"
293            )
294        return vk
295
296    raise esptool.FatalError("Unsupported public key for Secure Boot V2")
297
298
299def _get_sbv2_pub_key(keyfile):
300    key_data = keyfile.read()
301    if (
302        b"-BEGIN RSA PRIVATE KEY" in key_data
303        or b"-BEGIN EC PRIVATE KEY" in key_data
304        or b"-BEGIN PRIVATE KEY" in key_data
305    ):
306        return _load_sbv2_signing_key(key_data).public_key()
307    elif b"-BEGIN PUBLIC KEY" in key_data:
308        vk = _load_sbv2_pub_key(key_data)
309    else:
310        raise esptool.FatalError(
311            "Verification key does not appear to be an RSA Private or "
312            "Public key in PEM format. Unsupported"
313        )
314    return vk
315
316
317def _get_sbv2_rsa_primitives(public_key):
318    primitives = namedtuple("primitives", ["n", "e", "m", "rinv"])
319    numbers = public_key.public_numbers()
320    primitives.n = numbers.n  #
321    primitives.e = numbers.e  # two public key components
322
323    # Note: this cheats and calls a private 'rsa' method to get the modular
324    # inverse calculation.
325    primitives.m = -rsa._modinv(primitives.n, 1 << 32)
326
327    rr = 1 << (public_key.key_size * 2)
328    primitives.rinv = rr % primitives.n
329    return primitives
330
331
332def _microecc_format(a, b, curve_len):
333    """
334    Given two numbers (curve coordinates or (r,s) signature), write them out as a
335    little-endian byte sequence suitable for micro-ecc
336    "native little endian" mode
337    """
338    byte_len = int(curve_len / 8)
339    ab = int_to_bytes(a, byte_len)[::-1] + int_to_bytes(b, byte_len)[::-1]
340    assert len(ab) in [48, 64, 96]
341    return ab
342
343
344def sign_data(args):
345    if args.keyfile:
346        _check_output_is_not_input(args.keyfile, args.output)
347    _check_output_is_not_input(args.datafile, args.output)
348    if args.version == "1":
349        return sign_secure_boot_v1(args)
350    elif args.version == "2":
351        return sign_secure_boot_v2(args)
352
353
354def sign_secure_boot_v1(args):
355    """
356    Sign a data file with a ECDSA private key, append binary signature to file contents
357    """
358    binary_content = args.datafile.read()
359
360    if args.hsm:
361        raise esptool.FatalError(
362            "Secure Boot V1 does not support signing using an "
363            "external Hardware Security Module (HSM)"
364        )
365
366    if args.signature:
367        print("Pre-calculated signatures found")
368        if len(args.pub_key) > 1:
369            raise esptool.FatalError("Secure Boot V1 only supports one signing key")
370        signature = args.signature[0].read()
371        # get verifying/public key
372        vk = _load_ecdsa_verifying_key(args.pub_key[0])
373    else:
374        if len(args.keyfile) > 1:
375            raise esptool.FatalError("Secure Boot V1 only supports one signing key")
376        sk = _load_ecdsa_signing_key(args.keyfile[0])
377
378        # calculate signature of binary data
379        signature = sk.sign_deterministic(binary_content, hashlib.sha256)
380        # get verifying/public key
381        vk = sk.get_verifying_key()
382
383    # back-verify signature
384    vk.verify(signature, binary_content, hashlib.sha256)  # throws exception on failure
385    if args.output is None or os.path.abspath(args.output) == os.path.abspath(
386        args.datafile.name
387    ):  # append signature to input file
388        args.datafile.close()
389        outfile = open(args.datafile.name, "ab")
390    else:  # write file & signature to new file
391        outfile = open(args.output, "wb")
392        outfile.write(binary_content)
393    outfile.write(
394        struct.pack("I", 0)
395    )  # Version indicator, allow for different curves/formats later
396    outfile.write(signature)
397    outfile.close()
398    print("Signed %d bytes of data from %s" % (len(binary_content), args.datafile.name))
399
400
401def sign_secure_boot_v2(args):
402    """
403    Sign a firmware app image with an RSA private key using RSA-PSS,
404    or ECDSA private key using P192 or P256 or P384.
405
406    Write output file with a Secure Boot V2 header appended.
407    """
408    SIG_BLOCK_MAX_COUNT = 3
409    contents = args.datafile.read()
410    sig_block_num = 0
411    signature_sector = b""
412
413    signature = args.signature
414    pub_key = args.pub_key
415
416    if len(contents) % SECTOR_SIZE != 0:
417        if args.signature:
418            raise esptool.FatalError(
419                "Secure Boot V2 requires the signature block to start "
420                "from a 4KB aligned sector "
421                "but the datafile supplied is not sector aligned."
422            )
423        else:
424            pad_by = SECTOR_SIZE - (len(contents) % SECTOR_SIZE)
425            print(
426                f"Padding data contents by {pad_by} bytes "
427                "so signature sector aligns at sector boundary"
428            )
429            contents += b"\xff" * pad_by
430
431    elif args.append_signatures:
432        while sig_block_num < SIG_BLOCK_MAX_COUNT:
433            sig_block = validate_signature_block(contents, sig_block_num)
434            if sig_block is None:
435                break
436            signature_sector += (
437                sig_block  # Signature sector is populated with already valid blocks
438            )
439            sig_block_num += 1
440
441        if len(signature_sector) % SIG_BLOCK_SIZE != 0:
442            raise esptool.FatalError("Incorrect signature sector size")
443
444        if sig_block_num == 0:
445            print(
446                "No valid signature blocks found. "
447                "Discarding --append-signature and proceeding to sign the image afresh."
448            )
449        else:
450            print(
451                f"{sig_block_num} valid signature block(s) already present "
452                "in the signature sector."
453            )
454            if sig_block_num == SIG_BLOCK_MAX_COUNT:
455                raise esptool.FatalError(
456                    f"Upto {SIG_BLOCK_MAX_COUNT} signature blocks are supported. "
457                    "(For ESP32-ECO3 only 1 signature block is supported)"
458                )
459
460            # Signature stripped off the content
461            # (the legitimate blocks are included in signature_sector)
462            contents = contents[: len(contents) - SECTOR_SIZE]
463
464    if args.hsm:
465        if args.hsm_config is None:
466            raise esptool.FatalError(
467                "Config file is required to generate signature using an external HSM."
468            )
469        import espsecure.esp_hsm_sign as hsm
470
471        try:
472            config = hsm.read_hsm_config(args.hsm_config)
473        except Exception as e:
474            raise esptool.FatalError(f"Incorrect HSM config file format ({e})")
475        if pub_key is None:
476            pub_key = extract_pubkey_from_hsm(config)
477        signature = generate_signature_using_hsm(config, contents)
478
479    if signature:
480        print("Pre-calculated signatures found")
481        key_count = len(pub_key)
482        if len(signature) != key_count:
483            raise esptool.FatalError(
484                f"Number of public keys ({key_count}) not equal to "
485                f"the number of signatures {len(signature)}."
486            )
487    else:
488        key_count = len(args.keyfile)
489
490    empty_signature_blocks = SIG_BLOCK_MAX_COUNT - sig_block_num
491    if key_count > empty_signature_blocks:
492        raise esptool.FatalError(
493            f"Number of keys({key_count}) more than the empty signature blocks."
494            f"({empty_signature_blocks})"
495        )
496
497    print(f"{key_count} signing key(s) found.")
498
499    # Generate signature block using pre-calculated signatures
500    if signature:
501        signature_block = generate_signature_block_using_pre_calculated_signature(
502            signature, pub_key, contents
503        )
504    # Generate signature block by signing using private keys
505    else:
506        signature_block = generate_signature_block_using_private_key(
507            args.keyfile, contents
508        )
509
510    if signature_block is None or len(signature_block) == 0:
511        raise esptool.FatalError("Signature Block generation failed")
512
513    signature_sector += signature_block
514
515    if (
516        len(signature_sector) < 0
517        and len(signature_sector) > SIG_BLOCK_SIZE * 3
518        and len(signature_sector) % SIG_BLOCK_SIZE != 0
519    ):
520        raise esptool.FatalError("Incorrect signature sector generation")
521
522    total_sig_blocks = len(signature_sector) // SIG_BLOCK_SIZE
523
524    # Pad signature_sector to sector
525    signature_sector = signature_sector + (
526        b"\xff" * (SECTOR_SIZE - len(signature_sector))
527    )
528    if len(signature_sector) != SECTOR_SIZE:
529        raise esptool.FatalError("Incorrect signature sector size")
530
531    # Write to output file, or append to existing file
532    if args.output is None:
533        args.datafile.close()
534        args.output = args.datafile.name
535    with open(args.output, "wb") as f:
536        f.write(contents + signature_sector)
537    print(
538        f"Signed {len(contents)} bytes of data from {args.datafile.name}. "
539        f"Signature sector now has {total_sig_blocks} signature blocks."
540    )
541
542
543def generate_signature_using_hsm(config, contents):
544    import espsecure.esp_hsm_sign as hsm
545
546    session = hsm.establish_session(config)
547    # get the private key
548    private_key = hsm.get_privkey_info(session, config)
549    # Sign payload
550    signature = hsm.sign_payload(private_key, contents)
551    hsm.close_connection(session)
552    temp_signature_file = tempfile.TemporaryFile()
553    temp_signature_file.write(signature)
554    temp_signature_file.seek(0)
555    return [temp_signature_file]
556
557
558def generate_signature_block_using_pre_calculated_signature(
559    signature, pub_key, contents
560):
561    signature_blocks = b""
562    for sig, pk in zip(signature, pub_key):
563        try:
564            public_key = _get_sbv2_pub_key(pk)
565            signature = sig.read()
566            if isinstance(public_key, rsa.RSAPublicKey):
567                # Calculate digest of data file
568                digest = _sha256_digest(contents)
569                # RSA signature
570                rsa_primitives = _get_sbv2_rsa_primitives(public_key)
571                # Verify the signature
572                public_key.verify(
573                    signature,
574                    digest,
575                    padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32),
576                    utils.Prehashed(hashes.SHA256()),
577                )
578
579                signature_block = generate_rsa_signature_block(
580                    digest, rsa_primitives, signature
581                )
582            else:
583                # ECDSA signature
584                numbers = public_key.public_numbers()
585                if isinstance(numbers.curve, ec.SECP192R1):
586                    curve_len = 192
587                    curve_id = CURVE_ID_P192
588                    hash_type = hashes.SHA256()
589                    digest = _sha256_digest(contents)
590                elif isinstance(numbers.curve, ec.SECP256R1):
591                    curve_len = 256
592                    curve_id = CURVE_ID_P256
593                    hash_type = hashes.SHA256()
594                    digest = _sha256_digest(contents)
595                elif isinstance(numbers.curve, ec.SECP384R1):
596                    curve_len = 384
597                    curve_id = CURVE_ID_P384
598                    hash_type = hashes.SHA384()
599                    digest = _sha384_digest(contents)
600                else:
601                    raise esptool.FatalError("Invalid ECDSA curve instance.")
602
603                # Verify the signature
604                public_key.verify(
605                    signature, digest, ec.ECDSA(utils.Prehashed(hash_type))
606                )
607
608                pubkey_point = _microecc_format(numbers.x, numbers.y, curve_len)
609                r, s = utils.decode_dss_signature(signature)
610                signature_rs = _microecc_format(r, s, curve_len)
611                signature_block = generate_ecdsa_signature_block(
612                    digest, curve_id, pubkey_point, signature_rs
613                )
614        except exceptions.InvalidSignature:
615            raise esptool.FatalError(
616                "Signature verification failed: Invalid Signature\n"
617                "The pre-calculated signature has not been signed "
618                "using the given public key"
619            )
620        signature_block += struct.pack("<I", zlib.crc32(signature_block) & 0xFFFFFFFF)
621        signature_block += b"\x00" * 16  # padding
622
623        if len(signature_block) != SIG_BLOCK_SIZE:
624            raise esptool.FatalError("Incorrect signature block size")
625
626        signature_blocks += signature_block
627    return signature_blocks
628
629
630def generate_signature_block_using_private_key(keyfiles, contents):
631    signature_blocks = b""
632    for keyfile in keyfiles:
633        private_key = _load_sbv2_signing_key(keyfile.read())
634
635        # Sign
636        if isinstance(private_key, rsa.RSAPrivateKey):
637            digest = _sha256_digest(contents)
638            # RSA signature
639            signature = private_key.sign(
640                digest,
641                padding.PSS(
642                    mgf=padding.MGF1(hashes.SHA256()),
643                    salt_length=32,
644                ),
645                utils.Prehashed(hashes.SHA256()),
646            )
647            rsa_primitives = _get_sbv2_rsa_primitives(private_key.public_key())
648            signature_block = generate_rsa_signature_block(
649                digest, rsa_primitives, signature
650            )
651        else:
652            numbers = private_key.public_key().public_numbers()
653            if isinstance(private_key.curve, ec.SECP192R1):
654                curve_len = 192
655                curve_id = CURVE_ID_P192
656                hash_type = hashes.SHA256()
657                digest = _sha256_digest(contents)
658            elif isinstance(numbers.curve, ec.SECP256R1):
659                curve_len = 256
660                curve_id = CURVE_ID_P256
661                hash_type = hashes.SHA256()
662                digest = _sha256_digest(contents)
663            elif isinstance(numbers.curve, ec.SECP384R1):
664                curve_len = 384
665                curve_id = CURVE_ID_P384
666                hash_type = hashes.SHA384()
667                digest = _sha384_digest(contents)
668            else:
669                raise esptool.FatalError("Invalid ECDSA curve instance.")
670
671            # ECDSA signatures
672            signature = private_key.sign(digest, ec.ECDSA(utils.Prehashed(hash_type)))
673
674            pubkey_point = _microecc_format(numbers.x, numbers.y, curve_len)
675
676            r, s = utils.decode_dss_signature(signature)
677            signature_rs = _microecc_format(r, s, curve_len)
678            signature_block = generate_ecdsa_signature_block(
679                digest, curve_id, pubkey_point, signature_rs
680            )
681
682        signature_block += struct.pack("<I", zlib.crc32(signature_block) & 0xFFFFFFFF)
683        signature_block += b"\x00" * 16  # padding
684
685        if len(signature_block) != SIG_BLOCK_SIZE:
686            raise esptool.FatalError("Incorrect signature block size")
687
688        signature_blocks += signature_block
689    return signature_blocks
690
691
692def generate_rsa_signature_block(digest, rsa_primitives, signature):
693    """
694    Encode in rsa signature block format
695
696    Note: the [::-1] is to byte swap all of the bignum
697    values (signatures, coefficients) to little endian
698    for use with the RSA peripheral, rather than big endian
699    which is conventionally used for RSA.
700    """
701    signature_block = struct.pack(
702        "<BBxx32s384sI384sI384s",
703        SIG_BLOCK_MAGIC,
704        SIG_BLOCK_VERSION_RSA,
705        digest,
706        int_to_bytes(rsa_primitives.n)[::-1],
707        rsa_primitives.e,
708        int_to_bytes(rsa_primitives.rinv)[::-1],
709        rsa_primitives.m & 0xFFFFFFFF,
710        signature[::-1],
711    )
712    return signature_block
713
714
715def generate_ecdsa_signature_block(digest, curve_id, pubkey_point, signature_rs):
716    """
717    Encode in rsa signature block format
718
719    # block is padded out to the much larger size
720    # of the RSA version of this structure
721    """
722
723    if curve_id in [CURVE_ID_P192, CURVE_ID_P256]:
724        signature_block = struct.pack(
725            "<BBBx32sB64s64s1031x",
726            SIG_BLOCK_MAGIC,
727            SIG_BLOCK_VERSION_ECDSA,
728            ECDSA_SHA_256,
729            digest,
730            curve_id,
731            pubkey_point,
732            signature_rs,
733        )
734    elif curve_id == CURVE_ID_P384:
735        signature_block = struct.pack(
736            "<BBBx48sB96s96s951x",
737            SIG_BLOCK_MAGIC,
738            SIG_BLOCK_VERSION_ECDSA,
739            ECDSA_SHA_384,
740            digest,
741            curve_id,
742            pubkey_point,
743            signature_rs,
744        )
745    else:
746        raise esptool.FatalError(
747            "Invalid ECDSA curve ID detected while generating ECDSA signature block."
748        )
749
750    return signature_block
751
752
753def verify_signature(args):
754    if args.version == "1":
755        return verify_signature_v1(args)
756    elif args.version == "2":
757        return verify_signature_v2(args)
758
759
760def verify_signature_v1(args):
761    """Verify a previously signed binary image, using the ECDSA public key"""
762    key_data = args.keyfile.read()
763    if b"-BEGIN EC PRIVATE KEY" in key_data:
764        sk = ecdsa.SigningKey.from_pem(key_data)
765        vk = sk.get_verifying_key()
766    elif b"-BEGIN PUBLIC KEY" in key_data:
767        vk = ecdsa.VerifyingKey.from_pem(key_data)
768    elif len(key_data) == 64:
769        vk = ecdsa.VerifyingKey.from_string(key_data, curve=ecdsa.NIST256p)
770    else:
771        raise esptool.FatalError(
772            "Verification key does not appear to be an EC key in PEM format "
773            "or binary EC public key data. Unsupported"
774        )
775
776    if vk.curve != ecdsa.NIST256p:
777        raise esptool.FatalError(
778            "Public key uses incorrect curve. ESP32 Secure Boot only supports "
779            "NIST256p (openssl calls this curve 'prime256v1"
780        )
781
782    binary_content = args.datafile.read()
783    data = binary_content[0:-68]
784    sig_version, signature = struct.unpack("I64s", binary_content[-68:])
785    if sig_version != 0:
786        raise esptool.FatalError(
787            "Signature block has version %d. This version of espsecure "
788            "only supports version 0." % sig_version
789        )
790    print("Verifying %d bytes of data" % len(data))
791    try:
792        if vk.verify(signature, data, hashlib.sha256):
793            print("Signature is valid")
794        else:
795            raise esptool.FatalError("Signature is not valid")
796    except ecdsa.keys.BadSignatureError:
797        raise esptool.FatalError("Signature is not valid")
798
799
800def validate_signature_block(image_content, sig_blk_num):
801    offset = -SECTOR_SIZE + sig_blk_num * SIG_BLOCK_SIZE
802    sig_blk = image_content[offset : offset + SIG_BLOCK_SIZE]
803    assert len(sig_blk) == SIG_BLOCK_SIZE
804
805    # note: in case of ECDSA key, the exact fields in the middle are wrong
806    # (but unused here)
807    magic, version, _, _, _, _, _, _, blk_crc = struct.unpack(
808        "<BBxx32s384sI384sI384sI16x", sig_blk
809    )
810
811    # The signature block(1216 bytes) consists of the data part(1196 bytes)
812    # followed by a crc32(4 byte) and a 16 byte pad.
813    calc_crc = zlib.crc32(sig_blk[:1196])
814
815    is_invalid_block = magic != SIG_BLOCK_MAGIC
816    is_invalid_block |= version not in [SIG_BLOCK_VERSION_RSA, SIG_BLOCK_VERSION_ECDSA]
817
818    if is_invalid_block or blk_crc != calc_crc & 0xFFFFFFFF:  # Signature block invalid
819        return None
820    key_type = "RSA" if version == SIG_BLOCK_VERSION_RSA else "ECDSA"
821    print(f"Signature block {sig_blk_num} is valid ({key_type}).")
822    return sig_blk
823
824
825def verify_signature_v2(args):
826    """Verify a previously signed binary image, using the RSA or ECDSA public key"""
827
828    keyfile = args.keyfile
829    if args.hsm:
830        if args.hsm_config is None:
831            raise esptool.FatalError(
832                "Config file is required to extract public key from an external HSM."
833            )
834        import espsecure.esp_hsm_sign as hsm
835
836        try:
837            config = hsm.read_hsm_config(args.hsm_config)
838        except Exception as e:
839            raise esptool.FatalError(f"Incorrect HSM config file format ({e})")
840        # get public key from HSM
841        keyfile = extract_pubkey_from_hsm(config)[0]
842
843    vk = _get_sbv2_pub_key(keyfile)
844
845    if isinstance(vk, rsa.RSAPublicKey):
846        SIG_BLOCK_MAX_COUNT = 3
847    elif isinstance(vk, ec.EllipticCurvePublicKey):
848        SIG_BLOCK_MAX_COUNT = 1
849
850    image_content = args.datafile.read()
851    if len(image_content) < SECTOR_SIZE or len(image_content) % SECTOR_SIZE != 0:
852        raise esptool.FatalError(
853            "Invalid datafile. Data size should be non-zero & a multiple of 4096."
854        )
855
856    valid = False
857
858    for sig_blk_num in range(SIG_BLOCK_MAX_COUNT):
859        sig_blk = validate_signature_block(image_content, sig_blk_num)
860        if sig_blk is None:
861            print(f"Signature block {sig_blk_num} invalid. Skipping.")
862            continue
863        _, version, ecdsa_sha_version = struct.unpack("<BBBx", sig_blk[:4])
864
865        if version == SIG_BLOCK_VERSION_ECDSA and ecdsa_sha_version == ECDSA_SHA_384:
866            blk_digest = struct.unpack("<48s", sig_blk[4:52])[0]
867            digest = _sha384_digest(image_content[:-SECTOR_SIZE])
868        else:
869            blk_digest = struct.unpack("<32s", sig_blk[4:36])[0]
870            digest = _sha256_digest(image_content[:-SECTOR_SIZE])
871
872        if blk_digest != digest:
873            raise esptool.FatalError(
874                "Signature block image digest does not match "
875                f"the actual image digest {digest}. Expected {blk_digest}."
876            )
877
878        try:
879            if isinstance(vk, rsa.RSAPublicKey):
880                _, _, _, _, signature, _ = struct.unpack(
881                    "<384sI384sI384sI16x", sig_blk[36:]
882                )
883                vk.verify(
884                    signature[::-1],
885                    digest,
886                    padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32),
887                    utils.Prehashed(hashes.SHA256()),
888                )
889            else:
890                if ecdsa_sha_version == ECDSA_SHA_256:
891                    curve_id, _pubkey, encoded_rs = struct.unpack(
892                        "B64s64s1031x4x16x", sig_blk[36:]
893                    )
894                elif ecdsa_sha_version == ECDSA_SHA_384:
895                    curve_id, _pubkey, encoded_rs = struct.unpack(
896                        "B96s96s951x4x16x", sig_blk[52:]
897                    )
898
899                assert curve_id in (CURVE_ID_P192, CURVE_ID_P256, CURVE_ID_P384)
900
901                # length of each number in the keypair
902                if curve_id == CURVE_ID_P192:
903                    keylen = 24
904                    hash_type = hashes.SHA256()
905                elif curve_id == CURVE_ID_P256:
906                    keylen = 32
907                    hash_type = hashes.SHA256()
908                elif curve_id == CURVE_ID_P384:
909                    keylen = 48
910                    hash_type = hashes.SHA384()
911
912                r = int.from_bytes(encoded_rs[:keylen], "little")
913                s = int.from_bytes(encoded_rs[keylen : keylen * 2], "little")
914
915                signature = utils.encode_dss_signature(r, s)
916
917                vk.verify(signature, digest, ec.ECDSA(utils.Prehashed(hash_type)))
918
919            key_type = "RSA" if isinstance(vk, rsa.RSAPublicKey) else "ECDSA"
920
921            print(
922                f"Signature block {sig_blk_num} verification successful using "
923                f"the supplied key ({key_type})."
924            )
925            valid = True
926
927        except exceptions.InvalidSignature:
928            print(
929                f"Signature block {sig_blk_num} is not signed by the supplied key. "
930                "Checking the next block"
931            )
932            continue
933
934    if not valid:
935        raise esptool.FatalError(
936            "Checked all blocks. Signature could not be verified with the provided key."
937        )
938
939
940def extract_public_key(args):
941    _check_output_is_not_input(args.keyfile, args.public_keyfile)
942    if args.version == "1":
943        """
944        Load an ECDSA private key and extract the embedded public key
945        as raw binary data.
946        """
947        sk = _load_ecdsa_signing_key(args.keyfile)
948        vk = sk.get_verifying_key()
949        args.public_keyfile.write(vk.to_string())
950    elif args.version == "2":
951        """
952        Load an RSA or an ECDSA private key and extract the public key
953        as raw binary data.
954        """
955        sk = _load_sbv2_signing_key(args.keyfile.read())
956        vk = sk.public_key().public_bytes(
957            encoding=serialization.Encoding.PEM,
958            format=serialization.PublicFormat.SubjectPublicKeyInfo,
959        )
960        args.public_keyfile.write(vk)
961    print(
962        "%s public key extracted to %s" % (args.keyfile.name, args.public_keyfile.name)
963    )
964
965
966def extract_pubkey_from_hsm(config):
967    import espsecure.esp_hsm_sign as hsm
968
969    session = hsm.establish_session(config)
970    # get public key from HSM
971    public_key = hsm.get_pubkey(session, config)
972    hsm.close_connection(session)
973
974    pem = public_key.public_bytes(
975        encoding=serialization.Encoding.PEM,
976        format=serialization.PublicFormat.SubjectPublicKeyInfo,
977    )
978    temp_pub_key_file = tempfile.TemporaryFile()
979    temp_pub_key_file.write(pem)
980    temp_pub_key_file.seek(0)
981    return [temp_pub_key_file]
982
983
984def _sha256_digest(data):
985    digest = hashlib.sha256()
986    digest.update(data)
987    return digest.digest()
988
989
990def _sha384_digest(contents):
991    # Calculate digest of data file
992    digest = hashlib.sha384()
993    digest.update(contents)
994    return digest.digest()
995
996
997def signature_info_v2(args):
998    """
999    Validates the signature block and prints the RSA/ECDSA public key
1000    digest for valid blocks
1001    """
1002    SIG_BLOCK_MAX_COUNT = 3
1003
1004    image_content = args.datafile.read()
1005    if len(image_content) < SECTOR_SIZE or len(image_content) % SECTOR_SIZE != 0:
1006        raise esptool.FatalError(
1007            "Invalid datafile. Data size should be non-zero & a multiple of 4096."
1008        )
1009
1010    for sig_blk_num in range(SIG_BLOCK_MAX_COUNT):
1011        sig_blk = validate_signature_block(image_content, sig_blk_num)
1012        if sig_blk is None:
1013            print(
1014                "Signature block %d absent/invalid. Skipping checking next blocks."
1015                % sig_blk_num
1016            )
1017            return
1018
1019        _, version, ecdsa_sha_version = struct.unpack("<BBBx", sig_blk[:4])
1020
1021        if version == SIG_BLOCK_VERSION_ECDSA and ecdsa_sha_version == ECDSA_SHA_384:
1022            sig_data = struct.unpack("<BBxx48s1164x", sig_blk)
1023            digest = _sha384_digest(image_content[:-SECTOR_SIZE])
1024        else:
1025            sig_data = struct.unpack("<BBxx32s1180x", sig_blk)
1026            digest = _sha256_digest(image_content[:-SECTOR_SIZE])
1027
1028        if sig_data[2] != digest:
1029            raise esptool.FatalError(
1030                "Digest in signature block %d doesn't match the image digest."
1031                % (sig_blk_num)
1032            )
1033
1034        offset = -SECTOR_SIZE + sig_blk_num * SIG_BLOCK_SIZE
1035        sig_blk = image_content[offset : offset + SIG_BLOCK_SIZE]
1036        if sig_data[1] == SIG_BLOCK_VERSION_RSA:
1037            key_digest = _sha256_digest(sig_blk[36:812])
1038        elif sig_data[1] == SIG_BLOCK_VERSION_ECDSA:
1039            if ecdsa_sha_version == ECDSA_SHA_384:
1040                key_digest = _sha256_digest(sig_blk[52:149])
1041            else:
1042                key_digest = _sha256_digest(sig_blk[36:101])
1043        else:
1044            raise esptool.FatalError(
1045                "Unsupported scheme in signature block %d" % (sig_blk_num)
1046            )
1047
1048        print(
1049            "Public key digest for block %d: %s"
1050            % (sig_blk_num, " ".join("{:02x}".format(c) for c in bytearray(key_digest)))
1051        )
1052
1053
1054def _digest_sbv2_public_key(keyfile):
1055    public_key = _get_sbv2_pub_key(keyfile)
1056
1057    if isinstance(public_key, rsa.RSAPublicKey):
1058        rsa_primitives = _get_sbv2_rsa_primitives(public_key)
1059
1060        # Encode in the same way it is represented in the signature block
1061        #
1062        # Note: the [::-1] is to byte swap all of the bignum
1063        # values (signatures, coefficients) to little endian
1064        # for use with the RSA peripheral, rather than big endian
1065        # which is conventionally used for RSA.
1066        binary_format = struct.pack(
1067            "<384sI384sI",
1068            int_to_bytes(rsa_primitives.n)[::-1],
1069            rsa_primitives.e,
1070            int_to_bytes(rsa_primitives.rinv)[::-1],
1071            rsa_primitives.m & 0xFFFFFFFF,
1072        )
1073    else:  # ECC public key
1074        numbers = public_key.public_numbers()
1075        if isinstance(public_key.curve, ec.SECP192R1):
1076            curve_len = 192
1077            curve_id = CURVE_ID_P192
1078        elif isinstance(public_key.curve, ec.SECP256R1):
1079            curve_len = 256
1080            curve_id = CURVE_ID_P256
1081        elif isinstance(public_key.curve, ec.SECP384R1):
1082            curve_len = 384
1083            curve_id = CURVE_ID_P384
1084
1085        pubkey_point = _microecc_format(numbers.x, numbers.y, curve_len)
1086
1087        if curve_id == CURVE_ID_P384:
1088            binary_format = struct.pack(
1089                "<B96s",
1090                curve_id,
1091                pubkey_point,
1092            )
1093        else:
1094            binary_format = struct.pack(
1095                "<B64s",
1096                curve_id,
1097                pubkey_point,
1098            )
1099
1100    return hashlib.sha256(binary_format).digest()
1101
1102
1103def digest_sbv2_public_key(args):
1104    _check_output_is_not_input(args.keyfile, args.output)
1105    public_key_digest = _digest_sbv2_public_key(args.keyfile)
1106    with open(args.output, "wb") as f:
1107        print(
1108            "Writing the public key digest of %s to %s."
1109            % (args.keyfile.name, args.output)
1110        )
1111        f.write(public_key_digest)
1112
1113
1114def digest_rsa_public_key(args):
1115    # Kept for compatibility purpose
1116    digest_sbv2_public_key(args)
1117
1118
1119def digest_private_key(args):
1120    _check_output_is_not_input(args.keyfile, args.digest_file)
1121    sk = _load_ecdsa_signing_key(args.keyfile)
1122    repr(sk.to_string())
1123    digest = hashlib.sha256()
1124    digest.update(sk.to_string())
1125    result = digest.digest()
1126    if args.keylen == 192:
1127        result = result[0:24]
1128    args.digest_file.write(result)
1129    print(
1130        "SHA-256 digest of private key %s%s written to %s"
1131        % (
1132            args.keyfile.name,
1133            "" if args.keylen == 256 else " (truncated to 192 bits)",
1134            args.digest_file.name,
1135        )
1136    )
1137
1138
1139# flash encryption key tweaking pattern: the nth bit of the key is
1140# flipped if the kth bit in the flash offset is set, where mapping
1141# from n to k is provided by this list of 'n' bit offsets (range k)
1142# fmt: off
1143_FLASH_ENCRYPTION_TWEAK_PATTERN = [
1144    23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
1145    23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
1146    23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
1147    14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
1148    23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
1149    23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
1150    23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
1151    12, 11, 10, 9, 8, 7, 6, 5,
1152    23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
1153    23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
1154    23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
1155    10, 9, 8, 7, 6, 5,
1156    23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
1157    23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
1158    23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
1159    8, 7, 6, 5
1160]
1161assert len(_FLASH_ENCRYPTION_TWEAK_PATTERN) == 256
1162# fmt: on
1163
1164
1165def _flash_encryption_tweak_range(flash_crypt_config=0xF):
1166    """Return a list of the bit indexes that the "key tweak" applies to,
1167    as determined by the FLASH_CRYPT_CONFIG 4 bit efuse value.
1168    """
1169    tweak_range = []
1170    if (flash_crypt_config & 1) != 0:
1171        tweak_range += range(67)
1172    if (flash_crypt_config & 2) != 0:
1173        tweak_range += range(67, 132)
1174    if (flash_crypt_config & 4) != 0:
1175        tweak_range += range(132, 195)
1176    if (flash_crypt_config & 8) != 0:
1177        tweak_range += range(195, 256)
1178    return tweak_range
1179
1180
1181def _flash_encryption_tweak_range_bits(flash_crypt_config=0xF):
1182    """Return bits (in reverse order) that the "key tweak" applies to,
1183    as determined by the FLASH_CRYPT_CONFIG 4 bit efuse value.
1184    """
1185    tweak_range = 0
1186    if (flash_crypt_config & 1) != 0:
1187        tweak_range |= (
1188            0xFFFFFFFFFFFFFFFFE00000000000000000000000000000000000000000000000
1189        )
1190    if (flash_crypt_config & 2) != 0:
1191        tweak_range |= (
1192            0x00000000000000001FFFFFFFFFFFFFFFF0000000000000000000000000000000
1193        )
1194    if (flash_crypt_config & 4) != 0:
1195        tweak_range |= (
1196            0x000000000000000000000000000000000FFFFFFFFFFFFFFFE000000000000000
1197        )
1198    if (flash_crypt_config & 8) != 0:
1199        tweak_range |= (
1200            0x0000000000000000000000000000000000000000000000001FFFFFFFFFFFFFFF
1201        )
1202    return tweak_range
1203
1204
1205# Forward bit order masks
1206mul1 = 0x0000200004000080000004000080001000000200004000080000040000800010
1207mul2 = 0x0000000000000000200000000000000010000000000000002000000000000001
1208
1209mul1_mask = 0xFFFFFFFFFFFFFF801FFFFFFFFFFFFFF00FFFFFFFFFFFFFF81FFFFFFFFFFFFFF0
1210mul2_mask = 0x000000000000007FE00000000000000FF000000000000007E00000000000000F
1211
1212
1213def _flash_encryption_tweak_key(key, offset, tweak_range):
1214    """Apply XOR "tweak" values to the key, derived from flash offset
1215    'offset'. This matches the ESP32 hardware flash encryption.
1216
1217    tweak_range is a list of bit indexes to apply the tweak to, as
1218    generated by _flash_encryption_tweak_range() from the
1219    FLASH_CRYPT_CONFIG efuse value.
1220
1221    Return tweaked key
1222    """
1223    addr = offset >> 5
1224    key ^= ((mul1 * addr) | ((mul2 * addr) & mul2_mask)) & tweak_range
1225    return int.to_bytes(key, length=32, byteorder="big", signed=False)
1226
1227
1228def generate_flash_encryption_key(args):
1229    print("Writing %d random bits to key file %s" % (args.keylen, args.key_file.name))
1230    args.key_file.write(os.urandom(args.keylen // 8))
1231
1232
1233def _flash_encryption_operation_esp32(
1234    output_file, input_file, flash_address, keyfile, flash_crypt_conf, do_decrypt
1235):
1236    key = _load_hardware_key(keyfile)
1237
1238    if flash_address % 16 != 0:
1239        raise esptool.FatalError(
1240            "Starting flash address 0x%x must be a multiple of 16" % flash_address
1241        )
1242
1243    if flash_crypt_conf == 0:
1244        print("WARNING: Setting FLASH_CRYPT_CONF to zero is not recommended")
1245
1246    tweak_range = _flash_encryption_tweak_range_bits(flash_crypt_conf)
1247    key = int.from_bytes(key, byteorder="big", signed=False)
1248
1249    backend = default_backend()
1250
1251    cipher = None
1252    block_offs = flash_address
1253    while True:
1254        block = input_file.read(16)
1255        if len(block) == 0:
1256            break
1257        elif len(block) < 16:
1258            if do_decrypt:
1259                raise esptool.FatalError("Data length is not a multiple of 16 bytes")
1260            pad = 16 - len(block)
1261            block = block + os.urandom(pad)
1262            print(
1263                "Note: Padding with %d bytes of random data "
1264                "(encrypted data must be multiple of 16 bytes long)" % pad
1265            )
1266
1267        if block_offs % 32 == 0 or cipher is None:
1268            # each bit of the flash encryption key is XORed with tweak bits
1269            # derived from the offset of 32 byte block of flash
1270            block_key = _flash_encryption_tweak_key(key, block_offs, tweak_range)
1271
1272            if cipher is None:  # first pass
1273                cipher = Cipher(algorithms.AES(block_key), modes.ECB(), backend=backend)
1274
1275                # note AES is used inverted for flash encryption, so
1276                # "decrypting" flash uses AES encrypt algorithm and vice
1277                # versa. (This does not weaken AES.)
1278                actor = cipher.encryptor() if do_decrypt else cipher.decryptor()
1279            else:
1280                # performance hack: changing the key using pyca-cryptography API
1281                # requires recreating'actor'.
1282                # With openssl backend, this re-initializes the openssl cipher context.
1283                # To save some time, manually call EVP_CipherInit_ex() in the openssl
1284                # backend to update the key.
1285                # If it fails, fall back to recreating the entire context via public API
1286                try:
1287                    backend = actor._ctx._backend
1288                    res = backend._lib.EVP_CipherInit_ex(
1289                        actor._ctx._ctx,
1290                        backend._ffi.NULL,
1291                        backend._ffi.NULL,
1292                        backend._ffi.from_buffer(block_key),
1293                        backend._ffi.NULL,
1294                        actor._ctx._operation,
1295                    )
1296                    backend.openssl_assert(res != 0)
1297                except AttributeError:
1298                    # backend is not an openssl backend, or implementation has changed:
1299                    # fall back to the slow safe version
1300                    cipher.algorithm.key = block_key
1301                    actor = cipher.encryptor() if do_decrypt else cipher.decryptor()
1302
1303        block = block[::-1]  # reverse input block byte order
1304        block = actor.update(block)
1305
1306        output_file.write(block[::-1])  # reverse output block byte order
1307        block_offs += 16
1308
1309
1310def _flash_encryption_operation_aes_xts(
1311    output_file, input_file, flash_address, keyfile, do_decrypt
1312):
1313    """
1314    Apply the AES-XTS algorithm with the hardware addressing scheme used by Espressif
1315
1316    key = AES-XTS key (32 or 64 bytes)
1317    flash_address = address in flash to encrypt at. Must be multiple of 16 bytes.
1318    indata = Data to encrypt/decrypt. Must be multiple of 16 bytes.
1319    encrypt = True to Encrypt indata, False to decrypt indata.
1320
1321    Returns a bitstring of the ciphertext or plaintext result.
1322    """
1323
1324    backend = default_backend()
1325    key = _load_hardware_key(keyfile)
1326    indata = input_file.read()
1327
1328    if flash_address % 16 != 0:
1329        raise esptool.FatalError(
1330            "Starting flash address 0x%x must be a multiple of 16" % flash_address
1331        )
1332
1333    if len(indata) % 16 != 0:
1334        raise esptool.FatalError(
1335            "Input data length (%d) must be a multiple of 16" % len(indata)
1336        )
1337
1338    if len(indata) == 0:
1339        raise esptool.FatalError("Input data must be longer than 0")
1340
1341    # left pad for a 1024-bit aligned address
1342    pad_left = flash_address % 0x80
1343    indata = (b"\x00" * pad_left) + indata
1344
1345    # right pad for full 1024-bit blocks
1346    pad_right = len(indata) % 0x80
1347    if pad_right > 0:
1348        pad_right = 0x80 - pad_right
1349    indata = indata + (b"\x00" * pad_right)
1350
1351    inblocks = _split_blocks(indata, 0x80)  # split into 1024 bit blocks
1352
1353    output = []
1354    for inblock in inblocks:  # for each block
1355        tweak = struct.pack("<I", (flash_address & ~0x7F)) + (b"\x00" * 12)
1356        flash_address += 0x80  # for next block
1357
1358        if len(tweak) != 16:
1359            raise esptool.FatalError(
1360                "Length of tweak must be 16, was {}".format(len(tweak))
1361            )
1362
1363        cipher = Cipher(algorithms.AES(key), modes.XTS(tweak), backend=backend)
1364        encryptor = cipher.decryptor() if do_decrypt else cipher.encryptor()
1365
1366        inblock = inblock[::-1]  # reverse input
1367        outblock = encryptor.update(inblock)  # standard algo
1368        output.append(outblock[::-1])  # reverse output
1369
1370    output = b"".join(output)
1371
1372    # undo any padding we applied to the input
1373    if pad_right != 0:
1374        output = output[:-pad_right]
1375    if pad_left != 0:
1376        output = output[pad_left:]
1377
1378    # output length matches original input
1379    if len(output) != len(indata) - pad_left - pad_right:
1380        raise esptool.FatalError(
1381            "Length of input data ({}) should match the output data ({})".format(
1382                len(indata) - pad_left - pad_right, len(output)
1383            )
1384        )
1385
1386    output_file.write(output)
1387
1388
1389def _split_blocks(text, block_len=16):
1390    """Take a bitstring, split it into chunks of "block_len" each"""
1391    assert len(text) % block_len == 0
1392    pos = 0
1393    while pos < len(text):
1394        yield text[pos : pos + block_len]
1395        pos = pos + block_len
1396
1397
1398def decrypt_flash_data(args):
1399    _check_output_is_not_input(args.keyfile, args.output)
1400    _check_output_is_not_input(args.encrypted_file, args.output)
1401    if args.aes_xts:
1402        return _flash_encryption_operation_aes_xts(
1403            args.output, args.encrypted_file, args.address, args.keyfile, True
1404        )
1405    else:
1406        return _flash_encryption_operation_esp32(
1407            args.output,
1408            args.encrypted_file,
1409            args.address,
1410            args.keyfile,
1411            args.flash_crypt_conf,
1412            True,
1413        )
1414
1415
1416def encrypt_flash_data(args):
1417    _check_output_is_not_input(args.keyfile, args.output)
1418    _check_output_is_not_input(args.plaintext_file, args.output)
1419    if args.aes_xts:
1420        return _flash_encryption_operation_aes_xts(
1421            args.output, args.plaintext_file, args.address, args.keyfile, False
1422        )
1423    else:
1424        return _flash_encryption_operation_esp32(
1425            args.output,
1426            args.plaintext_file,
1427            args.address,
1428            args.keyfile,
1429            args.flash_crypt_conf,
1430            False,
1431        )
1432
1433
1434def _samefile(p1, p2):
1435    return os.path.normcase(os.path.normpath(p1)) == os.path.normcase(
1436        os.path.normpath(p2)
1437    )
1438
1439
1440def _check_output_is_not_input(input_file, output_file):
1441    i = getattr(input_file, "name", input_file)
1442    o = getattr(output_file, "name", output_file)
1443    # i & o should be string containing the path to files if espsecure
1444    # was invoked from command line
1445    # i & o still can be something else when espsecure was imported
1446    # and the functions used directly (e.g. io.BytesIO())
1447    check_f = _samefile if isinstance(i, str) and isinstance(o, str) else operator.eq
1448    if check_f(i, o):
1449        raise esptool.FatalError(
1450            'The input "{}" and output "{}" should not be the same!'.format(i, o)
1451        )
1452
1453
1454class OutFileType(object):
1455    """
1456    This class is a replacement of argparse.FileType('wb').
1457    It doesn't create a file immediately but only during thefirst write.
1458    This allows us to do some checking before,
1459    e.g. that we are not overwriting the input.
1460
1461    argparse.FileType('w')('-') returns STDOUT but argparse.FileType('wb') is not.
1462
1463    The file object is not closed on failure
1464    just like in the case of argparse.FileType('w').
1465    """
1466
1467    def __init__(self):
1468        self.path = None
1469        self.file_obj = None
1470
1471    def __call__(self, path):
1472        self.path = path
1473        return self
1474
1475    def __repr__(self):
1476        return "{}({})".format(type(self).__name__, self.path)
1477
1478    def write(self, payload):
1479        if len(payload) > 0:
1480            if not self.file_obj:
1481                self.file_obj = open(self.path, "wb")
1482            self.file_obj.write(payload)
1483
1484    def close(self):
1485        if self.file_obj:
1486            self.file_obj.close()
1487            self.file_obj = None
1488
1489    @property
1490    def name(self):
1491        return self.path
1492
1493
1494def main(custom_commandline=None):
1495    """
1496    Main function for espsecure
1497
1498    custom_commandline - Optional override for default arguments parsing
1499    (that uses sys.argv), can be a list of custom arguments as strings.
1500    Arguments and their values need to be added as individual items to the list
1501    e.g. "--port /dev/ttyUSB1" thus becomes ['--port', '/dev/ttyUSB1'].
1502    """
1503    parser = argparse.ArgumentParser(
1504        description="espsecure.py v%s - ESP32 Secure Boot & Flash Encryption tool"
1505        % esptool.__version__,
1506        prog="espsecure",
1507    )
1508
1509    subparsers = parser.add_subparsers(
1510        dest="operation", help="Run espsecure.py {command} -h for additional help"
1511    )
1512
1513    p = subparsers.add_parser(
1514        "digest_secure_bootloader",
1515        help="Take a bootloader binary image and a secure boot key, "
1516        "and output a combined digest+binary suitable for flashing along "
1517        "with the precalculated secure boot key.",
1518    )
1519    p.add_argument(
1520        "--keyfile",
1521        "-k",
1522        help="256 bit key for secure boot digest.",
1523        type=argparse.FileType("rb"),
1524        required=True,
1525    )
1526    p.add_argument("--output", "-o", help="Output file for signed digest image.")
1527    p.add_argument(
1528        "--iv",
1529        help="128 byte IV file. Supply a file for testing purposes only, "
1530        "if not supplied an IV will be randomly generated.",
1531        type=argparse.FileType("rb"),
1532    )
1533    p.add_argument(
1534        "image",
1535        help="Bootloader image file to calculate digest from",
1536        type=argparse.FileType("rb"),
1537    )
1538
1539    p = subparsers.add_parser(
1540        "generate_signing_key",
1541        help="Generate a private key for signing secure boot images "
1542        "as per the secure boot version. "
1543        "Key file is generated in PEM format, "
1544        "Secure Boot V1 - ECDSA NIST256p private key. "
1545        "Secure Boot V2 - RSA 3072, ECDSA NIST384p, ECDSA NIST256p, ECDSA NIST192p private key.",
1546    )
1547    p.add_argument(
1548        "--version",
1549        "-v",
1550        help="Version of the secure boot signing scheme to use.",
1551        choices=["1", "2"],
1552        default="1",
1553    )
1554    p.add_argument(
1555        "--scheme",
1556        "-s",
1557        help="Scheme of secure boot signing.",
1558        choices=["rsa3072", "ecdsa192", "ecdsa256", "ecdsa384"],
1559        required=False,
1560    )
1561    p.add_argument(
1562        "keyfile", help="Filename for private key file (embedded public key)"
1563    )
1564
1565    p = subparsers.add_parser(
1566        "sign_data",
1567        help="Sign a data file for use with secure boot. "
1568        "Signing algorithm is deterministic ECDSA w/ SHA-512 (V1) "
1569        "or either RSA-PSS or ECDSA w/ SHA-256 or ECDSA w/ SHA-384 (V2).",
1570    )
1571    p.add_argument(
1572        "--version",
1573        "-v",
1574        help="Version of the secure boot signing scheme to use.",
1575        choices=["1", "2"],
1576        required=True,
1577    )
1578    p.add_argument(
1579        "--keyfile",
1580        "-k",
1581        help="Private key file for signing. Key is in PEM format.",
1582        type=argparse.FileType("rb"),
1583        nargs="+",
1584    )
1585    p.add_argument(
1586        "--append_signatures",
1587        "-a",
1588        help="Append signature block(s) to already signed image. "
1589        "Not valid for ESP32 and ESP32-C2.",
1590        action="store_true",
1591    )
1592    p.add_argument(
1593        "--hsm",
1594        help="Use an external Hardware Security Module "
1595        "to generate signature using PKCS#11 interface.",
1596        action="store_true",
1597    )
1598    p.add_argument(
1599        "--hsm-config",
1600        help="Config file for the external Hardware Security Module "
1601        "to be used to generate signature.",
1602        default=None,
1603    )
1604    p.add_argument(
1605        "--pub-key",
1606        help="Public key files corresponding to the private key used to generate "
1607        "the pre-calculated signatures. Keys should be in PEM format.",
1608        type=argparse.FileType("rb"),
1609        nargs="+",
1610    )
1611    p.add_argument(
1612        "--signature",
1613        help="Pre-calculated signatures. "
1614        "Signatures generated using external private keys e.g. keys stored in HSM.",
1615        type=argparse.FileType("rb"),
1616        nargs="+",
1617        default=None,
1618    )
1619    p.add_argument(
1620        "--output",
1621        "-o",
1622        help="Output file for signed digest image. Default is to sign the input file.",
1623    )
1624    p.add_argument(
1625        "datafile",
1626        help="File to sign. For version 1, this can be any file. "
1627        "For version 2, this must be a valid app image.",
1628        type=argparse.FileType("rb"),
1629    )
1630
1631    p = subparsers.add_parser(
1632        "verify_signature",
1633        help='Verify a data file previously signed by "sign_data", '
1634        "using the public key.",
1635    )
1636    p.add_argument(
1637        "--version",
1638        "-v",
1639        help="Version of the secure boot scheme to use.",
1640        choices=["1", "2"],
1641        required=True,
1642    )
1643    p.add_argument(
1644        "--hsm",
1645        help="Use an external Hardware Security Module "
1646        "to verify signature using PKCS#11 interface.",
1647        action="store_true",
1648    )
1649    p.add_argument(
1650        "--hsm-config",
1651        help="Config file for the external Hardware Security Module "
1652        "to be used to verify signature.",
1653        default=None,
1654    )
1655    p.add_argument(
1656        "--keyfile",
1657        "-k",
1658        help="Public key file for verification. "
1659        "Can be private or public key in PEM format.",
1660        type=argparse.FileType("rb"),
1661    )
1662    p.add_argument(
1663        "datafile",
1664        help="Signed data file to verify signature.",
1665        type=argparse.FileType("rb"),
1666    )
1667
1668    p = subparsers.add_parser(
1669        "extract_public_key",
1670        help="Extract the public verification key for signatures, "
1671        "save it as a raw binary file.",
1672    )
1673    p.add_argument(
1674        "--version",
1675        "-v",
1676        help="Version of the secure boot signing scheme to use.",
1677        choices=["1", "2"],
1678        default="1",
1679    )
1680    p.add_argument(
1681        "--keyfile",
1682        "-k",
1683        help="Private key file (PEM format) to extract the "
1684        "public verification key from.",
1685        type=argparse.FileType("rb"),
1686        required=True,
1687    )
1688    p.add_argument(
1689        "public_keyfile", help="File to save new public key into", type=OutFileType()
1690    )
1691
1692    # Kept for compatibility purpose. We can deprecate this in a future release
1693    p = subparsers.add_parser(
1694        "digest_rsa_public_key",
1695        help="Generate an SHA-256 digest of the RSA public key. "
1696        "This digest is burned into the eFuse and asserts the legitimacy "
1697        "of the public key for Secure boot v2.",
1698    )
1699    p.add_argument(
1700        "--keyfile",
1701        "-k",
1702        help="Public key file for verification. "
1703        "Can be private or public key in PEM format.",
1704        type=argparse.FileType("rb"),
1705        required=True,
1706    )
1707    p.add_argument("--output", "-o", help="Output file for the digest.", required=True)
1708
1709    p = subparsers.add_parser(
1710        "digest_sbv2_public_key",
1711        help="Generate an SHA-256 digest of the public key. "
1712        "This digest is burned into the eFuse and asserts the legitimacy "
1713        "of the public key for Secure boot v2.",
1714    )
1715    p.add_argument(
1716        "--keyfile",
1717        "-k",
1718        help="Public key file for verification. "
1719        "Can be private or public key in PEM format.",
1720        type=argparse.FileType("rb"),
1721        required=True,
1722    )
1723    p.add_argument("--output", "-o", help="Output file for the digest.", required=True)
1724
1725    p = subparsers.add_parser(
1726        "signature_info_v2",
1727        help="Reads the signature block and provides the signature block information.",
1728    )
1729    p.add_argument(
1730        "datafile",
1731        help="Secure boot v2 signed data file.",
1732        type=argparse.FileType("rb"),
1733    )
1734
1735    p = subparsers.add_parser(
1736        "digest_private_key",
1737        help="Generate an SHA-256 digest of the private signing key. "
1738        "This can be used as a reproducible secure bootloader (only secure boot v1) "
1739        "or flash encryption key.",
1740    )
1741    p.add_argument(
1742        "--keyfile",
1743        "-k",
1744        help="Private key file (PEM format) to generate a digest from.",
1745        type=argparse.FileType("rb"),
1746        required=True,
1747    )
1748    p.add_argument(
1749        "--keylen",
1750        "-l",
1751        help="Length of private key digest file to generate (in bits). "
1752        "3/4 Coding Scheme requires 192 bit key.",
1753        choices=[192, 256],
1754        default=256,
1755        type=int,
1756    )
1757    p.add_argument(
1758        "digest_file", help="File to write 32 byte digest into", type=OutFileType()
1759    )
1760
1761    p = subparsers.add_parser(
1762        "generate_flash_encryption_key",
1763        help="Generate a development-use flash encryption key with random data.",
1764    )
1765    p.add_argument(
1766        "--keylen",
1767        "-l",
1768        help="Length of private key digest file to generate (in bits). "
1769        "3/4 Coding Scheme requires 192 bit key.",
1770        choices=[128, 192, 256, 512],
1771        default=256,
1772        type=int,
1773    )
1774    p.add_argument(
1775        "key_file",
1776        help="File to write 16, 24, 32 or 64 byte key into",
1777        type=OutFileType(),
1778    )
1779
1780    p = subparsers.add_parser(
1781        "decrypt_flash_data",
1782        help="Decrypt some data read from encrypted flash (using known key)",
1783    )
1784    p.add_argument(
1785        "encrypted_file",
1786        help="File with encrypted flash contents",
1787        type=argparse.FileType("rb"),
1788    )
1789    p.add_argument(
1790        "--aes_xts",
1791        "-x",
1792        help="Decrypt data using AES-XTS as used on "
1793        "ESP32-S2, ESP32-C2, ESP32-C3, ESP32-C6, ESP32-C5, ESP32-C61 and ESP32-P4",
1794        action="store_true",
1795    )
1796    p.add_argument(
1797        "--keyfile",
1798        "-k",
1799        help="File with flash encryption key",
1800        type=argparse.FileType("rb"),
1801        required=True,
1802    )
1803    p.add_argument(
1804        "--output",
1805        "-o",
1806        help="Output file for plaintext data.",
1807        type=OutFileType(),
1808        required=True,
1809    )
1810    p.add_argument(
1811        "--address",
1812        "-a",
1813        help="Address offset in flash that file was read from.",
1814        required=True,
1815        type=esptool.arg_auto_int,
1816    )
1817    p.add_argument(
1818        "--flash_crypt_conf",
1819        help="Override FLASH_CRYPT_CONF efuse value (default is 0XF).",
1820        required=False,
1821        default=0xF,
1822        type=esptool.arg_auto_int,
1823    )
1824
1825    p = subparsers.add_parser(
1826        "encrypt_flash_data",
1827        help="Encrypt some data suitable for encrypted flash (using known key)",
1828    )
1829    p.add_argument(
1830        "--aes_xts",
1831        "-x",
1832        help="Encrypt data using AES-XTS as used on "
1833        "ESP32-S2, ESP32-C2, ESP32-C3, ESP32-C6, ESP32-C5, ESP32-C61 and ESP32-P4",
1834        action="store_true",
1835    )
1836    p.add_argument(
1837        "--keyfile",
1838        "-k",
1839        help="File with flash encryption key",
1840        type=argparse.FileType("rb"),
1841        required=True,
1842    )
1843    p.add_argument(
1844        "--output",
1845        "-o",
1846        help="Output file for encrypted data.",
1847        type=OutFileType(),
1848        required=True,
1849    )
1850    p.add_argument(
1851        "--address",
1852        "-a",
1853        help="Address offset in flash where file will be flashed.",
1854        required=True,
1855        type=esptool.arg_auto_int,
1856    )
1857    p.add_argument(
1858        "--flash_crypt_conf",
1859        help="Override FLASH_CRYPT_CONF efuse value (default is 0XF).",
1860        required=False,
1861        default=0xF,
1862        type=esptool.arg_auto_int,
1863    )
1864    p.add_argument(
1865        "plaintext_file",
1866        help="File with plaintext content for encrypting",
1867        type=argparse.FileType("rb"),
1868    )
1869
1870    # Enable argcomplete only on Unix-like systems
1871    if sys.platform != "win32":
1872        try:
1873            import argcomplete
1874
1875            argcomplete.autocomplete(parser)
1876        except ImportError:
1877            pass
1878
1879    args = parser.parse_args(custom_commandline)
1880    print("espsecure.py v%s" % esptool.__version__)
1881    if args.operation is None:
1882        parser.print_help()
1883        parser.exit(1)
1884
1885    try:
1886        # each 'operation' is a module-level function of the same name
1887        operation_func = globals()[args.operation]
1888        operation_func(args)
1889    finally:
1890        for arg_name in vars(args):
1891            obj = getattr(args, arg_name)
1892            if isinstance(obj, (OutFileType, IOBase)):
1893                obj.close()
1894            elif isinstance(obj, list):
1895                for f in [o for o in obj if isinstance(o, IOBase)]:
1896                    f.close()
1897
1898
1899def _main():
1900    try:
1901        main()
1902    except esptool.FatalError as e:
1903        print("\nA fatal error occurred: %s" % e)
1904        sys.exit(2)
1905    except ValueError as e:
1906        try:
1907            if [arg for arg in e.args if "Could not deserialize key data." in arg]:
1908                print(
1909                    "Note: This error originates from the cryptography module. "
1910                    "It is likely not a problem with espsecure, "
1911                    "please make sure you are using a compatible OpenSSL backend."
1912                )
1913        finally:
1914            raise
1915
1916
1917if __name__ == "__main__":
1918    _main()
1919