earlgrey: update signing infrastructure Update signing tooling to incorporate ownershp transfer blob manifest extensions. Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/target/earlgrey/tooling/opentitan_runner.bzl b/target/earlgrey/tooling/opentitan_runner.bzl index b29fe02..f514696 100644 --- a/target/earlgrey/tooling/opentitan_runner.bzl +++ b/target/earlgrey/tooling/opentitan_runner.bzl
@@ -233,7 +233,7 @@ mandatory = True, ), "manifest": attr.label( - allow_single_file = True, + allow_files = True, doc = "A json manifest to apply to the image being signed", ), "spx_key": attr.label_keyed_string_dict(
diff --git a/target/earlgrey/tooling/signing/defs.bzl b/target/earlgrey/tooling/signing/defs.bzl index 0312a65..00ab0c7 100644 --- a/target/earlgrey/tooling/signing/defs.bzl +++ b/target/earlgrey/tooling/signing/defs.bzl
@@ -2,6 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 load("//target/earlgrey/tooling/signing:keyset.bzl", _keyset = "keyset") +load("//target/earlgrey/tooling/signing:manifest.bzl", _manifest = "manifest") +load("//target/earlgrey/tooling/signing:ownership.bzl", _owner_block_binary = "owner_block_binary", _ownership_detached_signature = "ownership_detached_signature") load( "//target/earlgrey/tooling/signing:signing.bzl", _sign_bin = "sign_bin", @@ -23,6 +25,9 @@ # - signing_tool: describe a signing tool and configuration. # - sign_bin: sign a binary with a given key. keyset = _keyset +manifest = _manifest +owner_block_binary = _owner_block_binary +ownership_detached_signature = _ownership_detached_signature signing_tool = _signing_tool sign_bin = _sign_bin
diff --git a/target/earlgrey/tooling/signing/manifest.bzl b/target/earlgrey/tooling/signing/manifest.bzl new file mode 100644 index 0000000..98f6f24 --- /dev/null +++ b/target/earlgrey/tooling/signing/manifest.bzl
@@ -0,0 +1,232 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +_SEL_DEVICE_ID = 1 +_SEL_MANUF_STATE_CREATOR = (1 << 8) +_SEL_MANUF_STATE_OWNER = (1 << 9) +_SEL_LIFE_CYCLE_STATE = (1 << 10) + +DEFAULT_USAGE_CONSTRAINTS = 0xa5a5a5a5 + +_HEX_MAP = "0123456789abcdef" + +def hex_digits(v, width = 32): + """Convert an int into a hex string without 0x prefix""" + if v >= (1 << width): + fail("Int {} too large to convert to string of width {}".format(v, width)) + hex_digits = [_HEX_MAP[(v >> i) & 0xf] for i in range(0, width, 4)] + return "".join(reversed(hex_digits)) + +def hex(v, width = 32): + """Convert an int into a hex string with 0x prefix""" + return "0x{}".format(hex_digits(v, width = width)) + +def _manifest_impl(ctx): + mf = {} + mf_version = {} + + # All the easy parameters are simple assignments + if ctx.attr.signature: + mf["signature"] = ctx.attr.signature + if ctx.attr.modulus: + mf["modulus"] = ctx.attr.modulus + if ctx.attr.identifier: + mf["identifier"] = ctx.attr.identifier + if ctx.attr.length: + mf["length"] = ctx.attr.length + if ctx.attr.version_major: + mf["version_major"] = ctx.attr.version_major + if ctx.attr.version_minor: + mf["version_minor"] = ctx.attr.version_minor + if ctx.attr.security_version: + mf["security_version"] = ctx.attr.security_version + if ctx.attr.timestamp: + mf["timestamp"] = ctx.attr.timestamp + if ctx.attr.max_key_version: + mf["max_key_version"] = ctx.attr.max_key_version + if ctx.attr.code_start: + mf["code_start"] = ctx.attr.code_start + if ctx.attr.code_end: + mf["code_end"] = ctx.attr.code_end + if ctx.attr.entry_point: + mf["entry_point"] = ctx.attr.entry_point + + if ctx.attr.manifest_version_major: + mf_version["major"] = ctx.attr.manifest_version_major + if ctx.attr.manifest_version_minor: + mf_version["minor"] = ctx.attr.manifest_version_minor + if mf_version: + mf["manifest_version"] = mf_version + + if ctx.attr.address_translation: + mf["address_translation"] = ctx.attr.address_translation + + if ctx.attr.manifest_base_address: + mf["manifest_base_address"] = ctx.attr.manifest_base_address + + if ctx.attr.binding_value: + if len(ctx.attr.binding_value) != 8: + fail("The binding_value must be exactly 8 words.") + mf["binding_value"] = [v for v in ctx.attr.binding_value] + + uc = {} + selector_bits = 0 + device_id = list(ctx.attr.device_id) + if len(device_id) > 8: + fail("The device_id must be 8 words or fewer.") + + if len(device_id) < 8: + device_id.extend([hex(DEFAULT_USAGE_CONSTRAINTS)] * (8 - len(device_id))) + for i, d in enumerate(device_id): + if d != hex(DEFAULT_USAGE_CONSTRAINTS): + selector_bits |= _SEL_DEVICE_ID << i + device_id[i] = d + uc["device_id"] = device_id + + if ctx.attr.manuf_state_creator: + uc["manuf_state_creator"] = ctx.attr.manuf_state_creator + selector_bits |= _SEL_MANUF_STATE_CREATOR + else: + uc["manuf_state_creator"] = hex(DEFAULT_USAGE_CONSTRAINTS) + + if ctx.attr.manuf_state_owner: + uc["manuf_state_owner"] = ctx.attr.manuf_state_owner + selector_bits |= _SEL_MANUF_STATE_OWNER + else: + uc["manuf_state_owner"] = hex(DEFAULT_USAGE_CONSTRAINTS) + + if ctx.attr.life_cycle_state: + uc["life_cycle_state"] = ctx.attr.life_cycle_state + selector_bits |= _SEL_LIFE_CYCLE_STATE + else: + uc["life_cycle_state"] = hex(DEFAULT_USAGE_CONSTRAINTS) + + if ctx.attr.selector_bits: + if int(ctx.attr.selector_bits, base = 16) != selector_bits and ctx.attr.selector_mismatch_is_failure: + fail("User provided selector_bits ({}) don't match computed selector_bits ({})".format(ctx.attr.selector_bits, selector_bits)) + uc["selector_bits"] = ctx.attr.selector_bits + else: + uc["selector_bits"] = selector_bits + + mf["usage_constraints"] = uc + + extensions = [e or None for e in ctx.attr.extensions] if ctx.attr.extensions else [ + "spx_key", + "spx_signature", + "secver_write", + "isfb", + "isfb_erase", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ] + + manifest_dependent_files = [] + if ctx.file.owner_transfer_block: + manifest_dependent_files.append(ctx.file.owner_transfer_block) + if ctx.file.owner_transfer_detached_signature: + manifest_dependent_files.append(ctx.file.owner_transfer_detached_signature) + + if "owner_transfer_blob" not in extensions: + if None not in extensions: + fail("No free slot in extensions table for owner_transfer_blob.") + extensions[extensions.index(None)] = "owner_transfer_blob" + + mf["extensions"] = extensions + mf["extension_params"] = [] + + if ctx.file.owner_transfer_block: + params = { + "owner_block": ctx.file.owner_transfer_block.path, + } + if ctx.file.owner_transfer_detached_signature: + params["detached_signature"] = ctx.file.owner_transfer_detached_signature.path + mf["extension_params"].append({ + "owner_transfer_blob": params, + }) + + if ctx.attr.integrator_specific_firmware_binding: + mf["extension_params"].append( + { + "integrator_specific_firmware_binding": json.decode(ctx.attr.integrator_specific_firmware_binding), + }, + ) + + secver_write = ctx.attr.secver_write + if secver_write == "none": + pass + elif secver_write in ("false", "true"): + mf["extension_params"].append( + { + "secver_write": { + "secver_write": json.decode(secver_write), + }, + }, + ) + else: + fail("Unknown value for secver_write:", secver_write) + + if ctx.attr.isfb_erase_allowed_policy: + mf["extension_params"].append( + { + "isfb_erase_policy": json.decode(ctx.attr.isfb_erase_allowed_policy), + }, + ) + + file = ctx.actions.declare_file("{}.json".format(ctx.attr.name)) + ctx.actions.write(file, json.encode_indent(mf)) + return [ + DefaultInfo( + files = depset([file]), + data_runfiles = ctx.runfiles(files = [file] + manifest_dependent_files), + ), + ] + +_manifest = rule( + implementation = _manifest_impl, + attrs = { + "address_translation": attr.string(doc = "Whether this image uses address translation as a 0x-prefixed hex-encoded string"), + "binding_value": attr.string_list(doc = "Binding value used by key manager to derive secrets as a 0x-prefixed hex-encoded string"), + "code_end": attr.string(doc = "End offset of the executable region in the image as a 0x-prefixed hex-encoded string"), + "code_start": attr.string(doc = "Start offset of the executable region in the image as a 0x-prefixed hex-encoded string"), + "device_id": attr.string_list(doc = "Usage constraint device ID as a 0x-prefixed hex-encoded string"), + "entry_point": attr.string(doc = "Offset of the first instruction in the image as a 0x-prefixed hex-encoded string"), + "extensions": attr.string_list(doc = "Names of the manifest extensions as an array of strings"), + "identifier": attr.string(doc = "Manifest identifier as a 0x-prefixed hex-encoded string"), + "integrator_specific_firmware_binding": attr.string(doc = "Create an Integrator Specific Firmware Block (ISFB) JSON object"), + "isfb_erase_allowed_policy": attr.string(doc = "Create an ISFB Erase Allowed Policy JSON object"), + "length": attr.string(doc = "Length of this image as a 0x-prefixed hex-encoded string"), + "life_cycle_state": attr.string(doc = "Usage constraint for life cycle status as a 0x-prefixed hex-encoded string"), + "manifest_base_address": attr.string(doc = "Manifest base address as a 0x-prefixed hex-encoded string"), + "manifest_version_major": attr.string(doc = "Manifest major version as a 0x-prefixed hex-encoded string"), + "manifest_version_minor": attr.string(doc = "Manifest minor version as a 0x-prefixed hex-encoded string"), + "manuf_state_creator": attr.string(doc = "Usage constraint for silicon creator manufacturing status as a 0x-prefixed hex-encoded string"), + "manuf_state_owner": attr.string(doc = "Usage constraint for silicon owner manufacturing status as a 0x-prefixed hex-encoded string"), + "max_key_version": attr.string(doc = "Maximum allowed version for keys generated at the next boot stage as a 0x-prefixed hex-encoded string"), + "modulus": attr.string(doc = "Signing key modulus as a 0x-prefixed hex-encoded string"), + "owner_transfer_block": attr.label(allow_single_file = True, doc = "The owner block file to include for transfer"), + "owner_transfer_detached_signature": attr.label(allow_single_file = True, doc = "The detached signature of the owner block"), + "security_version": attr.string(doc = "Security version for anti-rollback protection as a 0x-prefixed hex-encoded string"), + "secver_write": attr.string(default = "none", values = ["none", "false", "true"], doc = "Add the secver_write extension with the specified value"), + "selector_bits": attr.string(doc = "Usage constraint selector bits as a 0x-prefixed hex-encoded string"), + "selector_mismatch_is_failure": attr.bool(default = True, doc = "A mismatch in computed selector bits is a failure"), + "signature": attr.string(doc = "Image signature as a 0x-prefixed hex-encoded string"), + "timestamp": attr.string(doc = "Unix timestamp of the image as a 0x-prefixed hex-encoded string"), + "version_major": attr.string(doc = "Image major version as a 0x-prefixed hex-encoded string"), + "version_minor": attr.string(doc = "Image minor version as a 0x-prefixed hex-encoded string"), + }, +) + +def manifest(name, **kwargs): + _manifest( + name = name, + **kwargs + ) + return name
diff --git a/target/earlgrey/tooling/signing/ownership.bzl b/target/earlgrey/tooling/signing/ownership.bzl new file mode 100644 index 0000000..36ffe5a --- /dev/null +++ b/target/earlgrey/tooling/signing/ownership.bzl
@@ -0,0 +1,133 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +def _owner_block_binary_impl(ctx): + output = ctx.attr.output + if not output: + output = ctx.attr.name + ".bin" + out_bin = ctx.actions.declare_file(output) + inputs = [ctx.file.src] + ctx.files.keys + + command = "set -euo pipefail" + + if ctx.attr.relative_to: + relative_to_files = ctx.attr.relative_to.files.to_list() + if not relative_to_files: + fail("relative_to target has no files") + ref_file = relative_to_files[0] + workspace_root = ref_file.owner.workspace_root + + if workspace_root: + command += """ + WORKSPACE_ROOT="{workspace_root}" + if [ -n "$WORKSPACE_ROOT" ]; then + for f in $WORKSPACE_ROOT/*; do + name=$(basename "$f") + if [ ! -e "$name" ]; then + ln -s "$f" "$name" + fi + done + fi + """.format(workspace_root = workspace_root) + inputs.append(ref_file) + + command += """ + {opentitantool} --rcfile= \ + ownership config --input {input} {output} + """.format( + opentitantool = ctx.executable.opentitantool.path, + input = ctx.file.src.path, + output = out_bin.path, + ) + + ctx.actions.run_shell( + inputs = inputs, + outputs = [out_bin], + command = command, + tools = [ctx.executable.opentitantool], + use_default_shell_env = True, + mnemonic = "OwnerBlockGen", + progress_message = "Generating owner block binary %s" % ctx.attr.name, + ) + + return [DefaultInfo(files = depset([out_bin]))] + +owner_block_binary = rule( + implementation = _owner_block_binary_impl, + attrs = { + "keys": attr.label_list(allow_files = True), + "opentitantool": attr.label( + default = Label("@opentitan_devbundle//:opentitantool/opentitantool"), + allow_single_file = True, + executable = True, + cfg = "exec", + ), + "output": attr.string(doc = "Optional output filename"), + "relative_to": attr.label(allow_single_file = True), + "src": attr.label(allow_single_file = True, mandatory = True), + }, +) + +def _ownership_detached_signature_impl(ctx): + out_sig = ctx.actions.declare_file(ctx.attr.output or (ctx.attr.name + ".bin")) + + inputs = [] + args = [ + "--rcfile=", + "--quiet", + "ownership", + "detached-signature", + "--command={}".format(ctx.attr.command), + "--key-alg={}".format(ctx.attr.key_alg), + "--nonce={}".format(ctx.attr.nonce), + ] + + if ctx.file.src: + inputs.append(ctx.file.src) + args.append("--input={}".format(ctx.file.src.path)) + if ctx.file.ecdsa_key: + inputs.append(ctx.file.ecdsa_key) + args.append("--ecdsa-key={}".format(ctx.file.ecdsa_key.path)) + if ctx.file.spx_key: + inputs.append(ctx.file.spx_key) + args.append("--spx-key={}".format(ctx.file.spx_key.path)) + if ctx.file.ecdsa_sig: + inputs.append(ctx.file.ecdsa_sig) + args.append("--ecdsa-sig={}".format(ctx.file.ecdsa_sig.path)) + if ctx.file.spx_sig: + inputs.append(ctx.file.spx_sig) + args.append("--spx-sig={}".format(ctx.file.spx_sig.path)) + + args.append(out_sig.path) + + ctx.actions.run( + outputs = [out_sig], + inputs = inputs, + arguments = args, + executable = ctx.executable.opentitantool, + mnemonic = "OwnershipDetachedSignatureGen", + progress_message = "Generating ownership detached signature %s" % ctx.attr.name, + ) + + return [DefaultInfo(files = depset([out_sig]))] + +ownership_detached_signature = rule( + implementation = _ownership_detached_signature_impl, + attrs = { + "command": attr.string(mandatory = True, doc = "Command: Owner, Unlock, Activate"), + "ecdsa_key": attr.label(allow_single_file = True, doc = "ECDSA private key file in DER format"), + "ecdsa_sig": attr.label(allow_single_file = True, doc = "Raw ECDSA signature file"), + "key_alg": attr.string(mandatory = True, doc = "Key algorithm: EcdsaP256, SpxPure, SpxPrehash, HybridSpxPure, HybridSpxPrehash"), + "nonce": attr.string(default = "0", doc = "Nonce value as string (due to int limits)"), + "opentitantool": attr.label( + default = Label("@opentitan_devbundle//:opentitantool/opentitantool"), + allow_single_file = True, + executable = True, + cfg = "exec", + ), + "output": attr.string(doc = "Optional output signature filename"), + "spx_key": attr.label(allow_single_file = True, doc = "SPHINCS+ private key file in PEM format"), + "spx_sig": attr.label(allow_single_file = True, doc = "Raw SPX signature file"), + "src": attr.label(allow_single_file = True, doc = "Raw data block to sign (e.g. owner_block_binary)"), + }, +)
diff --git a/target/earlgrey/tooling/signing/pre_post.bzl b/target/earlgrey/tooling/signing/pre_post.bzl index 81e5813..a096e56 100644 --- a/target/earlgrey/tooling/signing/pre_post.bzl +++ b/target/earlgrey/tooling/signing/pre_post.bzl
@@ -36,15 +36,26 @@ else: basename = paths.replace_extension(basename, "") + manifest_dependent_files = [] + manifest_file = None + if manifest: + if type(manifest) == "Target": + if DefaultInfo in manifest: + manifest_dependent_files = manifest[DefaultInfo].data_runfiles.files.to_list() + manifest_file = manifest.files.to_list()[0] + else: + manifest_file = manifest + signing_directives = [] pre = ctx.actions.declare_file("{}.pre-signing".format(basename)) inputs = [ src, - ] + ] + manifest_dependent_files manifest_args = [] - if manifest: - inputs.append(manifest) - manifest_args.append("--manifest={}".format(manifest.path)) + if manifest_file: + if manifest_file not in inputs: + inputs.append(manifest_file) + manifest_args.append("--manifest={}".format(manifest_file.path)) ecdsa_or_rsa_args = [] if ecdsa_key:
diff --git a/target/earlgrey/tooling/signing/signing.bzl b/target/earlgrey/tooling/signing/signing.bzl index 98b613e..332f965 100644 --- a/target/earlgrey/tooling/signing/signing.bzl +++ b/target/earlgrey/tooling/signing/signing.bzl
@@ -61,7 +61,7 @@ ctx, opentitantool, get_override(ctx, "file.bin", kwargs), - get_override(ctx, "file.manifest", kwargs), + get_override(ctx, "attr.manifest", kwargs), ecdsa_key, rsa_key, spx_key, @@ -98,8 +98,23 @@ } def _sign_bin_impl(ctx): - system_image_info = ctx.attr.bin[SystemImageInfo] - result = sign_binary(ctx, ctx.executable._opentitantool, bin = system_image_info.bin) + bin_file = None + if SystemImageInfo in ctx.attr.bin: + bin_file = ctx.attr.bin[SystemImageInfo].bin + else: + for f in ctx.files.bin: + if f.basename.endswith(".bin"): + bin_file = f + break + if not bin_file: + fail("No .bin file found in bin attribute") + + result = sign_binary( + ctx, + ctx.executable._opentitantool, + bin = bin_file, + basename = ctx.attr.basename or None, + ) return [ DefaultInfo(files = depset([result["signed"]]), data_runfiles = ctx.runfiles(files = [result["signed"]])), ] @@ -107,12 +122,13 @@ sign_bin = rule( implementation = _sign_bin_impl, attrs = { - "bin": attr.label(providers = [SystemImageInfo]), + "basename": attr.string(doc = "Optional output filename basename"), + "bin": attr.label(allow_files = True), "ecdsa_key": attr.label_keyed_string_dict( allow_files = True, doc = "ECDSA public key to validate this image", ), - "manifest": attr.label(allow_single_file = True), + "manifest": attr.label(allow_files = True), "rsa_key": attr.label_keyed_string_dict( allow_files = True, doc = "RSA public key to validate this image",