blob: 0ccbad154cbf76b777e0320308e9b15b9eca9ae7 [file]
# Licensed under the Apache-2.0 license
# SPDX-License-Identifier: Apache-2.0
load("@bazel_skylib//lib:paths.bzl", "paths")
SigningToolInfo = provider(fields = ["tool", "data", "env", "location"])
KeySetInfo = provider(fields = ["keys", "config", "selected_key", "profile", "sign", "tool"])
def get_override(obj, item, overrides):
"""Get an item from obj unless it exists in overrides.
Args:
obj: The object holding the item.
item: An object path to the desired item (ie: `attr.srcs`).
overrides: A dict that may contain an override named by the last
component of the item path (ie: `srcs`).
"""
items = item.split(".")
item = items[-1]
if item in overrides:
return overrides.get(item)
for i in items:
obj = getattr(obj, i)
return obj
def label_str(label):
return "@{}//{}:{}".format(label.workspace_name, label.package, label.name)
def key_from_dict(key, attr_name):
"""Extract the key information from the `key` dict.
Args:
key: dict; A signing key and nickname or a keyset and key nickname.
attr_name: The attribute name (used for error reporting).
Returns:
A struct with the key label, the key file and key nickname.
"""
if not key:
return None
if len(key) == 0:
return None
if len(key) != 1:
fail("Expected exactly one key/value pair for attribute", attr_name)
key, name = key.items()[0]
if "/" in name or "." in name or ":" in name:
fail("Invalid key nickname for ", str(key), ". Nickname ", name, " is invalid.")
if KeySetInfo in key:
ksi = key[KeySetInfo]
if ksi.selected_key:
name = ksi.selected_key
elif name.isdigit():
# If the nickname is a number, we assume it is the index of the key in
# the dictionary.
name = int(name)
name = ksi.keys.keys()[name]
return struct(
label = key,
file = ksi.keys[name],
name = name,
info = None,
config = ksi.config[name],
)
elif DefaultInfo in key:
key_file = key[DefaultInfo].files.to_list()
if len(key_file) != 1:
fail("Expected label to refer to exactly one file:", key)
return struct(
label = key,
file = key_file[0],
name = name,
config = {},
)
return None
def signing_tool_info(ctx, key, opentitantool):
"""Returns the signing tool information for a given key.
Args:
ctx: The rule context object.
key: The key dict attribute.
opentitantool: A reference to opentitantool.
Returns:
(SigningToolInfo, signing function, profile)
"""
key, _ = key.items()[0]
if KeySetInfo in key:
ksi = key[KeySetInfo]
return ksi.tool, ksi.sign, ksi.profile
elif DefaultInfo in key:
toolinfo = SigningToolInfo(
tool = opentitantool,
data = [],
env = {},
location = "local",
)
return toolinfo, local_sign, None
fail("Expected a KeySetInfo or DefaultInfo provider")
def key_ext(ecdsa, rsa, spx):
"""Returns the key extension for a given key.
Args:
ecdsa: struct; The ECDSA key.
rsa: struct; The RSA key.
spx: struct; The SPX+ key.
Returns:
str: The key extension.
"""
if ecdsa:
name = ecdsa.name
elif rsa:
name = rsa.name
else:
fail("Expected an ECDSA or RSA key")
if spx:
return ".{}.{}".format(name, spx.name)
else:
return ".{}".format(name)
def local_sign(ctx, tool, digest, ecdsa_key, rsa_key, spxmsg = None, spx_key = None, profile = None):
"""Sign a digest with a local on-disk RSA private key.
Args:
ctx: The rule context.
tool: SigningToolInfo; A provider refering to the opentitantool binary.
digest: file; The digest of the binary to be signed.
ecdsa_key: struct; The ECDSA private key.
rsa_key: struct; The RSA private key.
spxmsg: file; The SPX+ message to be signed.
spx_key: struct; The SPX+ private key.
profile: str; The token profile. Not used by this function.
Returns:
file, file, file: The ECDSA, RSA and SPX signature files.
"""
if rsa_key and ecdsa_key:
fail("Only one of ECDSA or RSA key should be provided")
inputs = [digest]
if rsa_key:
output_sig = ctx.actions.declare_file(paths.replace_extension(digest.basename, ".rsa_sig"))
inputs.append(rsa_key.file)
key_path = rsa_key.file.path
key_command = "rsa"
elif ecdsa_key:
output_sig = ctx.actions.declare_file(paths.replace_extension(digest.basename, ".ecdsa_sig"))
inputs.append(ecdsa_key.file)
key_path = ecdsa_key.file.path
key_command = "ecdsa"
else:
fail("Expected an ECDSA or RSA key")
ctx.actions.run(
outputs = [output_sig],
inputs = inputs,
arguments = [
"--rcfile=",
"--quiet",
key_command,
"sign",
"--input={}".format(digest.path),
"--output={}".format(output_sig.path),
key_path,
],
executable = tool.tool,
mnemonic = "LocalRsaOrEcdsaSign",
)
spx_sig = None
if spxmsg and spx_key:
private_key = spx_key.file
spx_sig = ctx.actions.declare_file(paths.replace_extension(spxmsg.basename, ".spx_sig"))
domain = spx_key.config.get("domain", "Pure")
rev = spx_key.config.get("byte-reversal-bug", "false")
ctx.actions.run(
outputs = [spx_sig],
inputs = [spxmsg, private_key],
arguments = [
"--rcfile=",
"--quiet",
"spx",
"sign",
"--spx-hash-reversal-bug={}".format(rev),
"--domain={}".format(domain),
"--output={}".format(spx_sig.path),
spxmsg.path,
private_key.path,
],
executable = tool.tool,
mnemonic = "LocalSpxSign",
)
if rsa_key:
return None, output_sig, spx_sig
elif ecdsa_key:
return output_sig, None, spx_sig
else:
fail("Expected an ECDSA or RSA key")
def hsmtool_sign(ctx, tool, digest, ecdsa_key, rsa_key, spxmsg = None, spx_key = None, profile = None):
"""Sign a digest with a token-provided private key.
Args:
ctx: The rule context.
tool: file; A SigningToolInfo provider referring to the hsmtool binary.
digest: file; The digest of the binary to be signed.
ecdsa_key: struct; The ECDSA private key.
rsa_key: struct; The RSA private key.
spxmsg: file; The SPX+ message to be signed.
spx_key: struct; The SPX+ private key.
profile: str; The hsmtool profile.
Returns:
file, file, file: The RSA and SPX signature files.
"""
if not profile:
fail("Missing the `hsmtool` profile")
if rsa_key:
cmd = "rsa"
sig = ctx.actions.declare_file(paths.replace_extension(digest.basename, ".rsa-sig"))
label = rsa_key.name
mnemonic = "HsmtoolRsaSign"
retval = (None, sig, None)
elif ecdsa_key:
cmd = "ecdsa"
sig = ctx.actions.declare_file(paths.replace_extension(digest.basename, ".ecdsa-sig"))
label = ecdsa_key.name
mnemonic = "HsmtoolEcdsaSign"
retval = (sig, None, None)
else:
fail("Expected either rsa_key or ecdsa_key; got neither")
ctx.actions.run(
outputs = [sig],
inputs = [digest, tool.tool] + tool.data,
arguments = [
"--quiet",
"--lockfile=/tmp/hsmtool.lock",
"--profile={}".format(profile),
cmd,
"sign",
"--little-endian",
"--format=sha256-hash",
"--label={}".format(label),
"--output={}".format(sig.path),
digest.path,
],
executable = tool.tool,
execution_requirements = {
"no-sandbox": "",
},
env = tool.env,
mnemonic = mnemonic,
)
spx_sig = None
if spxmsg and spx_key:
domain = spx_key.config.get("domain", "Pure")
if domain.lower() == "prehashedsha256":
rev = spx_key.config.get("byte-reversal-bug", "false")
fmt = "sha256-hash-reversed" if rev == "true" else "sha256-hash"
args = [
"--little-endian",
"--format={}".format(fmt),
"--domain={}".format(domain),
]
else:
args = [
"--format=plain-text",
"--domain={}".format(domain),
]
spx_sig = ctx.actions.declare_file(paths.replace_extension(spxmsg.basename, ".spx-sig"))
ctx.actions.run(
outputs = [spx_sig],
inputs = [spxmsg, tool.tool] + tool.data,
arguments = [
"--quiet",
"--lockfile=/tmp/hsmtool.lock",
"--profile={}".format(profile),
"spx",
"sign",
"--label={}".format(spx_key.name),
"--output={}".format(spx_sig.path),
spxmsg.path,
] + args,
executable = tool.tool,
execution_requirements = {
"no-sandbox": "",
},
env = tool.env,
mnemonic = "HsmtoolSpxSign",
)
if rsa_key:
return None, sig, spx_sig
elif ecdsa_key:
return sig, None, spx_sig
else:
fail("Expected an ECDSA or RSA key")
def clear_if_none_key(key_attr):
"""Clear the key attribute if it is set to "//hw/signing:none_key.
Args:
key_attr: The key attribute.
Returns:
The key attribute if it is not set to "//hw/signing:none_key" or {}.
"""
if not key_attr:
return None
key, _ = key_attr.items()[0]
if key.label.name == "none_key":
return None
return key_attr