blob: 2d346bc5c9006c5f3c85a2333f2b862ed3843c1c [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.
"""Tests Firmware related signing"""
from __future__ import print_function
import csv
import io
import os
import shutil
import sys
import textwrap
from chromite.lib import cros_test_lib
from chromite.lib import osutils
from chromite.lib import partial_mock
from chromite.signing.lib import firmware
from chromite.signing.lib import keys
from chromite.signing.lib import keys_unittest
from chromite.signing.lib import signer_unittest
def MockDumpFmap(rc, ec_ro=True):
"""Add futility dump_fmap mock for bios.bin and ec.bin."""
bios_output = textwrap.dedent("""
SI_ALL 0 2097152
SI_DESC 0 4096
SI_ME 4096 2093056
SI_BIOS 2097152 14680064
RW_SECTION_A 2097152 4096000
VBLOCK_A 2097152 65536
FW_MAIN_A 2162688 4030400
RW_FWID_A 6193088 64
RW_SECTION_B 6193152 4096000
VBLOCK_B 6193152 65536
FW_MAIN_B 6258688 4030400
RW_FWID_B 10289088 64
RW_MISC 10289152 196608
UNIFIED_MRC_CACHE 10289152 131072
RECOVERY_MRC_CACHE 10289152 65536
RW_MRC_CACHE 10354688 65536
RW_ELOG 10420224 16384
RW_SHARED 10436608 16384
SHARED_DATA 10436608 8192
VBLOCK_DEV 10444800 8192
RW_VPD 10452992 8192
RW_NVRAM 10461184 24576
RW_LEGACY 10485760 2097152
WP_RO 12582912 4194304
RO_VPD 12582912 16384
RO_UNUSED 12599296 49152
RO_SECTION 12648448 4128768
FMAP 12648448 2048
RO_FRID 12650496 64
RO_FRID_PAD 12650560 1984
GBB 12652544 978944
COREBOOT 13631488
""")
ec_output = textwrap.dedent("""
EC_RO 64 131072
FR_MAIN 64 131072
RO_FRID 388 32
FMAP 104000 518
WP_RO 0 262144
EC_RW 262144 131072
RW_FWID 262468 32
SIG_RW 392192 1024
EC_RW_B 393216 131072
""")
if ec_ro:
ec_output += 'KEY_RO 130112 1024'
rc.AddCmdResult(partial_mock.ListRegex('futility dump_fmap -p .*bios.bin'),
output=bios_output)
rc.AddCmdResult(partial_mock.ListRegex('futility dump_fmap -p .*ec.bin'),
output=ec_output)
def MockBiosSigner(rc):
"""Add Bios Signing Mocks to |rc|."""
def _copy_firmware(cmd, *_args, **_kwargs):
"""Copy file_in to file_out, if file_in exists."""
file_in = cmd[-2]
file_out = cmd[-1]
if os.path.exists(file_in):
shutil.copy(file_in, file_out)
rc.AddCmdResult(partial_mock.ListRegex('futility sign --type bios .*'),
side_effect=_copy_firmware)
def MockECSigner(rc, ec_ro=True):
"""Add EC Signing Mocks to |rc|.
Args:
rc: RunCommandMock that cmds are added to
ec_ro: Treat EC as RO in fmap
"""
rc.AddCmdResult(partial_mock.ListRegex('futility sign --type rwsig .*'))
rc.AddCmdResult(partial_mock.ListRegex('openssl dgst -sha256 -binary .*'))
rc.AddCmdResult(partial_mock.ListRegex('store_file_in_cbfs .*'))
MockDumpFmap(rc, ec_ro=ec_ro)
def MockGBBSigner(rc):
"""Add GBB Signer Mock commands to |rc|"""
rc.AddCmdResult(partial_mock.ListRegex('futility gbb'))
def MockFirmwareSigner(rc):
"""Add mocks to |rc| for Firmware signing."""
keys_unittest.MockVbutilKey(rc)
MockBiosSigner(rc)
MockECSigner(rc, ec_ro=False)
MockGBBSigner(rc)
class TestBiosSigner(cros_test_lib.RunCommandTempDirTestCase):
"""Test BiosSigner."""
def setUp(self):
MockBiosSigner(self.rc)
def testGetCmdArgs(self):
bs = firmware.BiosSigner()
ks = signer_unittest.KeysetFromSigner(bs, self.tempdir)
bios_bin = os.path.join(self.tempdir, 'bios.bin')
bios_out = os.path.join(self.tempdir, 'bios.out')
fw_key = ks.keys['firmware_data_key']
kernel_key = ks.keys['kernel_subkey']
self.assertListEqual(bs.GetFutilityArgs(ks, bios_bin, bios_out),
['sign',
'--type', 'bios',
'--signprivate', fw_key.private,
'--keyblock', fw_key.keyblock,
'--kernelkey', kernel_key.public,
'--version', str(fw_key.version),
'--devsign', fw_key.private,
'--devkeyblock', fw_key.keyblock,
bios_bin, bios_out])
def testGetCmdArgsWithDevKeys(self):
bs = firmware.BiosSigner()
ks = signer_unittest.KeysetFromSigner(bs, self.tempdir)
bios_bin = os.path.join(self.tempdir, 'bios.bin')
bios_out = os.path.join(self.tempdir, 'bios.out')
# Add 'dev_firmware' keys and keyblock
dev_fw_key = keys.KeyPair('dev_firmware_data_key', keydir=self.tempdir)
ks.AddKey(dev_fw_key)
keys_unittest.CreateDummyPrivateKey(dev_fw_key)
keys_unittest.CreateDummyKeyblock(dev_fw_key)
fw_key = ks.keys['firmware_data_key']
kernel_key = ks.keys['kernel_subkey']
self.assertListEqual(bs.GetFutilityArgs(ks, bios_bin, bios_out),
['sign',
'--type', 'bios',
'--signprivate', fw_key.private,
'--keyblock', fw_key.keyblock,
'--kernelkey', kernel_key.public,
'--version', str(fw_key.version),
'--devsign', dev_fw_key.private,
'--devkeyblock', dev_fw_key.keyblock,
bios_bin, bios_out])
def testGetCmdArgsWithPreamble(self):
bs = firmware.BiosSigner(preamble_flags=1)
ks = signer_unittest.KeysetFromSigner(bs, self.tempdir)
bios_bin = os.path.join(self.tempdir, 'bios.bin')
bios_out = os.path.join(self.tempdir, 'bios.out')
# Add 'dev_firmware' keys and keyblock
dev_fw_key = keys.KeyPair('dev_firmware_data_key', keydir=self.tempdir)
ks.AddKey(dev_fw_key)
keys_unittest.CreateDummyPrivateKey(dev_fw_key)
keys_unittest.CreateDummyKeyblock(dev_fw_key)
args = bs.GetFutilityArgs(ks, bios_bin, bios_out)
self.assertIn('--flags', args)
self.assertEqual(args[args.index('--flags') + 1], '1')
def testGetCmdArgsWithSig(self):
loem_dir = os.path.join(self.tempdir, 'loem1', 'keyset')
loem_id = 'loem1'
bs = firmware.BiosSigner(sig_id=loem_id, sig_dir=loem_dir)
ks = signer_unittest.KeysetFromSigner(bs, keydir=self.tempdir)
bios_bin = os.path.join(self.tempdir, loem_id, 'bios.bin')
fw_key = ks.keys['firmware_data_key']
kernel_key = ks.keys['kernel_subkey']
self.assertListEqual(bs.GetFutilityArgs(ks, bios_bin, loem_dir),
['sign',
'--type', 'bios',
'--signprivate', fw_key.private,
'--keyblock', fw_key.keyblock,
'--kernelkey', kernel_key.public,
'--version', str(fw_key.version),
'--devsign', fw_key.private,
'--devkeyblock', fw_key.keyblock,
'--loemdir', loem_dir,
'--loemid', loem_id,
bios_bin, loem_dir])
class TestECSigner(cros_test_lib.RunCommandTempDirTestCase):
"""Test ECSigner."""
def testIsROSignedRW(self):
MockECSigner(self.rc, ec_ro=False)
ec_signer = firmware.ECSigner()
ec_bin = os.path.join(self.tempdir, 'ec.bin')
self.assertFalse(ec_signer.IsROSigned(ec_bin))
self.assertCommandContains(['futility', 'dump_fmap', '-p', ec_bin])
def testIsROSignedRO(self):
MockECSigner(self.rc)
ec_signer = firmware.ECSigner()
ec_bin = os.path.join(self.tempdir, 'ec.bin')
self.assertTrue(ec_signer.IsROSigned(ec_bin))
def testSign(self):
MockECSigner(self.rc, ec_ro=False)
ec_signer = firmware.ECSigner()
ks = signer_unittest.KeysetFromSigner(ec_signer, self.tempdir)
ec_bin = os.path.join(self.tempdir, 'ec.bin')
bios_bin = os.path.join(self.tempdir, 'bios.bin')
ec_signer.Sign(ks, ec_bin, bios_bin)
self.assertCommandContains(['futility', 'sign', '--type', 'rwsig',
'--prikey', ks.keys['key_ec_efs'].private,
ec_bin])
self.assertCommandContains(['openssl', 'dgst', '-sha256', '-binary'])
self.assertCommandContains(['store_file_in_cbfs', bios_bin, 'ecrw'])
self.assertCommandContains(['store_file_in_cbfs', bios_bin, 'ecrw.hash'])
class TestFirmwareSigner(cros_test_lib.RunCommandTempDirTestCase):
"""Test FirmwareSigner."""
def setUp(self):
MockFirmwareSigner(self.rc)
def testSignOneSimple(self):
fs = firmware.FirmwareSigner()
ks = signer_unittest.KeysetFromSigner(fs, self.tempdir)
shellball_dir = os.path.join(self.tempdir, 'shellball')
bios_path = os.path.join(shellball_dir, 'bios.bin')
fs.SignOne(ks, self.tempdir, bios_path)
self.assertCommandContains(['futility', 'sign', '--type', 'bios'])
self.assertCommandContains(['futility', 'gbb'])
def testSignOneWithEC(self):
fs = firmware.FirmwareSigner()
keyset_dir = os.path.join(self.tempdir, 'keyset')
ks = keys_unittest.KeysetMock(keyset_dir)
ks.CreateDummyKeys()
shellball_dir = os.path.join(self.tempdir, 'shellball')
bios_path = os.path.join(shellball_dir, 'bios.bin')
ec_path = os.path.join(shellball_dir, 'ec.bin')
fs.SignOne(ks, self.tempdir, bios_path, ec_image=ec_path)
self.assertCommandContains(['futility', 'sign', '--type', 'rwsig', ec_path])
def testSignOneWithLoem(self):
fs = firmware.FirmwareSigner()
keyset_dir = os.path.join(self.tempdir, 'keyset')
ks = keys_unittest.KeysetMock(keyset_dir)
ks.CreateDummyKeys()
ks_subset = ks.GetBuildKeyset('ACME')
shellball_dir = os.path.join(self.tempdir, 'shellball')
bios_path = os.path.join(shellball_dir, 'bios.bin')
fs.SignOne(ks, shellball_dir, bios_path, model_name='acme-board',
key_id='ACME')
self.assertExists(os.path.join(shellball_dir, 'rootkey.acme-board'))
self.assertCommandContains(['futility', 'sign', '--type', 'bios',
'--signprivate',
ks_subset.keys['firmware_data_key'].private])
def testSignWithSignerConfig(self):
fs = firmware.FirmwareSigner()
keyset_dir = os.path.join(self.tempdir, 'keyset')
ks = keys_unittest.KeysetMock(keyset_dir)
ks.CreateDummyKeys()
shellball_dir = os.path.join(self.tempdir, 'shellball')
osutils.SafeMakedirs(shellball_dir)
# Create signer_config.csv
signer_config_csv = os.path.join(shellball_dir, 'signer_config.csv')
with open(signer_config_csv, 'w') as csv_file:
csv_file.write(SignerConfigsFromCSVTest.CreateCSV().read())
fs.Sign(ks, shellball_dir, None)
for board_config in SignerConfigsFromCSVTest.CreateBoardDicts():
bios_path = os.path.join(shellball_dir, board_config['firmware_image'])
self.assertCommandContains(['futility', 'sign', bios_path])
def testSignWithNoSignerConfigUnified(self):
"""Test signing unified builds with no signer_config.csv provided."""
fs = firmware.FirmwareSigner()
keyset_dir = os.path.join(self.tempdir, 'keyset')
ks = keys_unittest.KeysetMock(keyset_dir)
ks.CreateDummyKeys()
shellball_dir = os.path.join(self.tempdir, 'shellball')
test_bios = ('bios.loem1.bin',
'bios.loem2.bin')
for bios in test_bios:
osutils.Touch(os.path.join(shellball_dir, bios), makedirs=True)
fs.Sign(ks, shellball_dir, None)
for bios in test_bios:
bios_path = os.path.join(shellball_dir, bios)
self.assertCommandContains(['futility', 'sign', bios_path])
self.assertExists(os.path.join(shellball_dir, 'keyset.loem1'))
self.assertExists(os.path.join(shellball_dir, 'keyset.loem2'))
def testSignWithNoSignerConfigNonUnified(self):
"""Test signing non-unified build with no signer_config.csv provided."""
fs = firmware.FirmwareSigner()
keyset_dir = os.path.join(self.tempdir, 'keyset')
ks = keys_unittest.KeysetMock(keyset_dir, has_loem_ini=False)
ks.CreateDummyKeys()
shellball_dir = os.path.join(self.tempdir, 'shellball')
test_bios = ('bios.bin',)
for bios in test_bios:
osutils.Touch(os.path.join(shellball_dir, bios), makedirs=True)
fs.Sign(ks, shellball_dir, None)
for bios in test_bios:
bios_path = os.path.join(shellball_dir, bios)
self.assertCommandContains(['futility', 'sign', bios_path])
self.assertExists(os.path.join(shellball_dir, 'keyset'))
class TestGBBSigner(cros_test_lib.RunCommandTempDirTestCase):
"""Test GBBSigner."""
def setUp(self):
MockGBBSigner(self.rc)
def testGetFutilityArgs(self):
gb_signer = firmware.GBBSigner()
ks = signer_unittest.KeysetFromSigner(gb_signer, self.tempdir)
bios_in = os.path.join(self.tempdir, 'bin.orig')
bios_out = os.path.join(self.tempdir, 'bios.bin')
self.assertListEqual(gb_signer.GetFutilityArgs(ks, bios_in, bios_out),
['gbb',
'--set',
'--recoverykey=' + ks.keys['recovery_key'].public,
bios_in,
bios_out])
class ShellballTest(cros_test_lib.RunCommandTempDirTestCase):
"""Verify that shellball is being called with correct arguments."""
@staticmethod
def CmdMock(rc):
"""Add mock commands to |rc|"""
rc.AddCmdResult(partial_mock.ListRegex('.* --sb_extract .*'))
rc.AddCmdResult(partial_mock.ListRegex('.* --sb_repack .*'))
def setUp(self):
"""Setup simple Shellball instance for mock testing."""
ShellballTest.CmdMock(self.rc)
self.sb1name = os.path.join(self.tempdir, 'fooball')
osutils.Touch(self.sb1name)
self.sb1 = firmware.Shellball(self.sb1name)
def testExtractCall(self):
"""Test arguments for image extract."""
out_dir = 'bar'
expected_args = ['--sb_extract', out_dir]
self.sb1.Extract(out_dir)
self.assertCommandContains(expected_args)
def testRepackCall(self):
"""Test arguments for image repack."""
from_dir = 'bar'
expected_args = ['--sb_repack', from_dir]
self.sb1.Repack(from_dir)
self.assertCommandContains(expected_args)
def testContextManager(self):
with self.sb1 as sb_dir:
self.assertExists(sb_dir)
self.assertCommandContains(['--sb_extract'])
self.assertCommandContains(['--sb_repack'])
class SignerConfigsFromCSVTest(cros_test_lib.TestCase):
"""Test SignerConfigsFromCSV function."""
FIELDS = ('model_name', 'firmware_image', 'key_id', 'ec_image')
BOARDS = (('acme', 'models/acme/bios.bin', 'ACME', 'models/acme/ec.bin'),
('shinra', 'models/shinra/bios.bin', 'SHINRA',
'models/shinra/ec.bin'),
('acme-loem3', 'models/acme/bios.bin', 'loem3',
'models/acme/ec.bin'))
@staticmethod
def CreateBoardDicts(fields=None, boards=None):
"""Returns list of board dicts with the given fields"""
fields = fields or SignerConfigsFromCSVTest.FIELDS
boards = boards or SignerConfigsFromCSVTest.BOARDS
board_dicts = []
for board in boards:
board_dicts.append(dict(zip(fields, board)))
return board_dicts
@staticmethod
def CreateCSV(fields=None, boards=None, board_dict=None):
fields = fields or SignerConfigsFromCSVTest.FIELDS
boards = boards or SignerConfigsFromCSVTest.BOARDS
board_dicts = (board_dict or
SignerConfigsFromCSVTest.CreateBoardDicts(fields=fields,
boards=boards))
# 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:
csv_file = io.BytesIO()
else:
csv_file = io.StringIO()
csv_writer = csv.DictWriter(csv_file, fields)
csv_writer.writeheader()
csv_writer.writerows(board_dicts)
csv_file.seek(0)
return csv_file
def testMissingRow(self):
fields = SignerConfigsFromCSVTest.BOARDS[:-1]
csv_file = SignerConfigsFromCSVTest.CreateCSV(fields=fields)
with self.assertRaises(csv.Error):
firmware.SignerConfigsFromCSV(csv_file)
def testSimple(self):
orig_boards = SignerConfigsFromCSVTest.CreateBoardDicts()
csv_file = SignerConfigsFromCSVTest.CreateCSV(board_dict=orig_boards)
signer_configs = firmware.SignerConfigsFromCSV(csv_file)
self.assertListEqual(orig_boards, signer_configs)
class TestWriteSignerNotes(cros_test_lib.RunCommandTempDirTestCase):
"""Test WriteSignerNotes function."""
def setUp(self):
keys_unittest.MockVbutilKey(self.rc)
def testSingleKey(self):
"""Test function's output with fixed sha1sum."""
recovery_key = keys.KeyPair('recovery_key', self.tempdir)
root_key = keys.KeyPair('root_key', self.tempdir)
keyset = keys.Keyset()
keyset.AddKey(recovery_key)
keyset.AddKey(root_key)
sha1sum = keys_unittest.MOCK_SHA1SUM
expected_output = ['Signed with keyset in ' + self.tempdir,
'recovery: ' + sha1sum,
'root: ' + sha1sum]
version_signer = io.StringIO()
firmware.WriteSignerNotes(keyset, version_signer)
self.assertListEqual(expected_output,
version_signer.getvalue().splitlines())
def testLoemKeys(self):
"""Test function's output with multiple loem keys."""
recovery_key = keys.KeyPair('recovery_key', self.tempdir)
root_keys = {
'loem%d' % idx: keys.KeyPair('root_key.loem%d' % idx, self.tempdir)
for idx in range(1, 4)}
lines = ['[loem]'] + ['%d = loem%d' % (int(loem[4:]), int(loem[4:]))
for loem in sorted(root_keys.keys())]
contents = '\n'.join(lines) + '\n'
osutils.WriteFile(os.path.join(self.tempdir, 'loem.ini'), contents)
keyset = keys.Keyset(self.tempdir)
keyset.AddKey(recovery_key)
for k in root_keys.values():
keyset.AddKey(k)
sha1sum = keys_unittest.MOCK_SHA1SUM
expected_header = ['Signed with keyset in ' + self.tempdir,
'recovery: ' + sha1sum,
"List sha1sum of all loem/model's signatures:"]
expected_loems = ['loem1: ' + sha1sum,
'loem2: ' + sha1sum,
'loem3: ' + sha1sum]
version_signer = io.StringIO()
firmware.WriteSignerNotes(keyset, version_signer)
output = version_signer.getvalue().splitlines()
output_header = output[:len(expected_header)]
output_loem = output[len(expected_header):]
output_loem.sort() # loem's can be out of order, so sort first.
self.assertListEqual(expected_header, output_header)
self.assertListEqual(expected_loems, output_loem)