signing: add errors and add/update signers
* Add GBBSigner
* updated BiosSigner use of keys, version and loemdir
* Rewrite ECSigner to do signing in tempdir
* Add Key and Signer errors
* Add ec key for KeysetFromDir()
* Add subdir arg to KeysetFromSigner
BUG=chromium:813829
TEST=signing/*_unittest
Change-Id: Ifee5e537024e47be14a9687df689ad07be9df612
Reviewed-on: https://chromium-review.googlesource.com/1211122
Commit-Ready: Chris Ching <chingcodes@chromium.org>
Tested-by: Chris Ching <chingcodes@chromium.org>
Reviewed-by: Lann Martin <lannm@chromium.org>
diff --git a/signing/lib/firmware.py b/signing/lib/firmware.py
index d5bb353..7626270 100644
--- a/signing/lib/firmware.py
+++ b/signing/lib/firmware.py
@@ -8,77 +8,143 @@
from __future__ import print_function
import os
+import re
import shutil
import tempfile
from chromite.lib import cros_build_lib
from chromite.lib import cros_logging as logging
from chromite.lib import osutils
-from chromite.signing.lib.signer import FutilitySigner
+from chromite.signing.lib import signer
-class BiosSigner(FutilitySigner):
+class BiosSigner(signer.FutilitySigner):
"""Sign bios.bin file using futility."""
_required_keys_private = ('firmware_data_key',)
- _required_keys_public = ('kernel',)
+ _required_keys_public = ('kernel_subkey',)
_required_keyblocks = ('firmware_data_key',)
- def __init__(self, bios_version=1, sig_dir=None, sig_id=None):
+ def __init__(self, sig_id='', sig_dir=''):
"""Init BiosSigner
Args:
- bios_version: Version of the bios being signed
- sig_dir: Signature directory (aka loem dir)
sig_id: Signature ID (aka loem id)
+ sig_dir: Signature Output Directory (i.e shellball/keyset)
"""
- self.version = bios_version
- self.sig_dir = sig_dir
self.sig_id = sig_id
+ self.sig_dir = sig_dir
def GetFutilityArgs(self, keyset, input_name, output_name):
+ """Returns futility arguments for signing bios
+
+ Args:
+ keyset: keyset used for signing
+ input_name: bios image
+ output_name: output firmware file
+ """
fw_key = keyset.keys['firmware_data_key']
-
-
- kernel_key = keyset.keys['kernel']
+ kernel_key = keyset.keys['kernel_subkey']
+ dev_fw_key = keyset.keys.get('dev_firmware_data_key', fw_key)
args = ['sign',
'--type', 'bios',
'--signprivate', fw_key.private,
'--keyblock', fw_key.keyblock,
'--kernelkey', kernel_key.public,
- '--version', str(self.version)]
-
- # Add developer key arguments
- dev_fw_key = keyset.keys.get('dev_firmware_data_key')
-
- if dev_fw_key is not None:
- args += ['--devsign', dev_fw_key.private,
- '--devkeyblock', dev_fw_key.keyblock]
- else:
- # Fallback to fw_key if device key not found (legacy, still needed?)
- args += ['--devsign', fw_key.private,
- '--devkeyblock', fw_key.keyblock]
+ '--version', fw_key.version,
+ '--devsign', dev_fw_key.private,
+ '--devkeyblock', dev_fw_key.keyblock]
# Add loem related arguments
- if self.sig_dir is not None and self.sig_id is not None:
+ if self.sig_id and self.sig_dir:
args += ['--loemdir', self.sig_dir,
'--loemid', self.sig_id]
# Add final input/output arguments
args += [input_name, output_name]
+
return args
-class ECSigner(FutilitySigner):
+class ECSigner(signer.BaseSigner):
"""Sign EC bin file."""
- _required_keys_private = ('ec',)
+ _required_keys_private = ('key_ec_efs',)
+
+ def IsROSigned(self, firmware_image):
+ """Returns True if the given firmware has RO signed ec."""
+
+ # Check fmap for KEY_RO
+ fmap = cros_build_lib.RunCommand(['futility', 'dump_fmap', firmware_image],
+ capture_output=True)
+
+ return re.search('KEY_RO', fmap.output) is not None
+
+ def Sign(self, keyset, input_name, output_name):
+ """"Sign EC image
+
+ Args:
+ keyset: keyset used for this signing step
+ input_name: ec image path to be signed (i.e. to ec.bin)
+ output_name: bios image path to be updated with new hashes
+ """
+ # Use absolute paths since we use a temp directory
+ ec_path = os.path.abspath(input_name)
+ bios_path = os.path.abspath(output_name)
+
+ if self.IsROSigned(bios_path):
+ # Only sign if not read-only, nothing to do
+ return True
+
+ logging.info('Signing EC %s', ec_path)
+
+ # Run futility in temp_dir to avoid cwd artifacts
+ with osutils.TempDir() as temp_dir:
+ ec_rw_bin = os.path.join(temp_dir, 'EC_RW.bin')
+ ec_rw_hash = os.path.join(temp_dir, 'EC_RW.hash')
+ try:
+ cros_build_lib.RunCommand(['futility', 'sign',
+ '--type', 'rwsig',
+ '--prikey',
+ keyset.keys['key_ec_efs'].private,
+ ec_path],
+ cwd=temp_dir)
+
+ cros_build_lib.RunCommand(['openssl', 'dgst', '-sha256', '-binary',
+ ec_rw_bin],
+ log_stdout_to_file=ec_rw_hash,
+ cwd=temp_dir)
+
+ cros_build_lib.RunCommand(['store_file_in_cbfs', bios_path,
+ ec_rw_bin, 'ecrw'])
+
+ cros_build_lib.RunCommand(['store_file_in_cbfs', bios_path,
+ ec_rw_hash, 'ecrw.hash'])
+
+ except cros_build_lib.RunCommandError as err:
+ logging.warning('Signing EC failed: %s', str(err))
+ return False
+
+ return True
+
+
+class GBBSigner(signer.FutilitySigner):
+ """Sign GBB"""
+ _required_keys_public = ('recovery_key',)
+ _required_keys_private = ('root_key',)
def GetFutilityArgs(self, keyset, input_name, output_name):
- return ['sign',
- '--type', 'rwsig',
- '--prikey', keyset.keys['ec'].private,
+ """Return args for signing GBB
+
+ Args:
+ keyset: Keyset used for signing
+ input_name: Firmware image
+ output_name: Bios path (i.e. tobios.bin)
+ """
+ return ['gbb',
+ '--set',
+ '--recoverykey=' + keyset.keys['recovery_key'].public,
input_name,
output_name]
diff --git a/signing/lib/firmware_unittest.py b/signing/lib/firmware_unittest.py
index 5c1e209..ea5736d 100644
--- a/signing/lib/firmware_unittest.py
+++ b/signing/lib/firmware_unittest.py
@@ -17,90 +17,136 @@
from chromite.signing.lib import signer_unittest
-class TestBiosSigner(cros_test_lib.RunCommandTestCase,
- cros_test_lib.TempDirTestCase):
+class TestBiosSigner(cros_test_lib.RunCommandTempDirTestCase):
"""Test BiosSigner."""
def testGetCmdArgs(self):
bs = firmware.BiosSigner()
ks = signer_unittest.KeysetFromSigner(bs, self.tempdir)
- fw_key = ks.keys['firmware_data_key']
+ bios_bin = os.path.join(self.tempdir, 'bios.bin')
+ bios_out = os.path.join(self.tempdir, 'bios.out')
- self.assertListEqual(bs.GetFutilityArgs(ks, 'foo', 'bar'),
+ 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', ks.keys['kernel'].public,
- '--version', str(bs.version),
+ '--kernelkey', kernel_key.public,
+ '--version', fw_key.version,
'--devsign', fw_key.private,
'--devkeyblock', fw_key.keyblock,
- 'foo', 'bar'])
+ 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, 'foo', 'bar'),
+ self.assertListEqual(bs.GetFutilityArgs(ks, bios_bin, bios_out),
['sign',
'--type', 'bios',
'--signprivate', fw_key.private,
'--keyblock', fw_key.keyblock,
- '--kernelkey', ks.keys['kernel'].public,
- '--version', str(bs.version),
+ '--kernelkey', kernel_key.public,
+ '--version', fw_key.version,
'--devsign', dev_fw_key.private,
'--devkeyblock', dev_fw_key.keyblock,
- 'foo', 'bar'])
+ bios_bin, bios_out])
def testGetCmdArgsWithSig(self):
- loem_dir = os.path.join(self.tempdir, 'loem')
+ loem_dir = os.path.join(self.tempdir, 'loem1', 'keyset')
loem_id = 'loem1'
- bs = firmware.BiosSigner(sig_dir=loem_dir, sig_id=loem_id)
- ks = signer_unittest.KeysetFromSigner(bs, self.tempdir)
+
+ 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, 'foo', 'bar'),
+ self.assertListEqual(bs.GetFutilityArgs(ks, bios_bin, loem_dir),
['sign',
'--type', 'bios',
'--signprivate', fw_key.private,
'--keyblock', fw_key.keyblock,
- '--kernelkey', ks.keys['kernel'].public,
- '--version', str(bs.version),
+ '--kernelkey', kernel_key.public,
+ '--version', fw_key.version,
'--devsign', fw_key.private,
'--devkeyblock', fw_key.keyblock,
'--loemdir', loem_dir,
'--loemid', loem_id,
- 'foo', 'bar'])
+ bios_bin, loem_dir])
-class TestECSigner(cros_test_lib.RunCommandTestCase,
- cros_test_lib.TempDirTestCase):
+class TestECSigner(cros_test_lib.RunCommandTempDirTestCase):
"""Test ECSigner."""
- def testGetCmdArgs(self):
- ecs = firmware.ECSigner()
- ks = signer_unittest.KeysetFromSigner(ecs, self.tempdir)
+ def testIsROSignedRW(self):
+ ec_signer = firmware.ECSigner()
+ bios_bin = os.path.join(self.tempdir, 'bin.bin')
- self.assertListEqual(ecs.GetFutilityArgs(ks, 'foo', 'bar'),
- ['sign',
- '--type', 'rwsig',
- '--prikey', ks.keys['ec'].private,
- 'foo', 'bar'])
+ self.assertFalse(ec_signer.IsROSigned(bios_bin))
+
+ self.assertCommandContains(['futility', 'dump_fmap', bios_bin])
+
+ def testIsROSignedRO(self):
+ ec_signer = firmware.ECSigner()
+ bios_bin = os.path.join(self.tempdir, 'bin.bin')
+
+ self.rc.SetDefaultCmdResult(output='KEY_RO')
+
+ self.assertTrue(ec_signer.IsROSigned(bios_bin))
+
+ def testSign(self):
+ 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')
+
+ self.assertTrue(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 ShellballTest(cros_test_lib.RunCommandTestCase,
- cros_test_lib.TempDirTestCase):
+class TestGBBSigner(cros_test_lib.TempDirTestCase):
+ """Test GBBSigner."""
+
+ 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."""
def setUp(self):
diff --git a/signing/lib/keys.py b/signing/lib/keys.py
index 2a0c848..e4c304c 100644
--- a/signing/lib/keys.py
+++ b/signing/lib/keys.py
@@ -14,6 +14,18 @@
from chromite.lib import cros_logging as logging
+class SignerKeyError(Exception):
+ """Raise when there in an error related to keys"""
+
+
+class SignerKeyMissingError(SignerKeyError):
+ """Raise if key is missing from subset"""
+
+
+class SignerSubkeyMissingError(SignerKeyMissingError):
+ """Raise if a subkey is missing"""
+
+
class KeyPair(object):
"""Container for a key's files.
@@ -157,19 +169,28 @@
* Keys does not have a subkey of the given name
* Subkey exists for the given name, or alias. Key will be indexed under
it's parent key's name. ex: 'firmware.loem1' -> 'firmware'
+
+ Raises SubkeyMissingError if subkey not found
"""
ks = Keyset()
+ found = False
+
# Use alias if exists
subkey_alias = self.subkey_aliases.get(subkey_name, '')
for key_name, key in self.keys.items():
if subkey_alias in key.subkeys:
+ found = True
ks.AddKey(key.subkeys[subkey_alias], key_name=key_name)
elif subkey_name in key.subkeys:
+ found = True
ks.AddKey(key.subkeys[subkey_name], key_name=key_name)
else:
- ks.AddKey(key)
+ ks.AddKey(key, key_name=key_name)
+
+ if not found:
+ raise SignerSubkeyMissingError("Unable to find %s", subkey_name)
return ks
@@ -216,4 +237,8 @@
logging.debug('Found subkey %s.%s', key_name, subkey)
ks.AddSubkey(key_name, subkey)
+ elif f_name == 'key_ec_efs.vbprik2':
+ ks.AddKey(KeyPair('key_ec_efs', key_dir,
+ pub_ext='.vbpubk2', priv_ext='.vbprivk2'))
+
return ks
diff --git a/signing/lib/keys_unittest.py b/signing/lib/keys_unittest.py
index 251d7f9..59d898f 100644
--- a/signing/lib/keys_unittest.py
+++ b/signing/lib/keys_unittest.py
@@ -21,6 +21,7 @@
'firmware_data_key',
'installer_kernel_data_key',
'kernel_data_key',
+ 'kernel_subkey'
'recovery_kernel_data_key',
'root_key')
@@ -193,7 +194,7 @@
def testEqSame(self):
kc1 = self._get_keyset()
kc2 = self._get_keyset()
- self.assertTrue(kc1 == kc2)
+ self.assertEqual(kc1, kc2)
def testEqDiffrent(self):
kc1 = self._get_keyset()
@@ -231,10 +232,9 @@
def testGetSubKeysetMissmatch(self):
ks0 = self._get_keyset()
- ks1 = ks0.GetSubKeyset('foo')
- self.assertIsInstance(ks1, keys.Keyset)
- self.assertDictEqual(ks0.keys, ks1.keys)
+ with self.assertRaises(keys.SignerSubkeyMissingError):
+ ks0.GetSubKeyset('foo')
def testGetSubKeyset(self):
ks0 = self._get_keyset()
@@ -341,7 +341,7 @@
for subkey in key.subkeys.values():
CreateDummyPublic(subkey)
else:
- osutils.Touch(key.public)
+ osutils.Touch(key.public, makedirs=True)
def CreateDummyPrivateKey(key):
@@ -350,7 +350,7 @@
for subkey in key.subkeys.values():
CreateDummyPrivateKey(subkey)
else:
- osutils.Touch(key.private)
+ osutils.Touch(key.private, makedirs=True)
def CreateDummyKeyblock(key):
@@ -359,7 +359,7 @@
for subkey in key.subkeys.values():
CreateDummyKeyblock(subkey)
else:
- osutils.Touch(key.keyblock)
+ osutils.Touch(key.keyblock, makedirs=True)
def CreateDummyKeys(key):
diff --git a/signing/lib/signer.py b/signing/lib/signer.py
index 1bda934..3fd9d1b 100644
--- a/signing/lib/signer.py
+++ b/signing/lib/signer.py
@@ -34,6 +34,9 @@
from chromite.lib import cros_build_lib
+class SigningFailedError(Exception):
+ """Raise when a signing failed"""
+
class SignerOutputTemplateError(Exception):
"""Raise when there is an issue with filling a signer output template"""
diff --git a/signing/lib/signer_unittest.py b/signing/lib/signer_unittest.py
index 08957cd..3363534 100644
--- a/signing/lib/signer_unittest.py
+++ b/signing/lib/signer_unittest.py
@@ -9,6 +9,7 @@
import ConfigParser
import io
+import os
from chromite.lib import cros_test_lib
from chromite.signing.lib import keys
@@ -194,10 +195,12 @@
self.assertTrue(s0.CheckKeyset(ks0))
-def KeysetFromSigner(s, keydir):
+def KeysetFromSigner(s, keydir, subdir='keyset'):
"""Returns a valid keyset containing required keys and keyblocks."""
ks = keys.Keyset()
+ keydir = os.path.join(keydir, subdir)
+
# pylint: disable=protected-access
for key_name in s._required_keys:
key = keys.KeyPair(key_name, keydir=keydir)
@@ -236,8 +239,7 @@
return [input_name, output_name]
-class TestFutilitySigner(cros_test_lib.RunCommandTestCase,
- cros_test_lib.TempDirTestCase):
+class TestFutilitySigner(cros_test_lib.RunCommandTempDirTestCase):
"""Test Futility Signer."""
def testSign(self):