Skip to content

Commit 3113df8

Browse files
mingulovdavidvincze
authored andcommitted
imgtool: initial sanity test
An initial sanity test for imgtool is added, checks different commands for key operations (keygen, getpriv, getpub and getpubhash). Also very basic test for sign / verify is added. Some tests are disabled (marked as 'xfail') due to the missing implementation. Signed-off-by: Denis Mingulov <[email protected]>
1 parent a4cb878 commit 3113df8

File tree

3 files changed

+396
-0
lines changed

3 files changed

+396
-0
lines changed

scripts/tests/conftest.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import pytest
16+
17+
# List of tests expected to fail for some reason
18+
XFAILED_TESTS = {
19+
"tests/test_keys.py::test_getpriv[openssl-ed25519]",
20+
"tests/test_keys.py::test_getpriv[openssl-x25519]",
21+
"tests/test_keys.py::test_getpriv[pkcs8-rsa-2048]",
22+
"tests/test_keys.py::test_getpriv[pkcs8-rsa-3072]",
23+
"tests/test_keys.py::test_getpriv[pkcs8-ed25519]",
24+
"tests/test_keys.py::test_getpub[pem-ed25519]",
25+
"tests/test_keys.py::test_sign_verify[x25519]",
26+
}
27+
28+
29+
def pytest_runtest_setup(item):
30+
if item.nodeid in XFAILED_TESTS:
31+
pytest.xfail()

scripts/tests/test_commands.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import pytest
16+
17+
from click.testing import CliRunner
18+
from imgtool.main import imgtool
19+
from imgtool import imgtool_version
20+
21+
# all available imgtool commands
22+
COMMANDS = [
23+
"create",
24+
"dumpinfo",
25+
"getpriv",
26+
"getpub",
27+
"getpubhash",
28+
"keygen",
29+
"sign",
30+
"verify",
31+
"version",
32+
]
33+
34+
35+
def test_new_command():
36+
"""Check that no new commands had been added,
37+
so that tests would be updated in such case"""
38+
for cmd in imgtool.commands:
39+
assert cmd in COMMANDS
40+
41+
42+
def test_help():
43+
"""Simple test for the imgtool's help option,
44+
mostly just to see that it can be started"""
45+
runner = CliRunner()
46+
47+
result_short = runner.invoke(imgtool, ["-h"])
48+
assert result_short.exit_code == 0
49+
50+
result_long = runner.invoke(imgtool, ["--help"])
51+
assert result_long.exit_code == 0
52+
assert result_short.output == result_long.output
53+
54+
# by default help should be also produced
55+
result_empty = runner.invoke(imgtool)
56+
assert result_empty.exit_code == 0
57+
assert result_empty.output == result_short.output
58+
59+
60+
def test_version():
61+
"""Check that some version info is produced"""
62+
runner = CliRunner()
63+
64+
result = runner.invoke(imgtool, ["version"])
65+
assert result.exit_code == 0
66+
assert result.output == imgtool_version + "\n"
67+
68+
result_help = runner.invoke(imgtool, ["version", "-h"])
69+
assert result_help.exit_code == 0
70+
assert result_help.output != result.output
71+
72+
73+
def test_unknown():
74+
"""Check that unknown command will be handled"""
75+
runner = CliRunner()
76+
77+
result = runner.invoke(imgtool, ["unknown"])
78+
assert result.exit_code != 0
79+
80+
81+
@pytest.mark.parametrize("command", COMMANDS)
82+
def test_cmd_help(command):
83+
"""Check that all commands have some help"""
84+
runner = CliRunner()
85+
86+
result_short = runner.invoke(imgtool, [command, "-h"])
87+
assert result_short.exit_code == 0
88+
89+
result_long = runner.invoke(imgtool, [command, "--help"])
90+
assert result_long.exit_code == 0
91+
92+
assert result_short.output == result_long.output
93+
94+
95+
@pytest.mark.parametrize("command1", COMMANDS)
96+
@pytest.mark.parametrize("command2", COMMANDS)
97+
def test_cmd_dif_help(command1, command2):
98+
"""Check that all commands have some different help"""
99+
runner = CliRunner()
100+
101+
result_general = runner.invoke(imgtool, "--help")
102+
assert result_general.exit_code == 0
103+
104+
result_cmd1 = runner.invoke(imgtool, [command1, "--help"])
105+
assert result_cmd1.exit_code == 0
106+
assert result_cmd1.output != result_general.output
107+
108+
if command1 != command2:
109+
result_cmd2 = runner.invoke(imgtool, [command2, "--help"])
110+
assert result_cmd2.exit_code == 0
111+
112+
assert result_cmd1.output != result_cmd2.output

scripts/tests/test_keys.py

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import pytest
16+
17+
import subprocess
18+
from click.testing import CliRunner
19+
from imgtool import main as imgtool_main
20+
from imgtool.main import imgtool
21+
22+
# all supported key types for 'keygen'
23+
KEY_TYPES = [*imgtool_main.keygens]
24+
KEY_ENCODINGS = [*imgtool_main.valid_encodings]
25+
PUB_HASH_ENCODINGS = [*imgtool_main.valid_hash_encodings]
26+
PVT_KEY_FORMATS = [*imgtool_main.valid_formats]
27+
28+
OPENSSL_KEY_TYPES = {
29+
"rsa-2048": "Private-Key: (2048 bit, 2 primes)",
30+
"rsa-3072": "Private-Key: (3072 bit, 2 primes)",
31+
"ecdsa-p256": "Private-Key: (256 bit)",
32+
"ecdsa-p384": "Private-Key: (384 bit)",
33+
"ed25519": "ED25519 Private-Key:",
34+
"x25519": "X25519 Private-Key:",
35+
}
36+
37+
GEN_KEY_EXT = ".key"
38+
GEN_ANOTHER_KEY_EXT = ".another.key"
39+
PUB_KEY_EXT = ".pub"
40+
PUB_KEY_HASH_EXT = ".pubhash"
41+
42+
43+
def tmp_name(tmp_path, key_type, suffix=""):
44+
return tmp_path / (key_type + suffix)
45+
46+
47+
@pytest.fixture(scope="session")
48+
def tmp_path_persistent(tmp_path_factory):
49+
return tmp_path_factory.mktemp("keys")
50+
51+
52+
@pytest.mark.parametrize("key_type", KEY_TYPES)
53+
def test_keygen(key_type, tmp_path_persistent):
54+
"""Generate keys by imgtool"""
55+
56+
runner = CliRunner()
57+
58+
gen_key = tmp_name(tmp_path_persistent, key_type, GEN_KEY_EXT)
59+
60+
assert not gen_key.exists()
61+
result = runner.invoke(
62+
imgtool, ["keygen", "--key", str(gen_key), "--type", key_type]
63+
)
64+
assert result.exit_code == 0
65+
assert gen_key.exists()
66+
assert gen_key.stat().st_size > 0
67+
68+
# another key
69+
gen_key2 = tmp_name(tmp_path_persistent, key_type, GEN_ANOTHER_KEY_EXT)
70+
71+
assert str(gen_key2) != str(gen_key)
72+
73+
assert not gen_key2.exists()
74+
result = runner.invoke(
75+
imgtool, ["keygen", "--key", str(gen_key2), "--type", key_type]
76+
)
77+
assert result.exit_code == 0
78+
assert gen_key2.exists()
79+
assert gen_key2.stat().st_size > 0
80+
81+
# content must be different
82+
assert gen_key.read_bytes() != gen_key2.read_bytes()
83+
84+
85+
@pytest.mark.parametrize("key_type", KEY_TYPES)
86+
def test_keygen_type(key_type, tmp_path_persistent):
87+
"""Check generated keys"""
88+
assert key_type in OPENSSL_KEY_TYPES
89+
90+
gen_key = tmp_name(tmp_path_persistent, key_type, GEN_KEY_EXT)
91+
92+
result = subprocess.run(
93+
["openssl", "pkey", "-in", str(gen_key), "-check", "-noout", "-text"],
94+
capture_output=True,
95+
text=True,
96+
)
97+
assert result.returncode == 0
98+
assert "Key is valid" in result.stdout
99+
assert OPENSSL_KEY_TYPES[key_type] in result.stdout
100+
101+
102+
@pytest.mark.parametrize("key_type", KEY_TYPES)
103+
@pytest.mark.parametrize("format", PVT_KEY_FORMATS)
104+
def test_getpriv(key_type, format, tmp_path_persistent):
105+
"""Get private key"""
106+
runner = CliRunner()
107+
108+
gen_key = tmp_name(tmp_path_persistent, key_type, GEN_KEY_EXT)
109+
110+
result = runner.invoke(
111+
imgtool,
112+
[
113+
"getpriv",
114+
"--key",
115+
str(gen_key),
116+
"--format",
117+
format,
118+
],
119+
)
120+
assert result.exit_code == 0
121+
122+
123+
@pytest.mark.parametrize("key_type", KEY_TYPES)
124+
@pytest.mark.parametrize("encoding", KEY_ENCODINGS)
125+
def test_getpub(key_type, encoding, tmp_path_persistent):
126+
"""Get public key"""
127+
runner = CliRunner()
128+
129+
gen_key = tmp_name(tmp_path_persistent, key_type, GEN_KEY_EXT)
130+
pub_key = tmp_name(tmp_path_persistent, key_type, PUB_KEY_EXT
131+
+ "." + encoding)
132+
133+
assert not pub_key.exists()
134+
result = runner.invoke(
135+
imgtool,
136+
[
137+
"getpub",
138+
"--key",
139+
str(gen_key),
140+
"--output",
141+
str(pub_key),
142+
"--encoding",
143+
encoding,
144+
],
145+
)
146+
assert result.exit_code == 0
147+
assert pub_key.exists()
148+
assert pub_key.stat().st_size > 0
149+
150+
151+
@pytest.mark.parametrize("key_type", KEY_TYPES)
152+
@pytest.mark.parametrize("encoding", PUB_HASH_ENCODINGS)
153+
def test_getpubhash(key_type, encoding, tmp_path_persistent):
154+
"""Get the hash of the public key"""
155+
runner = CliRunner()
156+
157+
gen_key = tmp_name(tmp_path_persistent, key_type, GEN_KEY_EXT)
158+
pub_key_hash = tmp_name(
159+
tmp_path_persistent, key_type, PUB_KEY_HASH_EXT + "." + encoding
160+
)
161+
162+
assert not pub_key_hash.exists()
163+
result = runner.invoke(
164+
imgtool,
165+
[
166+
"getpubhash",
167+
"--key",
168+
str(gen_key),
169+
"--output",
170+
str(pub_key_hash),
171+
"--encoding",
172+
encoding,
173+
],
174+
)
175+
assert result.exit_code == 0
176+
assert pub_key_hash.exists()
177+
assert pub_key_hash.stat().st_size > 0
178+
179+
180+
@pytest.mark.parametrize("key_type", KEY_TYPES)
181+
def test_sign_verify(key_type, tmp_path_persistent):
182+
"""Test basic sign and verify"""
183+
runner = CliRunner()
184+
185+
gen_key = tmp_name(tmp_path_persistent, key_type, GEN_KEY_EXT)
186+
wrong_key = tmp_name(tmp_path_persistent, key_type, GEN_ANOTHER_KEY_EXT)
187+
image = tmp_name(tmp_path_persistent, "image", "bin")
188+
image_signed = tmp_name(tmp_path_persistent, "image", "signed")
189+
190+
with image.open("wb") as f:
191+
f.write(b"\x00" * 1024)
192+
193+
# not all required arguments are provided
194+
result = runner.invoke(
195+
imgtool,
196+
[
197+
"sign",
198+
"--key",
199+
str(gen_key),
200+
str(image),
201+
str(image_signed),
202+
],
203+
)
204+
assert result.exit_code != 0
205+
assert not image_signed.exists()
206+
207+
result = runner.invoke(
208+
imgtool,
209+
[
210+
"sign",
211+
"--key",
212+
str(gen_key),
213+
"--align",
214+
"16",
215+
"--version",
216+
"1.0.0",
217+
"--header-size",
218+
"0x400",
219+
"--slot-size",
220+
"0x10000",
221+
"--pad-header",
222+
str(image),
223+
str(image_signed),
224+
],
225+
)
226+
assert result.exit_code == 0
227+
assert image_signed.exists()
228+
assert image_signed.stat().st_size > 0
229+
230+
# original key can be used to verify a signed image
231+
result = runner.invoke(
232+
imgtool,
233+
[
234+
"verify",
235+
"--key",
236+
str(gen_key),
237+
str(image_signed),
238+
],
239+
)
240+
assert result.exit_code == 0
241+
242+
# 'another' key is not valid to verify a signed image
243+
result = runner.invoke(
244+
imgtool,
245+
[
246+
"verify",
247+
"--key",
248+
str(wrong_key),
249+
str(image_signed),
250+
],
251+
)
252+
assert result.exit_code != 0
253+
image_signed.unlink()

0 commit comments

Comments
 (0)