Skip to content

Commit

Permalink
imgtool: initial sanity test
Browse files Browse the repository at this point in the history
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 <denis@mingulov.com>
  • Loading branch information
mingulov committed Sep 27, 2023
1 parent 977a58b commit 02a80cc
Show file tree
Hide file tree
Showing 3 changed files with 377 additions and 0 deletions.
31 changes: 31 additions & 0 deletions scripts/tests/conftest.py
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()
112 changes: 112 additions & 0 deletions scripts/tests/test_commands.py
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
234 changes: 234 additions & 0 deletions scripts/tests/test_keys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# 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"
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


@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)
# x25519 is not used for signing
wrong_key = tmp_name(tmp_path_persistent, "x25519", GEN_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

result = runner.invoke(
imgtool,
[
"verify",
"--key",
str(gen_key),
str(image_signed),
],
)
assert result.exit_code == 0

result = runner.invoke(
imgtool,
[
"verify",
"--key",
str(wrong_key),
str(image_signed),
],
)
assert result.exit_code != 0
image_signed.unlink()

0 comments on commit 02a80cc

Please sign in to comment.