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