Skip to content

Commit d024cc8

Browse files
committed
suit: Add support for Ed25519PH
Ref: NCSDK-31353 Signed-off-by: Artur Hadasz <[email protected]>
1 parent 7f4ca54 commit d024cc8

File tree

11 files changed

+48
-13
lines changed

11 files changed

+48
-13
lines changed

ncs/Kconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ config SUIT_ENVELOPE_TARGET_SIGN_ALG_ECDSA_384
176176
config SUIT_ENVELOPE_TARGET_SIGN_ALG_ECDSA_521
177177
bool "Use the ECDSA algorithm with key length of 521 bits"
178178

179+
config SUIT_ENVELOPE_TARGET_SIGN_ALG_HASH_EDDSA
180+
bool "Use the HashEdDSA algorithm (specifically: ed25519ph)"
181+
select EXPERIMENTAL
182+
179183
endchoice
180184

181185
config SUIT_ENVELOPE_TARGET_SIGN_ALG_NAME
@@ -184,5 +188,6 @@ config SUIT_ENVELOPE_TARGET_SIGN_ALG_NAME
184188
default "es-256" if SUIT_ENVELOPE_TARGET_SIGN_ALG_ECDSA_256
185189
default "es-384" if SUIT_ENVELOPE_TARGET_SIGN_ALG_ECDSA_384
186190
default "es-521" if SUIT_ENVELOPE_TARGET_SIGN_ALG_ECDSA_521
191+
default "hash-eddsa" if SUIT_ENVELOPE_TARGET_SIGN_ALG_HASH_EDDSA
187192

188193
endif # SUIT_ENVELOPE_TARGET_SIGN

ncs/basic_kms.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
from cryptography.hazmat.primitives.asymmetric import ec
1818
from cryptography.hazmat.primitives import hashes
1919
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
20+
21+
# eddsa25519ph is not supported by cryptography, so we need to use pycryptodome
22+
from Crypto.PublicKey import ECC
23+
from Crypto.Signature import eddsa
24+
from Crypto.Hash import SHA512
2025
import math
2126

2227
from suit_generator.suit_kms_base import SuitKMSBase
@@ -87,7 +92,7 @@ def _verify_signing_key_type(self, private_key, algorithm: str) -> bool:
8792
if isinstance(private_key, EllipticCurvePrivateKey):
8893
return f"es-{private_key.key_size}" == algorithm
8994
elif isinstance(private_key, Ed25519PrivateKey) or isinstance(private_key, Ed448PrivateKey):
90-
return "eddsa" == algorithm
95+
return "eddsa" == algorithm or "hash-eddsa" == algorithm
9196
else:
9297
raise ValueError(f"Key {type(private_key)} not supported")
9398

@@ -104,11 +109,19 @@ def _create_cose_ed_signature(self, input_data, private_key) -> bytes:
104109
"""Create ECDSA signature and return signature bytes."""
105110
return private_key.sign(input_data)
106111

107-
def _get_sign_method(self, private_key) -> bool:
112+
def _create_cose_ed_prehashed_signature(self, input_data, private_key) -> bytes:
113+
prehashed_message = SHA512.new(input_data)
114+
key = ECC.import_key(private_key)
115+
signer = eddsa.new(key, "rfc8032")
116+
return signer.sign(prehashed_message)
117+
118+
def _get_sign_method(self, private_key, algorithm) -> bool:
108119
"""Return sign method based on key type."""
109120
if isinstance(private_key, EllipticCurvePrivateKey):
110121
return self._create_cose_es_signature
111122
elif isinstance(private_key, Ed25519PrivateKey) or isinstance(private_key, Ed448PrivateKey):
123+
if algorithm == "hash-eddsa":
124+
return self._create_cose_ed_prehashed_signature
112125
return self._create_cose_ed_signature
113126
else:
114127
raise ValueError(f"Key {type(private_key)} not supported")
@@ -146,7 +159,13 @@ def sign(self, data: bytes, key_name: str, algorithm: str, context: str) -> byte
146159
if not self._verify_signing_key_type(private_key, algorithm):
147160
raise ValueError(f"Key {key_file_name} is not compatible with algorithm {algorithm}")
148161

149-
sign_method = self._get_sign_method(private_key)
162+
sign_method = self._get_sign_method(private_key, algorithm)
163+
164+
if algorithm == "hash-eddsa":
165+
# In the special case of hash-eddsa, we need to use pycryptodome, which needs the raw key
166+
# data to import the key
167+
private_key = open(private_key_path).read()
168+
150169
signature = sign_method(data, private_key)
151170

152171
return signature

ncs/sign_script.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class SuitCoseSignAlgorithms(Enum):
3636
COSE_ALG_ES_384 = -35
3737
COSE_ALG_ES_521 = -36
3838
COSE_ALG_EdDSA = -8
39+
COSE_ALG_VS_HashedEdDSA = -65537
3940

4041

4142
class SuitIds(Enum):
@@ -157,7 +158,7 @@ def sign_envelope(
157158
self.already_signed_action(already_signed_action)
158159

159160
if self._skip_signing:
160-
return
161+
return self.envelope
161162
protected = {
162163
SuitIds.COSE_ALG.value: SuitCoseSignAlgorithms["COSE_ALG_" + self._algorithm.name].value,
163164
SuitIds.COSE_KEY_ID.value: cbor2.dumps(self._key_id),

suit_generator/cmd_image.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ def as_intelhex(self, storage_domain: ManifestDomain = None):
300300
raise GeneratorError(
301301
f"Unable to fit envelope with role {role} inside the envelope slot (max: {max_size} bytes)"
302302
)
303-
envelope_bytes = self._envelopes[role].ljust(max_size, b"\xFF")
303+
envelope_bytes = self._envelopes[role].ljust(max_size, b"\xff")
304304
envelope_count += 1
305305
else:
306306
continue

suit_generator/cmd_mpi.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,13 @@ def generate(
143143
+ downgrade_prevention_enabled_bytes
144144
+ independent_updates_bytes
145145
+ signature_verification_bytes
146-
+ b"\xFF" * 12 # Reserved for future use
146+
+ b"\xff" * 12 # Reserved for future use
147147
+ vid.bytes
148148
+ cid.bytes
149149
)
150150

151151
mpi_hex = IntelHex()
152-
mpi_hex.frombytes(mpi.ljust(size, b"\xFF"), address)
152+
mpi_hex.frombytes(mpi.ljust(size, b"\xff"), address)
153153
mpi_hex.write_hex_file(output_file)
154154

155155
@staticmethod

suit_generator/cmd_sign.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def __init__(
8787
self.key_id = int(envelope_json["key-id"], 0)
8888

8989
if "sign-script" in envelope_json:
90-
self.sign_script = envelope_json["sign_script"]
90+
self.sign_script = envelope_json["sign-script"]
9191
elif self.sign_script is None:
9292
if os.environ.get("NCS_SUIT_SIGN_SCRIPT"):
9393
self.sign_script = os.environ.get("NCS_SUIT_SIGN_SCRIPT")
@@ -102,7 +102,7 @@ def __init__(
102102
self.signer = _import_signer(self.sign_script)
103103

104104
if "kms-script" in envelope_json:
105-
self.kms_script = envelope_json["kms_script"]
105+
self.kms_script = envelope_json["kms-script"]
106106
elif self.kms_script is None:
107107
if os.environ.get("NCS_SUIT_KMS_SCRIPT"):
108108
self.kms_script = os.environ.get("NCS_SUIT_KMS_SCRIPT")

suit_generator/suit/security.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
cose_alg_es_384,
4848
cose_alg_es_521,
4949
cose_alg_eddsa,
50+
cose_alg_vs_hash_eddsa,
5051
cose_alg_aes_gcm_128,
5152
cose_alg_aes_gcm_192,
5253
cose_alg_aes_gcm_256,
@@ -183,6 +184,7 @@ class SuitcoseAlg(SuitEnum):
183184
cose_alg_es_384,
184185
cose_alg_es_521,
185186
cose_alg_eddsa,
187+
cose_alg_vs_hash_eddsa,
186188
cose_alg_aes_gcm_128,
187189
cose_alg_aes_gcm_192,
188190
cose_alg_aes_gcm_256,

suit_generator/suit/types/keys.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,13 @@ class cose_alg_eddsa(suit_key):
694694
name = "cose-alg-eddsa"
695695

696696

697+
class cose_alg_vs_hash_eddsa(suit_key):
698+
"""Cose algorithm metadata."""
699+
700+
id = -65537
701+
name = "cose-alg-vs-hash-eddsa"
702+
703+
697704
class cose_alg_aes_gcm_128(suit_key):
698705
"""Cose algorithm metadata."""
699706

suit_generator/suit_sign_script_base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class SuitSignAlgorithms(Enum):
1919
ES_384 = "es-384"
2020
ES_521 = "es-521"
2121
EdDSA = "eddsa"
22+
VS_HashedEdDSA = "hash-eddsa"
2223

2324
def __str__(self):
2425
return self.value

tests/test_cmd_image.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121

2222
MAX_CACHE_COUNT = 16
2323

24-
addresses = {0x00000000: b"\x00\x00\x00\x00", 0xDEADBEEF: b"\xEF\xBE\xAD\xDE", 0xFFFFFFFF: b"\xFF\xFF\xFF\xFF"}
25-
sizes = {0x00000000: b"\x00\x00\x00\x00", 0x01020304: b"\x04\x03\x02\x01", 0xFFFFFFFF: b"\xFF\xFF\xFF\xFF"}
24+
addresses = {0x00000000: b"\x00\x00\x00\x00", 0xDEADBEEF: b"\xef\xbe\xad\xde", 0xFFFFFFFF: b"\xff\xff\xff\xff"}
25+
sizes = {0x00000000: b"\x00\x00\x00\x00", 0x01020304: b"\x04\x03\x02\x01", 0xFFFFFFFF: b"\xff\xff\xff\xff"}
2626

2727
signed_envelope_without_class_id_input = (
2828
b"\xd8k\xa4\x02X'\x81X$\x82/X 7d\x90\xc1\xa5\x84\xc1\xed\xeeO\x0f\xd6\xad\xc5t\xb0\x1dr\xb5r"
@@ -346,7 +346,7 @@ def test_update_candidate_info_verify_class_id_offset():
346346
@pytest.mark.parametrize("address", addresses)
347347
@pytest.mark.parametrize("size", sizes)
348348
def test_update_candidate_info_for_update(address, size, nb_of_caches):
349-
magic_bytes = b"\xAA\x55\xAA\x55"
349+
magic_bytes = b"\xaa\x55\xaa\x55"
350350
nregions_bytes = b"\x01\x00\x00\x00"
351351
address_bytes = addresses[address]
352352
size_bytes = sizes[size]

0 commit comments

Comments
 (0)