1
- # SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
1
+ # SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
2
2
#
3
3
# SPDX-License-Identifier: GPL-2.0-or-later
4
4
import configparser
23
23
24
24
from esptool .logger import log
25
25
26
- import ecdsa
27
-
28
26
import esptool
29
27
30
28
SIG_BLOCK_MAGIC = 0xE7
@@ -175,10 +173,22 @@ def digest_secure_bootloader(
175
173
print ("digest+image written to %s" % output )
176
174
177
175
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
+
180
190
with open (keyfile , "wb" ) as f :
181
- f .write (sk . to_pem () )
191
+ f .write (pem )
182
192
183
193
184
194
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):
191
201
"""
192
202
Generate an ECDSA signing key for signing secure boot images (post-bootloader)
193
203
"""
194
- _generate_ecdsa_signing_key (ecdsa . NIST256p , keyfile )
204
+ _generate_ecdsa_signing_key (ec . SECP256R1 , keyfile )
195
205
print (f"ECDSA NIST256p private key in PEM format written to { keyfile } " )
196
206
elif version == "2" :
197
207
if scheme == "rsa3072" or scheme is None :
@@ -208,55 +218,59 @@ def generate_signing_key(version: int, scheme: str | None, keyfile: str):
208
218
print (f"RSA 3072 private key in PEM format written to { keyfile } " )
209
219
elif scheme == "ecdsa192" :
210
220
"""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 )
212
222
print (f"ECDSA NIST192p private key in PEM format written to { keyfile } " )
213
223
elif scheme == "ecdsa256" :
214
224
"""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 )
216
226
print (f"ECDSA NIST256p private key in PEM format written to { keyfile } " )
217
227
elif scheme == "ecdsa384" :
218
228
"""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 )
220
230
print (f"ECDSA NIST384p private key in PEM format written to { keyfile } " )
221
231
else :
222
232
raise esptool .FatalError (f"ERROR: Unsupported signing scheme { scheme } " )
223
233
224
234
225
- def load_ecdsa_signing_key (keyfile : IO ) -> ecdsa . SigningKey :
235
+ def load_ecdsa_signing_key (keyfile : IO ) -> ec . EllipticCurvePrivateKey :
226
236
"""Load ECDSA signing key"""
227
237
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
+ )
229
241
except ValueError :
230
242
raise esptool .FatalError (
231
243
"Incorrect ECDSA private key specified. "
232
244
"Please check algorithm and/or format."
233
245
)
234
- if sk .curve not in [ ecdsa . NIST192p , ecdsa . NIST256p ] :
246
+ if not isinstance ( sk .curve , ( ec . SECP192R1 , ec . SECP256R1 )) :
235
247
raise esptool .FatalError ("Supports NIST192p and NIST256p keys only" )
236
248
return sk
237
249
238
250
239
- def _load_ecdsa_signing_key (keyfile : IO ) -> ecdsa . SigningKey :
251
+ def _load_ecdsa_signing_key (keyfile : IO ) -> ec . EllipticCurvePrivateKey :
240
252
"""Load ECDSA signing key for Secure Boot V1 only"""
241
253
sk = load_ecdsa_signing_key (keyfile )
242
- if sk .curve != ecdsa . NIST256p :
254
+ if not isinstance ( sk .curve , ec . SECP256R1 ) :
243
255
raise esptool .FatalError (
244
256
"Signing key uses incorrect curve. ESP32 Secure Boot only supports "
245
257
"NIST256p (openssl calls this curve 'prime256v1')"
246
258
)
247
259
return sk
248
260
249
261
250
- def _load_ecdsa_verifying_key (keyfile : IO ) -> ecdsa . VerifyingKey :
262
+ def _load_ecdsa_verifying_key (keyfile : IO ) -> ec . EllipticCurvePublicKey :
251
263
"""Load ECDSA verifying key for Secure Boot V1 only"""
252
264
try :
253
- vk = ecdsa .VerifyingKey .from_pem (keyfile .read ())
265
+ vk = serialization .load_pem_public_key (
266
+ keyfile .read (), backend = default_backend ()
267
+ )
254
268
except ValueError :
255
269
raise esptool .FatalError (
256
270
"Incorrect ECDSA public key specified. "
257
271
"Please check algorithm and/or format."
258
272
)
259
- if vk .curve != ecdsa . NIST256p :
273
+ if not isinstance ( vk .curve , ec . SECP256R1 ) :
260
274
raise esptool .FatalError (
261
275
"Signing key uses incorrect curve. ESP32 Secure Boot only supports "
262
276
"NIST256p (openssl calls this curve 'prime256v1')"
@@ -424,21 +438,36 @@ def sign_secure_boot_v1(
424
438
print ("Pre-calculated signatures found" )
425
439
if len (pub_key ) > 1 :
426
440
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 )
428
446
# get verifying/public key
429
447
vk = _load_ecdsa_verifying_key (pub_key [0 ])
430
448
else :
431
449
if len (keyfile ) > 1 :
432
450
raise esptool .FatalError ("Secure Boot V1 only supports one signing key" )
433
451
sk = _load_ecdsa_signing_key (keyfile [0 ])
434
452
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
+ )
437
457
# get verifying/public key
438
- vk = sk .get_verifying_key ()
458
+ vk = sk .public_key ()
439
459
440
460
# 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
+
442
471
if output is None or os .path .abspath (output ) == os .path .abspath (
443
472
datafile .name
444
473
): # append signature to input file
@@ -535,7 +564,7 @@ def sign_secure_boot_v2(
535
564
config = hsm_sign .read_hsm_config (hsm_config )
536
565
except Exception as e :
537
566
raise esptool .FatalError (f"Incorrect HSM config file format ({ e } )" )
538
- if pub_key is None :
567
+ if len ( pub_key ) == 0 :
539
568
pub_key = extract_pubkey_from_hsm (config )
540
569
signature = generate_signature_using_hsm (config , contents )
541
570
@@ -836,22 +865,27 @@ def verify_signature_v1(keyfile: IO, datafile: IO):
836
865
"""Verify a previously signed binary image, using the ECDSA public key"""
837
866
key_data = keyfile .read ()
838
867
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 ()
841
872
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 ())
845
879
else :
846
880
raise esptool .FatalError (
847
881
"Verification key does not appear to be an EC key in PEM format "
848
882
"or binary EC public key data. Unsupported"
849
883
)
850
884
851
- if vk .curve != ecdsa . NIST256p :
885
+ if not isinstance ( vk .curve , ec . SECP256R1 ) :
852
886
raise esptool .FatalError (
853
887
"Public key uses incorrect curve. ESP32 Secure Boot only supports "
854
- "NIST256p (openssl calls this curve 'prime256v1"
888
+ "NIST256p (openssl calls this curve 'prime256v1') "
855
889
)
856
890
857
891
binary_content = datafile .read ()
@@ -864,12 +898,15 @@ def verify_signature_v1(keyfile: IO, datafile: IO):
864
898
)
865
899
print ("Verifying %d bytes of data" % len (data ))
866
900
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." )
873
910
874
911
875
912
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):
1019
1056
as raw binary data.
1020
1057
"""
1021
1058
sk = _load_ecdsa_signing_key (keyfile )
1022
- vk = sk .get_verifying_key ()
1023
- public_keyfile .write (vk .to_string ())
1024
1059
elif version == "2" :
1025
1060
"""
1026
1061
Load an RSA or an ECDSA private key and extract the public key
1027
1062
as raw binary data.
1028
1063
"""
1029
1064
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 )
1035
1070
print (f"{ keyfile .name } public key extracted to { public_keyfile .name } " )
1036
1071
1037
1072
@@ -1180,13 +1215,17 @@ def digest_sbv2_public_key(keyfile: IO, output: str):
1180
1215
f .write (public_key_digest )
1181
1216
1182
1217
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
+
1183
1224
def digest_private_key (keyfile : IO , keylen : int , digest_file : IO ):
1184
1225
_check_output_is_not_input (keyfile , digest_file )
1185
1226
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 ()
1190
1229
if keylen == 192 :
1191
1230
result = result [0 :24 ]
1192
1231
digest_file .write (result )
@@ -1794,7 +1833,7 @@ def signature_info_v2_cli(datafile):
1794
1833
)
1795
1834
@click .argument ("digest-file" , type = click .File ("wb" , lazy = True ))
1796
1835
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."""
1798
1837
digest_private_key (keyfile , keylen , digest_file )
1799
1838
1800
1839
0 commit comments