blob: c361c5dd9ecee9792ce07bff71929da637faccf6 [file] [log] [blame]
# Copyright 2018 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unittest ChromeOS image signer logic"""
import configparser
import io
import os
from chromite.lib import cros_test_lib
from chromite.signing.lib import keys
from chromite.signing.lib import keys_unittest
from chromite.signing.lib import signer
class TestSignerConfig(cros_test_lib.TestCase):
"""Test SignerConfig."""
def GetSignerConfig(
self,
archive="foo.tar.bz2",
board="link",
artifact_type="update_payload",
version="1.2.3.4",
versionrev="R24-1.2.3.4",
keyset="link-mp",
channel="dev",
input_files=("foo.bin"),
output_files=("@ROOTNAME@-@VERSION@.bin"),
):
"""Returns SignerConfig, providing the defaults."""
return signer.SignerInstructionConfig(
archive=archive,
board=board,
artifact_type=artifact_type,
version=version,
versionrev=versionrev,
keyset=keyset,
channel=channel,
input_files=input_files,
output_files=output_files,
)
def testToIniDictSimple(self) -> None:
self.assertDictEqual(
self.GetSignerConfig().ToIniDict(),
{
"general": {
"archive": "foo.tar.bz2",
"board": "link",
"type": "update_payload",
"version": "1.2.3.4",
"versionrev": "R24-1.2.3.4",
},
"insns": {
"keyset": "link-mp",
"channel": "dev",
"input_files": "foo.bin",
"output_names": "@ROOTNAME@-@VERSION@.bin",
},
},
)
def testReadIniFile(self) -> None:
initial_sc = self.GetSignerConfig()
# Create INI file from initial SignerConfig
cp = configparser.ConfigParser()
for section, options in initial_sc.ToIniDict().items():
cp.add_section(section)
for option, value in options.items():
cp.set(section, option, value=value)
ini_in_file = io.StringIO()
cp.write(ini_in_file)
# Read INI to new SignerConfig
read_sc = signer.SignerInstructionConfig()
ini_in_file.seek(0)
read_sc.ReadIniFile(ini_in_file)
self.assertEqual(initial_sc, read_sc)
def testGetFilePairsSimple(self) -> None:
in_files = "foo.bar"
out_files = "foo.out.bar"
sc = self.GetSignerConfig(input_files=in_files, output_files=out_files)
self.assertListEqual(sc.GetFilePairs(), [(in_files, out_files)])
def testGetFilePairsSimpleMultiple(self) -> None:
in_files = ("foo.bin", "bar.bin")
out_files = ("foo.out.bin", "bar.out.bin")
sc = self.GetSignerConfig(input_files=in_files, output_files=out_files)
self.assertListEqual(
sc.GetFilePairs(),
[("foo.bin", "foo.out.bin"), ("bar.bin", "bar.out.bin")],
)
def testGetFilePairsSimpleTemplate(self) -> None:
in_files = "foo.bar"
sc = self.GetSignerConfig(input_files=in_files)
self.assertListEqual(sc.GetFilePairs(), [(in_files, "foo-1.2.3.4.bin")])
def testGetFilePairsDefault(self) -> None:
in_file = "foo.bar"
out_file = (
"chromeos_1.2.3.4_link_update_payload_dev-channel_link-mp.bin"
)
sc = self.GetSignerConfig(input_files=in_file, output_files=())
self.assertListEqual(sc.GetFilePairs(), [(in_file, out_file)])
def testGetFilePairsMultipleInput(self) -> None:
in_files = ("foo.bin", "bar.bin")
sc = self.GetSignerConfig(input_files=in_files)
self.assertListEqual(
sc.GetFilePairs(),
[("foo.bin", "foo-1.2.3.4.bin"), ("bar.bin", "bar-1.2.3.4.bin")],
)
def testGetFilePairsMultipleInputDefaultTemp(self) -> None:
in_files = ("foo.bin", "bar.bin")
sc = self.GetSignerConfig(input_files=in_files, output_files=())
with self.assertRaises(signer.SignerOutputTemplateError):
sc.GetFilePairs()
def testFillTemplate(self) -> None:
sc = self.GetSignerConfig()
in_file = "/tmp/foo.bar"
self.assertEqual("foo.out", sc.FillTemplate("foo.out"))
self.assertEqual(
"foo.out", sc.FillTemplate("foo.out", filename=in_file)
)
self.assertEqual("__link__", sc.FillTemplate("__@BOARD@__"))
self.assertEqual("__dev__", sc.FillTemplate("__@CHANNEL@__"))
self.assertEqual("__link-mp__", sc.FillTemplate("__@KEYSET@__"))
self.assertEqual("__update_payload__", sc.FillTemplate("__@TYPE@__"))
self.assertEqual("__1.2.3.4__", sc.FillTemplate("__@VERSION@__"))
self.assertEqual(
"__foo.bar__", sc.FillTemplate("__@BASENAME@__", filename=in_file)
)
self.assertEqual(
"__foo__", sc.FillTemplate("__@ROOTNAME@__", filename=in_file)
)
class MockBaseSigner(signer.BaseSigner):
"""Configurable Signer for testing."""
def __init__(
self,
required_keys=None,
required_keys_public=None,
required_keys_private=None,
required_keyblocks=None,
) -> None:
"""Create a Signer based on the passed required lists."""
self.required_keys = required_keys or []
self.required_keys_public = required_keys_public or []
self.required_keys_private = required_keys_private or []
self.required_keyblocks = required_keyblocks or []
def Sign(self, keyset, input_name, output_name):
"""Always return True on signing."""
return True
class TestSigner(cros_test_lib.TempDirTestCase):
"""Test Signer."""
def testSign(self) -> None:
ks = keys.Keyset()
s = signer.BaseSigner()
with self.assertRaises(NotImplementedError):
s.Sign(ks, "input", "output")
def testCheck(self) -> None:
ks = keys.Keyset()
s = signer.BaseSigner()
self.assertTrue(s.CheckKeyset(ks))
def testCheckRequiredKeysMissing(self) -> None:
ks_empty = keys.Keyset()
s0 = MockBaseSigner(required_keys=["key1"])
self.assertFalse(s0.CheckKeyset(ks_empty))
def testCheckRequiredKeys(self) -> None:
s0 = MockBaseSigner(required_keys=["key1"])
ks0 = KeysetFromSigner(s0, self.tempdir)
self.assertTrue(s0.CheckKeyset(ks0))
def testCheckRequiredPublicKeysMissing(self) -> None:
ks_empty = keys.Keyset()
s0 = MockBaseSigner(required_keys_public=["key1"])
self.assertFalse(s0.CheckKeyset(ks_empty))
def testCheckRequiredPublicKeys(self) -> None:
s0 = MockBaseSigner(required_keys_public=["key1"])
ks0 = KeysetFromSigner(s0, self.tempdir)
self.assertTrue(s0.CheckKeyset(ks0))
def testCheckRequiredPrivateKeysMissing(self) -> None:
ks_empty = keys.Keyset()
s0 = MockBaseSigner(required_keys_private=["key1"])
self.assertFalse(s0.CheckKeyset(ks_empty))
def testCheckRequiredPrivateKeys(self) -> None:
s0 = MockBaseSigner(required_keys_private=["key1"])
ks0 = KeysetFromSigner(s0, self.tempdir)
self.assertTrue(s0.CheckKeyset(ks0))
def testCheckRequiredKeyblocksEmpty(self) -> None:
ks_empty = keys.Keyset()
s0 = MockBaseSigner(required_keyblocks=["key1"])
self.assertFalse(s0.CheckKeyset(ks_empty))
def testCheckRequiredKeyblocks(self) -> None:
s0 = MockBaseSigner(required_keyblocks=["key1"])
ks0 = KeysetFromSigner(s0, self.tempdir)
self.assertTrue(s0.CheckKeyset(ks0))
def KeysetFromSigner(s, keydir, subdir="keyset"):
"""Returns a valid keyset containing required keys and keyblocks."""
ks = keys.Keyset()
keydir = os.path.join(keydir, subdir)
for key_name in s.required_keys:
key = keys.KeyPair(key_name, keydir=keydir)
ks.AddKey(key)
keys_unittest.CreateStubKeys(key)
for key_name in s.required_keys_public:
key = keys.KeyPair(key_name, keydir=keydir)
ks.AddKey(key)
keys_unittest.CreateStubPublic(key)
if key in s.required_keyblocks:
keys_unittest.CreateStubKeyblock(key)
for key_name in s.required_keys_private:
key = keys.KeyPair(key_name, keydir=keydir)
ks.AddKey(key)
keys_unittest.CreateStubPrivateKey(key)
for keyblock_name in s.required_keyblocks:
if keyblock_name not in ks.keys:
ks.AddKey(keys.KeyPair(keyblock_name, keydir=keydir))
key = ks.keys[keyblock_name]
keys_unittest.CreateStubKeyblock(key)
return ks
class MockFutilitySigner(signer.FutilitySigner):
"""Basic implementation of a FutilitySigner."""
required_keys = ("foo",)
def GetFutilityArgs(self, keyset, input_name, output_name):
"""Returns a list of [input_name, output_name]."""
return [input_name, output_name]
class TestFutilitySigner(cros_test_lib.RunCommandTempDirTestCase):
"""Test Futility Signer."""
def testSign(self) -> None:
keyset = keys.Keyset()
fs = signer.FutilitySigner()
self.assertRaises(NotImplementedError, fs.Sign, keyset, "stub", "stub")
def testSignWithMock(self) -> None:
foo_key = keys.KeyPair("foo", self.tempdir)
keys_unittest.CreateStubKeys(foo_key)
keyset = keys.Keyset()
keyset.AddKey(foo_key)
fsm = MockFutilitySigner()
fsm.Sign(keyset, "foo", "bar")
self.assertCommandContains(["foo", "bar"])
def testSignWithMockMissingKey(self) -> None:
keyset = keys.Keyset()
fsm = MockFutilitySigner()
self.assertFalse(fsm.Sign(keyset, "foo", "bar"))
def testGetCmdArgs(self) -> None:
keyset = keys.Keyset()
fs = signer.FutilitySigner()
self.assertRaises(
NotImplementedError, fs.GetFutilityArgs, keyset, "foo", "bar"
)
class TestFutilityFunction(cros_test_lib.RunCommandTestCase):
"""Test Futility command."""
def testCommand(self) -> None:
self.assertTrue(
signer.RunFutility([]), msg="Futility should pass w/ mock"
)
self.assertCommandContains(["futility"])
def testCommandWithArgs(self) -> None:
args = ["--privkey", "foo.priv2"]
signer.RunFutility(args)
self.assertCommandContains(args)