blob: 60cc9cfb39e8692f5acbd28ddc2ebd5419c54b59 [file] [log] [blame]
# -*- 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 io
import os
import sys
from six.moves import configparser
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)
# Python 2 is picky with unicode-vs-str in the CSV module.
# TODO(vapier): Drop this once we're Python 3-only.
if sys.version_info.major < 3:
ini_in_file = io.BytesIO()
else:
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):
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)