Skip to content

Commit 271488e

Browse files
committed
Extend tests for encryption commands.
Ref: NCSDK-31673 Signed-off-by: Artur Hadasz <[email protected]>
1 parent 0fa773e commit 271488e

File tree

7 files changed

+583
-12
lines changed

7 files changed

+583
-12
lines changed

ncs/encrypt_script.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ def generate_kms_artifacts(self, asset_plaintext: bytes, key_name: str, context:
9696
9797
This method encrypts asset_plaintext bytes using the specified key wrap algorithm,
9898
and returns the encrypted asset and encrypted content encryption key (CEK).
99-
10099
"""
101100
# Enc structure:
102101
# {
@@ -255,7 +254,7 @@ def generate(
255254
if kw_alg == SuitKWAlgorithms.A256KW:
256255
if encrypted_cek is None:
257256
raise ValueError("Encrypted CEK is required for AES Key Wrap 256")
258-
self.kw_alg_convert(kw_alg)
257+
self._kw_alg_convert(kw_alg)
259258
return self.generate_encryption_info_and_encrypted_payload(encrypted_asset, encrypted_cek, key_id)
260259

261260

suit_generator/cmd_encrypt.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ def encrypt_and_generate(**kwargs):
175175
kwargs["key_name"],
176176
kwargs["key_id"],
177177
kwargs["context"],
178-
kwargs["hash_alg"],
179-
kwargs["kw_alg"],
178+
SuitDigestAlgorithms(kwargs["hash_alg"]),
179+
SuitKWAlgorithms(kwargs["kw_alg"]),
180180
kwargs["kms_script"],
181181
)
182182
with open(os.path.join(kwargs["output_dir"], "plain_text_digest.bin"), "wb") as file:
@@ -200,7 +200,7 @@ def generate_info(**kwargs):
200200
encrypted_firmware,
201201
encrypted_key,
202202
kwargs["key_id"],
203-
kwargs["kw_alg"],
203+
SuitKWAlgorithms(kwargs["kw_alg"]),
204204
)
205205
with open(os.path.join(kwargs["output_dir"], "suit_encryption_info.bin"), "wb") as file:
206206
file.write(encryption_info)

tests/encrypt_script_mock.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor ASA
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
"""Script mocking the Encrypt script."""
7+
import json
8+
9+
from pathlib import Path
10+
from suit_generator.suit_encrypt_script_base import (
11+
SuitEncryptorBase,
12+
SuitDigestAlgorithms,
13+
SuitKWAlgorithms,
14+
)
15+
16+
17+
class EncryptorMock(SuitEncryptorBase):
18+
"""Encryptor mock implementation."""
19+
20+
def encrypt_and_generate(
21+
self,
22+
firmware: bytes,
23+
key_name: str,
24+
key_id: int,
25+
context: str,
26+
hash_alg: SuitDigestAlgorithms,
27+
kw_alg: SuitKWAlgorithms,
28+
kms_script: Path,
29+
) -> tuple[bytes, bytes, bytes, bytes, int]:
30+
"""Mock encrypting the payload and generation of encryption artifacts."""
31+
context_loaded = json.loads(context)
32+
self.output_file = f"test_output_{key_name}.json"
33+
self.json_data = {"firmware": firmware.hex()}
34+
self.json_data["key_name"] = key_name
35+
self.json_data["key_id"] = key_id
36+
self.json_data["kw_alg"] = kw_alg.value
37+
self.json_data["hash_alg"] = hash_alg.value
38+
self.json_data["context"] = context_loaded["ctx"]
39+
self.json_data["kms_script"] = kms_script
40+
with open(self.output_file, "w") as f:
41+
json.dump(self.json_data, f)
42+
43+
return (
44+
bytes.fromhex(context_loaded["encrypted_data"]),
45+
bytes.fromhex(context_loaded["tag"]),
46+
bytes.fromhex(context_loaded["encryption_info"]),
47+
bytes.fromhex(context_loaded["digest"]),
48+
context_loaded["plaintext_length"],
49+
)
50+
51+
def generate(
52+
self, encrypted_asset: bytes, encrypted_cek: bytes, key_id: int, kw_alg: SuitKWAlgorithms
53+
) -> tuple[bytes, bytes, bytes]:
54+
"""
55+
Mock generation of encryption artifacts.
56+
57+
The encrypted asset for this mock is a json object, containing the return values.
58+
"""
59+
# :return: The encrypted payload, tag, encryption info.
60+
context_loaded = json.loads(encrypted_asset.decode())
61+
self.output_file = f"test_output_{key_id}.json"
62+
63+
self.json_data = {"key_id": key_id}
64+
self.json_data["kw_alg"] = kw_alg.value
65+
self.json_data["encrypted_cek"] = encrypted_cek.hex()
66+
with open(self.output_file, "w") as f:
67+
json.dump(self.json_data, f)
68+
69+
return (
70+
bytes.fromhex(context_loaded["encrypted_data"]),
71+
bytes.fromhex(context_loaded["tag"]),
72+
bytes.fromhex(context_loaded["encryption_info"]),
73+
)
74+
75+
76+
def suit_encryptor_factory():
77+
"""Get an Encryptor object."""
78+
return EncryptorMock()

tests/kms_script_mock.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,17 @@ def init_kms(self, context: str) -> None:
3232
def encrypt(self, plaintext: bytes, key_name: str, context: str, aad: bytes) -> tuple[bytes, bytes, bytes]:
3333
"""Mock of the KMS script encrypt function."""
3434
context_loaded = json.loads(context)
35-
self.json_data["encrypt_plaintext"] = plaintext.decode()
35+
self.json_data["encrypt_plaintext"] = plaintext.hex()
3636
self.json_data["encrypt_key_name"] = key_name
3737
self.json_data["encrypt_context"] = context_loaded["ctx"]
38-
self.json_data["encrypt_aad"] = aad.decode()
38+
self.json_data["encrypt_aad"] = aad.hex()
3939
with open(self.output_file, "w") as f:
4040
json.dump(self.json_data, f)
41+
4142
return (
42-
context_loaded["iv"].encode(),
43-
context_loaded["encryption_key"].encode(),
44-
context_loaded["encrypted_data"].encode(),
43+
bytes.fromhex(context_loaded["iv"]),
44+
bytes.fromhex(context_loaded["tag"]),
45+
bytes.fromhex(context_loaded["encrypted_data"]),
4546
)
4647

4748
def sign(self, data: bytes, key_name: str, algorithm: str, context: str) -> bytes:

tests/test_cmd_encrypt.py

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# Copyright (c) 2025 Nordic Semiconductor ASA
2+
#
3+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
4+
#
5+
"""Unit tests for cmd_encrypt.py implementation."""
6+
7+
import pytest
8+
import os
9+
import pathlib
10+
import encrypt_script_mock
11+
import json
12+
13+
from suit_generator.cmd_encrypt import main as cmd_encrypt_main
14+
15+
16+
TEMP_DIRECTORY = pathlib.Path("test_test_data")
17+
18+
19+
@pytest.fixture
20+
def setup_and_teardown(tmp_path_factory):
21+
"""Create and cleanup environment."""
22+
# Setup environment
23+
# - create required files in TEMP_DIRECTORY
24+
start_directory = os.getcwd()
25+
path = tmp_path_factory.mktemp(TEMP_DIRECTORY)
26+
os.chdir(path)
27+
yield
28+
# Cleanup environment
29+
# - remove temp directory
30+
os.chdir(start_directory)
31+
32+
33+
@pytest.mark.parametrize(
34+
"firmware, key_name, key_id, hash_alg, kw_alg, ctx, kms_script, encrypted_data, "
35+
+ "tag, encryption_info, digest, plaintext_length, output_dir_name",
36+
[
37+
(
38+
b"test_firmware",
39+
"test_key",
40+
0x40000000,
41+
"sha-256",
42+
"direct",
43+
"test_ctx",
44+
"test_kms_script.py",
45+
b"test_encrypted_data",
46+
b"test_tag",
47+
b"test_encryption_info",
48+
b"test_digest",
49+
123,
50+
"enc_artifacts1",
51+
),
52+
(
53+
b"firmware2",
54+
"key2",
55+
0x12345678,
56+
"sha-512",
57+
"aes-kw-256",
58+
"testctx2",
59+
"kms_script2.py",
60+
b"encrypted_data2",
61+
b"tag2",
62+
b"encryption_info2",
63+
b"digest2",
64+
5000,
65+
"enc_artifacts2",
66+
),
67+
],
68+
)
69+
def test_encrypt_cmd_encrypt_and_generate(
70+
setup_and_teardown,
71+
firmware,
72+
key_name,
73+
key_id,
74+
hash_alg,
75+
kw_alg,
76+
ctx,
77+
kms_script,
78+
encrypted_data,
79+
tag,
80+
encryption_info,
81+
digest,
82+
plaintext_length,
83+
output_dir_name,
84+
):
85+
"""Test the encrypt-and-generate command"""
86+
87+
full_context = json.dumps(
88+
{
89+
"ctx": ctx,
90+
"encrypted_data": encrypted_data.hex(),
91+
"tag": tag.hex(),
92+
"encryption_info": encryption_info.hex(),
93+
"digest": digest.hex(),
94+
"plaintext_length": plaintext_length,
95+
}
96+
)
97+
98+
os.mkdir(output_dir_name)
99+
firmware_file_name = "fw.bin"
100+
with open(firmware_file_name, "wb") as file:
101+
file.write(firmware)
102+
103+
kwargs = {
104+
"encrypt_subcommand": "encrypt-and-generate",
105+
"firmware": firmware_file_name,
106+
"key_name": key_name,
107+
"key_id": key_id,
108+
"hash_alg": hash_alg,
109+
"kw_alg": kw_alg,
110+
"context": full_context,
111+
"output_dir": output_dir_name,
112+
"kms_script": kms_script,
113+
"encrypt_script": str(encrypt_script_mock.__file__),
114+
}
115+
116+
cmd_encrypt_main(**kwargs)
117+
118+
assert os.path.exists(f"test_output_{key_name}.json")
119+
assert os.path.exists(output_dir_name)
120+
assert os.path.exists(f"{output_dir_name}/plain_text_digest.bin")
121+
assert os.path.exists(f"{output_dir_name}/plain_text_size.txt")
122+
assert os.path.exists(f"{output_dir_name}/suit_encryption_info.bin")
123+
assert os.path.exists(f"{output_dir_name}/encrypted_content.bin")
124+
125+
test_json_data = json.load(open(f"test_output_{key_name}.json"))
126+
127+
assert test_json_data["firmware"] == firmware.hex()
128+
assert test_json_data["key_name"] == key_name
129+
assert test_json_data["key_id"] == key_id
130+
assert test_json_data["kw_alg"] == kw_alg
131+
assert test_json_data["hash_alg"] == hash_alg
132+
assert test_json_data["context"] == ctx
133+
assert test_json_data["kms_script"] == kms_script
134+
135+
assert open(f"{output_dir_name}/plain_text_digest.bin", "rb").read() == digest
136+
assert open(f"{output_dir_name}/plain_text_size.txt", "r").read() == str(plaintext_length)
137+
assert open(f"{output_dir_name}/suit_encryption_info.bin", "rb").read() == encryption_info
138+
assert open(f"{output_dir_name}/encrypted_content.bin", "rb").read() == tag + encrypted_data
139+
140+
141+
@pytest.mark.parametrize(
142+
"encrypted_firmware, encrypted_key, key_id, kw_alg, encrypted_data, tag, encryption_info, output_dir_name",
143+
[
144+
(
145+
b"test_encrypted_firmware",
146+
b"test_encrypted_key",
147+
0x40000000,
148+
"direct",
149+
b"test_encrypted_data",
150+
b"test_tag",
151+
b"test_encryption_info",
152+
"gen_artifacts1",
153+
),
154+
(
155+
b"encrypted_firmware2",
156+
b"encrypted_key2",
157+
0x12345678,
158+
"aes-kw-256",
159+
b"encrypted_data2",
160+
b"tag2",
161+
b"encryption_info2",
162+
"gen_artifacts2",
163+
),
164+
],
165+
)
166+
def test_encrypt_cmd_generate(
167+
setup_and_teardown,
168+
encrypted_firmware,
169+
encrypted_key,
170+
key_id,
171+
kw_alg,
172+
encrypted_data,
173+
tag,
174+
encryption_info,
175+
output_dir_name,
176+
):
177+
"""Test the generate-info command"""
178+
179+
os.mkdir(output_dir_name)
180+
encrypted_firmware_file_name = "encrypted_fw.bin"
181+
encrypted_key_file_name = "encrypted_key.bin"
182+
183+
context = json.dumps(
184+
{
185+
"encrypted_data": encrypted_data.hex(),
186+
"tag": tag.hex(),
187+
"encryption_info": encryption_info.hex(),
188+
}
189+
)
190+
191+
with open(encrypted_firmware_file_name, "wb") as file:
192+
file.write(context.encode())
193+
with open(encrypted_key_file_name, "wb") as file:
194+
file.write(encrypted_key)
195+
196+
kwargs = {
197+
"encrypt_subcommand": "generate-info",
198+
"encrypted_firmware": encrypted_firmware_file_name,
199+
"encrypted_key": encrypted_key_file_name,
200+
"key_id": key_id,
201+
"kw_alg": kw_alg,
202+
"output_dir": output_dir_name,
203+
"encrypt_script": str(encrypt_script_mock.__file__),
204+
}
205+
206+
cmd_encrypt_main(**kwargs)
207+
208+
assert os.path.exists(f"test_output_{key_id}.json")
209+
assert os.path.exists(output_dir_name)
210+
assert os.path.exists(f"{output_dir_name}/suit_encryption_info.bin")
211+
assert os.path.exists(f"{output_dir_name}/encrypted_content.bin")
212+
213+
test_json_data = json.load(open(f"test_output_{key_id}.json"))
214+
215+
assert test_json_data["key_id"] == key_id
216+
assert test_json_data["kw_alg"] == kw_alg
217+
assert test_json_data["encrypted_cek"] == encrypted_key.hex()
218+
219+
assert open(f"{output_dir_name}/suit_encryption_info.bin", "rb").read() == encryption_info
220+
assert open(f"{output_dir_name}/encrypted_content.bin", "rb").read() == tag + encrypted_data

0 commit comments

Comments
 (0)