blob: ba5b9d34ee47b52bbd7df502041bbf670af0a044 [file] [log] [blame]
# Copyright 2017 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.
import hashlib
import logging
import re
import tempfile
from cros.factory.gooftool import crosfw
from cros.factory.probe.lib import cached_probe_function
from cros.factory.utils.arg_utils import Arg
from cros.factory.utils import process_utils
from cros.factory.utils import type_utils
FIELDS = type_utils.Enum(
['firmware_keys', 'ro_main_firmware', 'ro_ec_firmware', 'ro_pd_firmware'])
def _FwKeyHash(fw_file_path, key_name):
"""Hash specified GBB key, extracted by vbutil_key."""
known_hashes = {
'b11d74edd286c144e1135b49e7f0bc20cf041f10': 'devkeys/rootkey',
'c14bd720b70d97394257e3e826bd8f43de48d4ed': 'devkeys/recovery',
}
with tempfile.NamedTemporaryFile(prefix='gbb_%s_' % key_name) as f:
process_utils.CheckOutput(
'futility gbb -g --%s=%s %s' % (key_name, f.name, fw_file_path),
shell=True, log=True)
key_info = process_utils.CheckOutput(
'futility vbutil_key --unpack %s' % f.name, shell=True)
sha1sum = re.findall(r'Key sha1sum:[\s]+([\w]+)', key_info)
if len(sha1sum) != 1:
logging.error('Failed calling vbutil_key for firmware key hash.')
return None
sha1 = sha1sum[0]
if sha1 in known_hashes:
sha1 += '#' + known_hashes[sha1]
return 'kv3#' + sha1
def _AddFirmwareIdTag(image, id_name='RO_FRID'):
"""Returns firmware ID in '#NAME' format if available."""
if not image.has_section(id_name):
return ''
id_stripped = image.get_section(id_name).decode('utf-8').strip(chr(0))
if id_stripped:
return '#%s' % id_stripped
return ''
def _MainRoHash(image):
"""Algorithm: sha256(fmap, RO_SECTION[-GBB])."""
hash_src = image.get_fmap_blob()
gbb = image.get_section('GBB')
zero_gbb = b'\x00' * len(gbb)
image.put_section('GBB', zero_gbb)
hash_src += image.get_section('RO_SECTION')
image.put_section('GBB', gbb)
# pylint: disable=no-member
return {
'hash': hashlib.sha256(hash_src).hexdigest(),
'version': _AddFirmwareIdTag(image).lstrip('#')}
def _EcRoHash(image):
"""Algorithm: sha256(fmap, EC_RO)."""
hash_src = image.get_fmap_blob()
hash_src += image.get_section('EC_RO')
# pylint: disable=no-member
return {
'hash': hashlib.sha256(hash_src).hexdigest(),
'version': _AddFirmwareIdTag(image).lstrip('#')}
def CalculateFirmwareHashes(fw_file_path):
"""Calculate the volatile hashes corresponding to a firmware blob.
Given a firmware blob, determine what kind of firmware it is based
on what sections are present. Then generate a dict containing the
corresponding hash values.
"""
raw_image = open(fw_file_path, 'rb').read()
try:
image = crosfw.FirmwareImage(raw_image)
except Exception:
return None
if image.has_section('EC_RO'):
return _EcRoHash(image)
if image.has_section('GBB') and image.has_section('RO_SECTION'):
return _MainRoHash(image)
return None
class ChromeosFirmwareFunction(cached_probe_function.LazyCachedProbeFunction):
# pylint: disable=line-too-long
"""Get firmware information from a flash chip.
Description
-----------
This function mainly runs ``flashrom`` to get the firmware image from the
specific flash chip and calculates the hash of some of the sections of the
image.
- If ``field="firmware_keys"``, this function outputs sha1sum hash of the
root key and the recovery key recorded in the readonly main firmware. If
the keyset is develop keyset, the output will contain a special suffix mark
``#devkeys/rootkey`` and ``#devkeys/recoverykey``. See `Examples`_ for
detail.
- If ``field="ro_main_firmware"``, this function outputs the sha256 hash of
the readonly main firmware and also the version of the firmware.
- If ``field="ro_ec_firmware"``, this function outputs the sha256 hash of
the readonly EC firmware and also the version of the firmware.
- If ``field="ro_pd_firmware"``, this function outputs the sha256 hash of
the readonly PD firmware and also the version of the firmware. Since
not all devices have a PD flash chip, it's possible that the output of this
function is empty.
Examples
--------
As this function has only one single argument, the ``eval`` part of the
probe statement can be simplified into a single string instead of a
dictionary. For example, the probe statement for probing the key hash is ::
{
"eval": "chromeos_firmware:firmware_keys":
}
, where ``chromeos_firmware:firmware_keys`` is equivalent to ::
{
"chromeos_firmware": {
"field": "firmware_keys"
}
}
We category below examples into two use cases.
Probe the Key Hash (``field="firmware_keys"``)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If the firmware is not signed and contains a develop keyset, the probed
results must be ::
[
{
"rootkey": "kv3#b11d74edd6c14...0bc20cf041f10#devkey/rootkey",
"recoverykey": "kv3#c14bd0b70d9...d8f43de48d4ed#devkey/recoverykey"
}
]
Probe the RO Firmware Image Hash (``field="ro_[main|ec|pd]_firmware"``)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``values`` of the output must contains two fields: ``hash`` and
``version``. For example::
[
{
"hash": "13497173ba1fb678ab3f...ebd27d00d",
"version": "supercoolproj_v1.1.2738-3927abcd2"
}
]
"""
ARGS = [
Arg('field', type_utils.Enum(FIELDS),
'The flash chip where this function probes the firmware from.')
]
def GetCategoryFromArgs(self):
if self.args.field not in FIELDS:
raise cached_probe_function.InvalidCategoryError(
'`field` should be one of %r' % FIELDS)
return self.args.field
@classmethod
def ProbeDevices(cls, category):
if category == FIELDS.firmware_keys:
fw_file_path = crosfw.LoadMainFirmware().GetFileName(
sections=['RO_SECTION'])
return {
'key_recovery': _FwKeyHash(fw_file_path, 'recoverykey'),
'key_root': _FwKeyHash(fw_file_path, 'rootkey')}
if category == FIELDS.ro_main_firmware:
fw_file_path = crosfw.LoadMainFirmware().GetFileName(
sections=['RO_SECTION'])
if category == FIELDS.ro_ec_firmware:
fw_file_path = crosfw.LoadEcFirmware().GetFileName()
if category == FIELDS.ro_pd_firmware:
fw_file_path = crosfw.LoadPDFirmware().GetFileName()
return CalculateFirmwareHashes(fw_file_path)