Skip to content

Commit e132f6f

Browse files
committed
feat(espsecure): Drop ecdsa module, use cryptography instead
1 parent 78535e4 commit e132f6f

File tree

12 files changed

+171
-90
lines changed

12 files changed

+171
-90
lines changed

espefuse/efuse/esp32c5/operations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def burn_key(esp, efuses, args, digest=None):
242242
if digest is None:
243243
if keypurpose == "ECDSA_KEY":
244244
sk = espsecure.load_ecdsa_signing_key(datafile)
245-
data = sk.to_string()
245+
data = espsecure.get_ecdsa_signing_key_raw_bytes(sk)
246246
if len(data) == 24:
247247
# the private key is 24 bytes long for NIST192p, and 8 bytes of padding
248248
data = b"\x00" * 8 + data

espefuse/efuse/esp32c61/operations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ def burn_key(esp, efuses, args, digest=None):
309309
if digest is None:
310310
if keypurpose == "ECDSA_KEY":
311311
sk = espsecure.load_ecdsa_signing_key(datafile)
312-
data = sk.to_string()
312+
data = espsecure.get_ecdsa_signing_key_raw_bytes(sk)
313313
if len(data) == 24:
314314
# the private key is 24 bytes long for NIST192p, and 8 bytes of padding
315315
data = b"\x00" * 8 + data

espefuse/efuse/esp32h2/operations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ def burn_key(esp, efuses, args, digest=None):
256256
if digest is None:
257257
if keypurpose == "ECDSA_KEY":
258258
sk = espsecure.load_ecdsa_signing_key(datafile)
259-
data = sk.to_string()
259+
data = espsecure.get_ecdsa_signing_key_raw_bytes(sk)
260260
if len(data) == 24:
261261
# the private key is 24 bytes long for NIST192p, add 8 bytes of padding
262262
data = b"\x00" * 8 + data

espefuse/efuse/esp32h21/operations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ def burn_key(esp, efuses, args, digest=None):
240240
if digest is None:
241241
if keypurpose == "ECDSA_KEY":
242242
sk = espsecure.load_ecdsa_signing_key(datafile)
243-
data = sk.to_string()
243+
data = espsecure.get_ecdsa_signing_key_raw_bytes(sk)
244244
if len(data) == 24:
245245
# the private key is 24 bytes long for NIST192p, add 8 bytes of padding
246246
data = b"\x00" * 8 + data

espefuse/efuse/esp32h4/operations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def burn_key(esp, efuses, args, digest=None):
238238
if digest is None:
239239
if keypurpose == "ECDSA_KEY":
240240
sk = espsecure._load_ecdsa_signing_key(datafile)
241-
data = sk.to_string()
241+
data = espsecure.get_ecdsa_signing_key_raw_bytes(sk)
242242
if len(data) == 24:
243243
# the private key is 24 bytes long for NIST192p, and 8 bytes of padding
244244
data = b"\x00" * 8 + data

espefuse/efuse/esp32p4/operations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ def burn_key(esp, efuses, args, digest=None):
309309
if digest is None:
310310
if keypurpose == "ECDSA_KEY":
311311
sk = espsecure.load_ecdsa_signing_key(datafile)
312-
data = sk.to_string()
312+
data = espsecure.get_ecdsa_signing_key_raw_bytes(sk)
313313
if len(data) == 24:
314314
# the private key is 24 bytes long for NIST192p, add 8 bytes of padding
315315
data = b"\x00" * 8 + data

espsecure/__init__.py

Lines changed: 88 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
1+
# SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
22
#
33
# SPDX-License-Identifier: GPL-2.0-or-later
44
import configparser
@@ -23,8 +23,6 @@
2323

2424
from esptool.logger import log
2525

26-
import ecdsa
27-
2826
import esptool
2927

3028
SIG_BLOCK_MAGIC = 0xE7
@@ -175,10 +173,22 @@ def digest_secure_bootloader(
175173
print("digest+image written to %s" % output)
176174

177175

178-
def _generate_ecdsa_signing_key(curve_id: ecdsa.curves.Curve, keyfile: str):
179-
sk = ecdsa.SigningKey.generate(curve=curve_id)
176+
def _generate_ecdsa_signing_key(curve_id: ec.EllipticCurve, keyfile: str):
177+
if curve_id not in [ec.SECP192R1, ec.SECP256R1, ec.SECP384R1]:
178+
raise ValueError(
179+
f"Unsupported curve: {curve_id}, "
180+
"only NIST192p, NIST256p, NIST384p are supported."
181+
)
182+
183+
private_key = ec.generate_private_key(curve_id())
184+
pem = private_key.private_bytes(
185+
encoding=serialization.Encoding.PEM,
186+
format=serialization.PrivateFormat.TraditionalOpenSSL,
187+
encryption_algorithm=serialization.NoEncryption(),
188+
)
189+
180190
with open(keyfile, "wb") as f:
181-
f.write(sk.to_pem())
191+
f.write(pem)
182192

183193

184194
def generate_signing_key(version: int, scheme: str | None, keyfile: str):
@@ -191,7 +201,7 @@ def generate_signing_key(version: int, scheme: str | None, keyfile: str):
191201
"""
192202
Generate an ECDSA signing key for signing secure boot images (post-bootloader)
193203
"""
194-
_generate_ecdsa_signing_key(ecdsa.NIST256p, keyfile)
204+
_generate_ecdsa_signing_key(ec.SECP256R1, keyfile)
195205
print(f"ECDSA NIST256p private key in PEM format written to {keyfile}")
196206
elif version == "2":
197207
if scheme == "rsa3072" or scheme is None:
@@ -208,55 +218,59 @@ def generate_signing_key(version: int, scheme: str | None, keyfile: str):
208218
print(f"RSA 3072 private key in PEM format written to {keyfile}")
209219
elif scheme == "ecdsa192":
210220
"""Generate a ECDSA 192 signing key for signing secure boot images"""
211-
_generate_ecdsa_signing_key(ecdsa.NIST192p, keyfile)
221+
_generate_ecdsa_signing_key(ec.SECP192R1, keyfile)
212222
print(f"ECDSA NIST192p private key in PEM format written to {keyfile}")
213223
elif scheme == "ecdsa256":
214224
"""Generate a ECDSA 256 signing key for signing secure boot images"""
215-
_generate_ecdsa_signing_key(ecdsa.NIST256p, keyfile)
225+
_generate_ecdsa_signing_key(ec.SECP256R1, keyfile)
216226
print(f"ECDSA NIST256p private key in PEM format written to {keyfile}")
217227
elif scheme == "ecdsa384":
218228
"""Generate a ECDSA 384 signing key for signing secure boot images"""
219-
_generate_ecdsa_signing_key(ecdsa.NIST384p, keyfile)
229+
_generate_ecdsa_signing_key(ec.SECP384R1, keyfile)
220230
print(f"ECDSA NIST384p private key in PEM format written to {keyfile}")
221231
else:
222232
raise esptool.FatalError(f"ERROR: Unsupported signing scheme {scheme}")
223233

224234

225-
def load_ecdsa_signing_key(keyfile: IO) -> ecdsa.SigningKey:
235+
def load_ecdsa_signing_key(keyfile: IO) -> ec.EllipticCurvePrivateKey:
226236
"""Load ECDSA signing key"""
227237
try:
228-
sk = ecdsa.SigningKey.from_pem(keyfile.read())
238+
sk = serialization.load_pem_private_key(
239+
keyfile.read(), password=None, backend=default_backend()
240+
)
229241
except ValueError:
230242
raise esptool.FatalError(
231243
"Incorrect ECDSA private key specified. "
232244
"Please check algorithm and/or format."
233245
)
234-
if sk.curve not in [ecdsa.NIST192p, ecdsa.NIST256p]:
246+
if not isinstance(sk.curve, (ec.SECP192R1, ec.SECP256R1)):
235247
raise esptool.FatalError("Supports NIST192p and NIST256p keys only")
236248
return sk
237249

238250

239-
def _load_ecdsa_signing_key(keyfile: IO) -> ecdsa.SigningKey:
251+
def _load_ecdsa_signing_key(keyfile: IO) -> ec.EllipticCurvePrivateKey:
240252
"""Load ECDSA signing key for Secure Boot V1 only"""
241253
sk = load_ecdsa_signing_key(keyfile)
242-
if sk.curve != ecdsa.NIST256p:
254+
if not isinstance(sk.curve, ec.SECP256R1):
243255
raise esptool.FatalError(
244256
"Signing key uses incorrect curve. ESP32 Secure Boot only supports "
245257
"NIST256p (openssl calls this curve 'prime256v1')"
246258
)
247259
return sk
248260

249261

250-
def _load_ecdsa_verifying_key(keyfile: IO) -> ecdsa.VerifyingKey:
262+
def _load_ecdsa_verifying_key(keyfile: IO) -> ec.EllipticCurvePublicKey:
251263
"""Load ECDSA verifying key for Secure Boot V1 only"""
252264
try:
253-
vk = ecdsa.VerifyingKey.from_pem(keyfile.read())
265+
vk = serialization.load_pem_public_key(
266+
keyfile.read(), backend=default_backend()
267+
)
254268
except ValueError:
255269
raise esptool.FatalError(
256270
"Incorrect ECDSA public key specified. "
257271
"Please check algorithm and/or format."
258272
)
259-
if vk.curve != ecdsa.NIST256p:
273+
if not isinstance(vk.curve, ec.SECP256R1):
260274
raise esptool.FatalError(
261275
"Signing key uses incorrect curve. ESP32 Secure Boot only supports "
262276
"NIST256p (openssl calls this curve 'prime256v1')"
@@ -424,21 +438,36 @@ def sign_secure_boot_v1(
424438
print("Pre-calculated signatures found")
425439
if len(pub_key) > 1:
426440
raise esptool.FatalError("Secure Boot V1 only supports one signing key")
427-
signature = signatures[0].read()
441+
raw_signature = signatures[0].read()
442+
# Signature needs to be DER-encoded for verification
443+
r = int.from_bytes(raw_signature[:32], "big")
444+
s = int.from_bytes(raw_signature[32:], "big")
445+
signature = utils.encode_dss_signature(r, s)
428446
# get verifying/public key
429447
vk = _load_ecdsa_verifying_key(pub_key[0])
430448
else:
431449
if len(keyfile) > 1:
432450
raise esptool.FatalError("Secure Boot V1 only supports one signing key")
433451
sk = _load_ecdsa_signing_key(keyfile[0])
434452

435-
# calculate signature of binary data
436-
signature = sk.sign_deterministic(binary_content, hashlib.sha256)
453+
# calculate signature of binary data, returns DER-encoded signature
454+
signature = sk.sign(
455+
binary_content, ec.ECDSA(hashes.SHA256(), deterministic_signing=True)
456+
)
437457
# get verifying/public key
438-
vk = sk.get_verifying_key()
458+
vk = sk.public_key()
439459

440460
# back-verify signature
441-
vk.verify(signature, binary_content, hashlib.sha256) # throws exception on failure
461+
vk.verify(
462+
signature, binary_content, ec.ECDSA(hashes.SHA256())
463+
) # throws exception on failure
464+
465+
# Secure boot signature block stores raw signature bytes, create raw signature
466+
r, s = utils.decode_dss_signature(signature)
467+
r_bytes = r.to_bytes(32, byteorder="big")
468+
s_bytes = s.to_bytes(32, byteorder="big")
469+
signature = r_bytes + s_bytes
470+
442471
if output is None or os.path.abspath(output) == os.path.abspath(
443472
datafile.name
444473
): # append signature to input file
@@ -535,7 +564,7 @@ def sign_secure_boot_v2(
535564
config = hsm_sign.read_hsm_config(hsm_config)
536565
except Exception as e:
537566
raise esptool.FatalError(f"Incorrect HSM config file format ({e})")
538-
if pub_key is None:
567+
if len(pub_key) == 0:
539568
pub_key = extract_pubkey_from_hsm(config)
540569
signature = generate_signature_using_hsm(config, contents)
541570

@@ -836,22 +865,27 @@ def verify_signature_v1(keyfile: IO, datafile: IO):
836865
"""Verify a previously signed binary image, using the ECDSA public key"""
837866
key_data = keyfile.read()
838867
if b"-BEGIN EC PRIVATE KEY" in key_data:
839-
sk = ecdsa.SigningKey.from_pem(key_data)
840-
vk = sk.get_verifying_key()
868+
sk = serialization.load_pem_private_key(
869+
key_data, password=None, backend=default_backend()
870+
)
871+
vk = sk.public_key()
841872
elif b"-BEGIN PUBLIC KEY" in key_data:
842-
vk = ecdsa.VerifyingKey.from_pem(key_data)
843-
elif len(key_data) == 64:
844-
vk = ecdsa.VerifyingKey.from_string(key_data, curve=ecdsa.NIST256p)
873+
vk = serialization.load_pem_public_key(key_data, backend=default_backend())
874+
elif len(key_data) == 64: # Raw public key bytes
875+
x = int.from_bytes(key_data[:32], byteorder="big") # x coordinates
876+
y = int.from_bytes(key_data[32:], byteorder="big") # y coordinates
877+
numbers = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1())
878+
vk = numbers.public_key(backend=default_backend())
845879
else:
846880
raise esptool.FatalError(
847881
"Verification key does not appear to be an EC key in PEM format "
848882
"or binary EC public key data. Unsupported"
849883
)
850884

851-
if vk.curve != ecdsa.NIST256p:
885+
if not isinstance(vk.curve, ec.SECP256R1):
852886
raise esptool.FatalError(
853887
"Public key uses incorrect curve. ESP32 Secure Boot only supports "
854-
"NIST256p (openssl calls this curve 'prime256v1"
888+
"NIST256p (openssl calls this curve 'prime256v1')"
855889
)
856890

857891
binary_content = datafile.read()
@@ -864,12 +898,15 @@ def verify_signature_v1(keyfile: IO, datafile: IO):
864898
)
865899
print("Verifying %d bytes of data" % len(data))
866900
try:
867-
if vk.verify(signature, data, hashlib.sha256):
868-
print("Signature is valid")
869-
else:
870-
raise esptool.FatalError("Signature is not valid")
871-
except ecdsa.keys.BadSignatureError:
872-
raise esptool.FatalError("Signature is not valid")
901+
# Convert raw signature to DER format
902+
r = int.from_bytes(signature[:32], byteorder="big")
903+
s = int.from_bytes(signature[32:], byteorder="big")
904+
der_signature = utils.encode_dss_signature(r, s)
905+
906+
vk.verify(der_signature, data, ec.ECDSA(hashes.SHA256()))
907+
print("Signature is valid.")
908+
except exceptions.InvalidSignature:
909+
raise esptool.FatalError("Signature is not valid.")
873910

874911

875912
def validate_signature_block(image_content: bytes, sig_blk_num: int) -> bytes | None:
@@ -1019,19 +1056,17 @@ def extract_public_key(version: int, keyfile: IO, public_keyfile: IO):
10191056
as raw binary data.
10201057
"""
10211058
sk = _load_ecdsa_signing_key(keyfile)
1022-
vk = sk.get_verifying_key()
1023-
public_keyfile.write(vk.to_string())
10241059
elif version == "2":
10251060
"""
10261061
Load an RSA or an ECDSA private key and extract the public key
10271062
as raw binary data.
10281063
"""
10291064
sk = _load_sbv2_signing_key(keyfile.read())
1030-
vk = sk.public_key().public_bytes(
1031-
encoding=serialization.Encoding.PEM,
1032-
format=serialization.PublicFormat.SubjectPublicKeyInfo,
1033-
)
1034-
public_keyfile.write(vk)
1065+
vk = sk.public_key().public_bytes(
1066+
encoding=serialization.Encoding.PEM,
1067+
format=serialization.PublicFormat.SubjectPublicKeyInfo,
1068+
)
1069+
public_keyfile.write(vk)
10351070
print(f"{keyfile.name} public key extracted to {public_keyfile.name}")
10361071

10371072

@@ -1180,13 +1215,17 @@ def digest_sbv2_public_key(keyfile: IO, output: str):
11801215
f.write(public_key_digest)
11811216

11821217

1218+
def get_ecdsa_signing_key_raw_bytes(sk):
1219+
return sk.private_numbers().private_value.to_bytes(
1220+
length=(sk.key_size + 7) // 8, byteorder="big"
1221+
)
1222+
1223+
11831224
def digest_private_key(keyfile: IO, keylen: int, digest_file: IO):
11841225
_check_output_is_not_input(keyfile, digest_file)
11851226
sk = _load_ecdsa_signing_key(keyfile)
1186-
repr(sk.to_string())
1187-
digest = hashlib.sha256()
1188-
digest.update(sk.to_string())
1189-
result = digest.digest()
1227+
private_bytes = get_ecdsa_signing_key_raw_bytes(sk)
1228+
result = hashlib.sha256(private_bytes).digest()
11901229
if keylen == 192:
11911230
result = result[0:24]
11921231
digest_file.write(result)
@@ -1794,7 +1833,7 @@ def signature_info_v2_cli(datafile):
17941833
)
17951834
@click.argument("digest-file", type=click.File("wb", lazy=True))
17961835
def digest_private_key_cli(keyfile, keylen, digest_file):
1797-
"""Generate an SHA-256 digest of the private signing key."""
1836+
"""Generate a SHA-256 digest of the private signing key."""
17981837
digest_private_key(keyfile, keylen, digest_file)
17991838

18001839

espsecure/esp_hsm_sign/__init__.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
1+
# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
22
#
33
# SPDX-License-Identifier: GPL-2.0-or-later
44

@@ -21,13 +21,12 @@
2121

2222
import cryptography.hazmat.primitives.asymmetric.ec as EC
2323
import cryptography.hazmat.primitives.asymmetric.rsa as RSA
24-
25-
import ecdsa
24+
import cryptography.hazmat.primitives.asymmetric.utils as utils
2625

2726

2827
def read_hsm_config(configfile: IO) -> configparser.SectionProxy:
2928
config = configparser.ConfigParser()
30-
config.read(configfile)
29+
config.read_file(configfile)
3130

3231
section = "hsm_config"
3332
if not config.has_section(section):
@@ -112,9 +111,17 @@ def get_pubkey(
112111
public_key = RSA.RSAPublicNumbers(e, n).public_key()
113112

114113
elif public_key.key_type == pkcs11.mechanisms.KeyType.EC:
115-
ecpoints, _ = ecdsa.der.remove_octet_string(
116-
public_key[pkcs11.Attribute.EC_POINT]
117-
)
114+
# EC_POINT is encoded as an octet string
115+
# First byte is "0x04" indicating uncompressed point format
116+
# followed by length bytes
117+
ec_point_der = public_key[pkcs11.Attribute.EC_POINT]
118+
if ec_point_der[0] != 0x04: # octet string tag
119+
raise ValueError(
120+
f"Invalid EC_POINT encoding. "
121+
f"Wanted type 'octetstring' (0x04), got {ec_point_der[0]:#02x}"
122+
)
123+
length = ec_point_der[1]
124+
ecpoints = ec_point_der[2 : 2 + length]
118125
public_key = EC.EllipticCurvePublicKey.from_encoded_point(
119126
EC.SECP256R1(), ecpoints
120127
)
@@ -148,10 +155,8 @@ def sign_payload(private_key: pkcs11.Key, payload: bytes) -> bytes:
148155
r = int(binascii.hexlify(signature[:32]), 16)
149156
s = int(binascii.hexlify(signature[32:]), 16)
150157

151-
# der encoding in case of ecdsa signatures
152-
signature = ecdsa.der.encode_sequence(
153-
ecdsa.der.encode_integer(r), ecdsa.der.encode_integer(s)
154-
)
158+
# ECDSA signature is encoded as a DER sequence
159+
signature = utils.encode_dss_signature(r, s)
155160

156161
return signature
157162

0 commit comments

Comments
 (0)