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",