forked from mcu-tools/mcuboot
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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]>
- Loading branch information
1 parent
a4cb878
commit 3113df8
Showing
3 changed files
with
396 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# SPDX-License-Identifier: Apache-2.0 | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import pytest | ||
|
||
# List of tests expected to fail for some reason | ||
XFAILED_TESTS = { | ||
"tests/test_keys.py::test_getpriv[openssl-ed25519]", | ||
"tests/test_keys.py::test_getpriv[openssl-x25519]", | ||
"tests/test_keys.py::test_getpriv[pkcs8-rsa-2048]", | ||
"tests/test_keys.py::test_getpriv[pkcs8-rsa-3072]", | ||
"tests/test_keys.py::test_getpriv[pkcs8-ed25519]", | ||
"tests/test_keys.py::test_getpub[pem-ed25519]", | ||
"tests/test_keys.py::test_sign_verify[x25519]", | ||
} | ||
|
||
|
||
def pytest_runtest_setup(item): | ||
if item.nodeid in XFAILED_TESTS: | ||
pytest.xfail() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
# SPDX-License-Identifier: Apache-2.0 | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import pytest | ||
|
||
from click.testing import CliRunner | ||
from imgtool.main import imgtool | ||
from imgtool import imgtool_version | ||
|
||
# all available imgtool commands | ||
COMMANDS = [ | ||
"create", | ||
"dumpinfo", | ||
"getpriv", | ||
"getpub", | ||
"getpubhash", | ||
"keygen", | ||
"sign", | ||
"verify", | ||
"version", | ||
] | ||
|
||
|
||
def test_new_command(): | ||
"""Check that no new commands had been added, | ||
so that tests would be updated in such case""" | ||
for cmd in imgtool.commands: | ||
assert cmd in COMMANDS | ||
|
||
|
||
def test_help(): | ||
"""Simple test for the imgtool's help option, | ||
mostly just to see that it can be started""" | ||
runner = CliRunner() | ||
|
||
result_short = runner.invoke(imgtool, ["-h"]) | ||
assert result_short.exit_code == 0 | ||
|
||
result_long = runner.invoke(imgtool, ["--help"]) | ||
assert result_long.exit_code == 0 | ||
assert result_short.output == result_long.output | ||
|
||
# by default help should be also produced | ||
result_empty = runner.invoke(imgtool) | ||
assert result_empty.exit_code == 0 | ||
assert result_empty.output == result_short.output | ||
|
||
|
||
def test_version(): | ||
"""Check that some version info is produced""" | ||
runner = CliRunner() | ||
|
||
result = runner.invoke(imgtool, ["version"]) | ||
assert result.exit_code == 0 | ||
assert result.output == imgtool_version + "\n" | ||
|
||
result_help = runner.invoke(imgtool, ["version", "-h"]) | ||
assert result_help.exit_code == 0 | ||
assert result_help.output != result.output | ||
|
||
|
||
def test_unknown(): | ||
"""Check that unknown command will be handled""" | ||
runner = CliRunner() | ||
|
||
result = runner.invoke(imgtool, ["unknown"]) | ||
assert result.exit_code != 0 | ||
|
||
|
||
@pytest.mark.parametrize("command", COMMANDS) | ||
def test_cmd_help(command): | ||
"""Check that all commands have some help""" | ||
runner = CliRunner() | ||
|
||
result_short = runner.invoke(imgtool, [command, "-h"]) | ||
assert result_short.exit_code == 0 | ||
|
||
result_long = runner.invoke(imgtool, [command, "--help"]) | ||
assert result_long.exit_code == 0 | ||
|
||
assert result_short.output == result_long.output | ||
|
||
|
||
@pytest.mark.parametrize("command1", COMMANDS) | ||
@pytest.mark.parametrize("command2", COMMANDS) | ||
def test_cmd_dif_help(command1, command2): | ||
"""Check that all commands have some different help""" | ||
runner = CliRunner() | ||
|
||
result_general = runner.invoke(imgtool, "--help") | ||
assert result_general.exit_code == 0 | ||
|
||
result_cmd1 = runner.invoke(imgtool, [command1, "--help"]) | ||
assert result_cmd1.exit_code == 0 | ||
assert result_cmd1.output != result_general.output | ||
|
||
if command1 != command2: | ||
result_cmd2 = runner.invoke(imgtool, [command2, "--help"]) | ||
assert result_cmd2.exit_code == 0 | ||
|
||
assert result_cmd1.output != result_cmd2.output |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,253 @@ | ||
# SPDX-License-Identifier: Apache-2.0 | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import pytest | ||
|
||
import subprocess | ||
from click.testing import CliRunner | ||
from imgtool import main as imgtool_main | ||
from imgtool.main import imgtool | ||
|
||
# all supported key types for 'keygen' | ||
KEY_TYPES = [*imgtool_main.keygens] | ||
KEY_ENCODINGS = [*imgtool_main.valid_encodings] | ||
PUB_HASH_ENCODINGS = [*imgtool_main.valid_hash_encodings] | ||
PVT_KEY_FORMATS = [*imgtool_main.valid_formats] | ||
|
||
OPENSSL_KEY_TYPES = { | ||
"rsa-2048": "Private-Key: (2048 bit, 2 primes)", | ||
"rsa-3072": "Private-Key: (3072 bit, 2 primes)", | ||
"ecdsa-p256": "Private-Key: (256 bit)", | ||
"ecdsa-p384": "Private-Key: (384 bit)", | ||
"ed25519": "ED25519 Private-Key:", | ||
"x25519": "X25519 Private-Key:", | ||
} | ||
|
||
GEN_KEY_EXT = ".key" | ||
GEN_ANOTHER_KEY_EXT = ".another.key" | ||
PUB_KEY_EXT = ".pub" | ||
PUB_KEY_HASH_EXT = ".pubhash" | ||
|
||
|
||
def tmp_name(tmp_path, key_type, suffix=""): | ||
return tmp_path / (key_type + suffix) | ||
|
||
|
||
@pytest.fixture(scope="session") | ||
def tmp_path_persistent(tmp_path_factory): | ||
return tmp_path_factory.mktemp("keys") | ||
|
||
|
||
@pytest.mark.parametrize("key_type", KEY_TYPES) | ||
def test_keygen(key_type, tmp_path_persistent): | ||
"""Generate keys by imgtool""" | ||
|
||
runner = CliRunner() | ||
|
||
gen_key = tmp_name(tmp_path_persistent, key_type, GEN_KEY_EXT) | ||
|
||
assert not gen_key.exists() | ||
result = runner.invoke( | ||
imgtool, ["keygen", "--key", str(gen_key), "--type", key_type] | ||
) | ||
assert result.exit_code == 0 | ||
assert gen_key.exists() | ||
assert gen_key.stat().st_size > 0 | ||
|
||
# another key | ||
gen_key2 = tmp_name(tmp_path_persistent, key_type, GEN_ANOTHER_KEY_EXT) | ||
|
||
assert str(gen_key2) != str(gen_key) | ||
|
||
assert not gen_key2.exists() | ||
result = runner.invoke( | ||
imgtool, ["keygen", "--key", str(gen_key2), "--type", key_type] | ||
) | ||
assert result.exit_code == 0 | ||
assert gen_key2.exists() | ||
assert gen_key2.stat().st_size > 0 | ||
|
||
# content must be different | ||
assert gen_key.read_bytes() != gen_key2.read_bytes() | ||
|
||
|
||
@pytest.mark.parametrize("key_type", KEY_TYPES) | ||
def test_keygen_type(key_type, tmp_path_persistent): | ||
"""Check generated keys""" | ||
assert key_type in OPENSSL_KEY_TYPES | ||
|
||
gen_key = tmp_name(tmp_path_persistent, key_type, GEN_KEY_EXT) | ||
|
||
result = subprocess.run( | ||
["openssl", "pkey", "-in", str(gen_key), "-check", "-noout", "-text"], | ||
capture_output=True, | ||
text=True, | ||
) | ||
assert result.returncode == 0 | ||
assert "Key is valid" in result.stdout | ||
assert OPENSSL_KEY_TYPES[key_type] in result.stdout | ||
|
||
|
||
@pytest.mark.parametrize("key_type", KEY_TYPES) | ||
@pytest.mark.parametrize("format", PVT_KEY_FORMATS) | ||
def test_getpriv(key_type, format, tmp_path_persistent): | ||
"""Get private key""" | ||
runner = CliRunner() | ||
|
||
gen_key = tmp_name(tmp_path_persistent, key_type, GEN_KEY_EXT) | ||
|
||
result = runner.invoke( | ||
imgtool, | ||
[ | ||
"getpriv", | ||
"--key", | ||
str(gen_key), | ||
"--format", | ||
format, | ||
], | ||
) | ||
assert result.exit_code == 0 | ||
|
||
|
||
@pytest.mark.parametrize("key_type", KEY_TYPES) | ||
@pytest.mark.parametrize("encoding", KEY_ENCODINGS) | ||
def test_getpub(key_type, encoding, tmp_path_persistent): | ||
"""Get public key""" | ||
runner = CliRunner() | ||
|
||
gen_key = tmp_name(tmp_path_persistent, key_type, GEN_KEY_EXT) | ||
pub_key = tmp_name(tmp_path_persistent, key_type, PUB_KEY_EXT | ||
+ "." + encoding) | ||
|
||
assert not pub_key.exists() | ||
result = runner.invoke( | ||
imgtool, | ||
[ | ||
"getpub", | ||
"--key", | ||
str(gen_key), | ||
"--output", | ||
str(pub_key), | ||
"--encoding", | ||
encoding, | ||
], | ||
) | ||
assert result.exit_code == 0 | ||
assert pub_key.exists() | ||
assert pub_key.stat().st_size > 0 | ||
|
||
|
||
@pytest.mark.parametrize("key_type", KEY_TYPES) | ||
@pytest.mark.parametrize("encoding", PUB_HASH_ENCODINGS) | ||
def test_getpubhash(key_type, encoding, tmp_path_persistent): | ||
"""Get the hash of the public key""" | ||
runner = CliRunner() | ||
|
||
gen_key = tmp_name(tmp_path_persistent, key_type, GEN_KEY_EXT) | ||
pub_key_hash = tmp_name( | ||
tmp_path_persistent, key_type, PUB_KEY_HASH_EXT + "." + encoding | ||
) | ||
|
||
assert not pub_key_hash.exists() | ||
result = runner.invoke( | ||
imgtool, | ||
[ | ||
"getpubhash", | ||
"--key", | ||
str(gen_key), | ||
"--output", | ||
str(pub_key_hash), | ||
"--encoding", | ||
encoding, | ||
], | ||
) | ||
assert result.exit_code == 0 | ||
assert pub_key_hash.exists() | ||
assert pub_key_hash.stat().st_size > 0 | ||
|
||
|
||
@pytest.mark.parametrize("key_type", KEY_TYPES) | ||
def test_sign_verify(key_type, tmp_path_persistent): | ||
"""Test basic sign and verify""" | ||
runner = CliRunner() | ||
|
||
gen_key = tmp_name(tmp_path_persistent, key_type, GEN_KEY_EXT) | ||
wrong_key = tmp_name(tmp_path_persistent, key_type, GEN_ANOTHER_KEY_EXT) | ||
image = tmp_name(tmp_path_persistent, "image", "bin") | ||
image_signed = tmp_name(tmp_path_persistent, "image", "signed") | ||
|
||
with image.open("wb") as f: | ||
f.write(b"\x00" * 1024) | ||
|
||
# not all required arguments are provided | ||
result = runner.invoke( | ||
imgtool, | ||
[ | ||
"sign", | ||
"--key", | ||
str(gen_key), | ||
str(image), | ||
str(image_signed), | ||
], | ||
) | ||
assert result.exit_code != 0 | ||
assert not image_signed.exists() | ||
|
||
result = runner.invoke( | ||
imgtool, | ||
[ | ||
"sign", | ||
"--key", | ||
str(gen_key), | ||
"--align", | ||
"16", | ||
"--version", | ||
"1.0.0", | ||
"--header-size", | ||
"0x400", | ||
"--slot-size", | ||
"0x10000", | ||
"--pad-header", | ||
str(image), | ||
str(image_signed), | ||
], | ||
) | ||
assert result.exit_code == 0 | ||
assert image_signed.exists() | ||
assert image_signed.stat().st_size > 0 | ||
|
||
# original key can be used to verify a signed image | ||
result = runner.invoke( | ||
imgtool, | ||
[ | ||
"verify", | ||
"--key", | ||
str(gen_key), | ||
str(image_signed), | ||
], | ||
) | ||
assert result.exit_code == 0 | ||
|
||
# 'another' key is not valid to verify a signed image | ||
result = runner.invoke( | ||
imgtool, | ||
[ | ||
"verify", | ||
"--key", | ||
str(wrong_key), | ||
str(image_signed), | ||
], | ||
) | ||
assert result.exit_code != 0 | ||
image_signed.unlink() |