| # -*- coding: utf-8 -*- |
| # Copyright 2018 The Chromium OS Authors. All rights reserved. |
| # 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""" |
| |
| from __future__ import print_function |
| |
| import ConfigParser |
| import io |
| import os |
| |
| from chromite.lib import cros_test_lib |
| from chromite.signing.lib import keys |
| from chromite.signing.lib import signer |
| from chromite.signing.lib import keys_unittest |
| |
| |
| 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 sane 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): |
| 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): |
| 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.BytesIO() |
| cp.write(ini_in_file) |
| |
| # Read INI to new SignerConfig |
| read_sc = signer.SignerInstructionConfig() |
| read_sc.ReadIniFile(io.BytesIO(ini_in_file.getvalue())) |
| |
| self.assertEqual(initial_sc, read_sc) |
| |
| def testGetFilePairsSimple(self): |
| 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): |
| 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): |
| 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): |
| 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): |
| 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): |
| 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): |
| 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): |
| """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): |
| ks = keys.Keyset() |
| s = signer.BaseSigner() |
| with self.assertRaises(NotImplementedError): |
| s.Sign(ks, 'input', 'output') |
| |
| def testCheck(self): |
| ks = keys.Keyset() |
| s = signer.BaseSigner() |
| self.assertTrue(s.CheckKeyset(ks)) |
| |
| def testCheckRequiredKeysMissing(self): |
| ks_empty = keys.Keyset() |
| s0 = MockBaseSigner(required_keys=['key1']) |
| self.assertFalse(s0.CheckKeyset(ks_empty)) |
| |
| def testCheckRequiredKeys(self): |
| s0 = MockBaseSigner(required_keys=['key1']) |
| ks0 = KeysetFromSigner(s0, self.tempdir) |
| self.assertTrue(s0.CheckKeyset(ks0)) |
| |
| def testCheckRequiredPublicKeysMissing(self): |
| ks_empty = keys.Keyset() |
| s0 = MockBaseSigner(required_keys_public=['key1']) |
| self.assertFalse(s0.CheckKeyset(ks_empty)) |
| |
| def testCheckRequiredPublicKeys(self): |
| s0 = MockBaseSigner(required_keys_public=['key1']) |
| ks0 = KeysetFromSigner(s0, self.tempdir) |
| self.assertTrue(s0.CheckKeyset(ks0)) |
| |
| def testCheckRequiredPrivateKeysMissing(self): |
| ks_empty = keys.Keyset() |
| s0 = MockBaseSigner(required_keys_private=['key1']) |
| self.assertFalse(s0.CheckKeyset(ks_empty)) |
| |
| def testCheckRequiredPrivateKeys(self): |
| s0 = MockBaseSigner(required_keys_private=['key1']) |
| ks0 = KeysetFromSigner(s0, self.tempdir) |
| self.assertTrue(s0.CheckKeyset(ks0)) |
| |
| def testCheckRequiredKeyblocksEmpty(self): |
| ks_empty = keys.Keyset() |
| s0 = MockBaseSigner(required_keyblocks=['key1']) |
| self.assertFalse(s0.CheckKeyset(ks_empty)) |
| |
| def testCheckRequiredKeyblocks(self): |
| 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.CreateDummyKeys(key) |
| |
| for key_name in s.required_keys_public: |
| key = keys.KeyPair(key_name, keydir=keydir) |
| ks.AddKey(key) |
| keys_unittest.CreateDummyPublic(key) |
| |
| if key in s.required_keyblocks: |
| keys_unittest.CreateDummyKeyblock(key) |
| |
| for key_name in s.required_keys_private: |
| key = keys.KeyPair(key_name, keydir=keydir) |
| ks.AddKey(key) |
| keys_unittest.CreateDummyPrivateKey(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.CreateDummyKeyblock(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): |
| keyset = keys.Keyset() |
| fs = signer.FutilitySigner() |
| self.assertRaises(NotImplementedError, fs.Sign, keyset, 'dummy', 'dummy') |
| |
| def testSignWithMock(self): |
| foo_key = keys.KeyPair('foo', self.tempdir) |
| keys_unittest.CreateDummyKeys(foo_key) |
| |
| keyset = keys.Keyset() |
| keyset.AddKey(foo_key) |
| |
| fsm = MockFutilitySigner() |
| fsm.Sign(keyset, 'foo', 'bar') |
| self.assertCommandContains(['foo', 'bar']) |
| |
| def testSignWithMockMissingKey(self): |
| keyset = keys.Keyset() |
| fsm = MockFutilitySigner() |
| self.assertFalse(fsm.Sign(keyset, 'foo', 'bar')) |
| |
| def testGetCmdArgs(self): |
| 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): |
| self.assertTrue(signer.RunFutility([]), |
| msg='Futility should pass w/ mock') |
| self.assertCommandContains(['futility']) |
| |
| def testCommandWithArgs(self): |
| args = ['--privkey', 'foo.priv2'] |
| signer.RunFutility(args) |
| self.assertCommandContains(args) |