fix(core): store translation signatures directly into history section

without messing with the "current" entry at all
pull/3646/head
matejcik 2 months ago
parent 4fb7d477d3
commit 4bcd6d4f12

@ -31,16 +31,22 @@ VERSION_H = HERE.parent / "embed" / "firmware" / "version.h"
SIGNATURES_JSON = HERE / "signatures.json" SIGNATURES_JSON = HERE / "signatures.json"
class SignatureInfo(t.TypedDict): class SignedInfo(t.TypedDict):
merkle_root: str
signature: str
datetime: str
commit: str
class UnsignedInfo(t.TypedDict):
merkle_root: str merkle_root: str
signature: str | None
datetime: str datetime: str
commit: str commit: str
class SignatureFile(t.TypedDict): class SignatureFile(t.TypedDict):
current: SignatureInfo current: UnsignedInfo
history: list[SignatureInfo] history: list[SignedInfo]
def _version_from_version_h() -> translations.VersionTuple: def _version_from_version_h() -> translations.VersionTuple:
@ -63,21 +69,22 @@ def _version_from_version_h() -> translations.VersionTuple:
) )
def make_signature_info(merkle_root: bytes, signature: bytes | None) -> SignatureInfo: def make_tree_info(merkle_root: bytes) -> UnsignedInfo:
now = datetime.datetime.utcnow() now = datetime.datetime.utcnow()
commit = ( commit = (
subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=HERE) subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=HERE)
.decode("ascii") .decode("ascii")
.strip() .strip()
) )
return SignatureInfo( return UnsignedInfo(
merkle_root=merkle_root.hex(), merkle_root=merkle_root.hex(), datetime=now.isoformat(), commit=commit
signature=signature.hex() if signature is not None else None,
datetime=now.isoformat(),
commit=commit,
) )
def sign_info(info: UnsignedInfo, signature: bytes) -> SignedInfo:
return SignedInfo(signature=signature.hex(), **info)
def update_merkle_root(signature_file: SignatureFile, merkle_root: bytes) -> bool: def update_merkle_root(signature_file: SignatureFile, merkle_root: bytes) -> bool:
"""Update signatures.json with the new Merkle root. """Update signatures.json with the new Merkle root.
@ -89,16 +96,9 @@ def update_merkle_root(signature_file: SignatureFile, merkle_root: bytes) -> boo
# Merkle root is already up to date # Merkle root is already up to date
return False return False
if current["signature"] is None: # overwrite with a new one
# current content is not signed. just overwrite with a new one signature_file["current"] = make_tree_info(merkle_root)
signature_file["current"] = make_signature_info(merkle_root, None) SIGNATURES_JSON.write_text(json.dumps(signature_file, indent=2))
SIGNATURES_JSON.write_text(json.dumps(signature_file, indent=2))
return True
# move current to history
signature_file["history"].insert(0, current)
# create new current
signature_file["current"] = make_signature_info(merkle_root, None)
return True return True
@ -188,7 +188,8 @@ def cli() -> None:
@cli.command() @cli.command()
def gen() -> None: @click.option("--signed", is_flag=True, help="Generate signed blobs.")
def gen(signed: bool | None) -> None:
"""Generate all language blobs for all models. """Generate all language blobs for all models.
The generated blobs will be signed with the development keys. The generated blobs will be signed with the development keys.
@ -196,11 +197,25 @@ def gen() -> None:
all_blobs = generate_all_blobs(rewrite_version=True) all_blobs = generate_all_blobs(rewrite_version=True)
tree = merkle_tree.MerkleTree(b.header_bytes for b in all_blobs) tree = merkle_tree.MerkleTree(b.header_bytes for b in all_blobs)
root = tree.get_root_hash() root = tree.get_root_hash()
signature_file: SignatureFile = json.loads(SIGNATURES_JSON.read_text())
if signed:
for entry in signature_file["history"]:
if entry["merkle_root"] == root.hex():
signature_hex = entry["signature"]
signature_bytes = bytes.fromhex(signature_hex)
sigmask, signature = signature_bytes[0], signature_bytes[1:]
build_all_blobs(all_blobs, tree, sigmask, signature, production=True)
return
else:
raise click.ClickException(
"No matching signature found in signatures.json. Run `cli.py sign` first."
)
signature = cosi.sign_with_privkeys(root, PRIVATE_KEYS_DEV) signature = cosi.sign_with_privkeys(root, PRIVATE_KEYS_DEV)
sigmask = 0b111 sigmask = 0b111
build_all_blobs(all_blobs, tree, sigmask, signature) build_all_blobs(all_blobs, tree, sigmask, signature)
signature_file = json.loads(SIGNATURES_JSON.read_text())
if update_merkle_root(signature_file, root): if update_merkle_root(signature_file, root):
SIGNATURES_JSON.write_text(json.dumps(signature_file, indent=2) + "\n") SIGNATURES_JSON.write_text(json.dumps(signature_file, indent=2) + "\n")
click.echo("Updated signatures.json") click.echo("Updated signatures.json")
@ -228,13 +243,10 @@ def merkle_root() -> None:
@cli.command() @cli.command()
@click.argument("signature_hex", required=False) @click.argument("signature_hex")
@click.option("--force", is_flag=True) @click.option("--force", is_flag=True, help="Write even if the signature is invalid.")
def sign(signature_hex: str | None, force: bool) -> None: def sign(signature_hex: str, force: bool | None) -> None:
"""Insert a signature into language blobs. """Insert a signature into language blobs."""
If signature_hex is not provided, the signature will be located in signatures.json.
"""
all_blobs = generate_all_blobs(rewrite_version=False) all_blobs = generate_all_blobs(rewrite_version=False)
tree = merkle_tree.MerkleTree(b.header_bytes for b in all_blobs) tree = merkle_tree.MerkleTree(b.header_bytes for b in all_blobs)
root = tree.get_root_hash() root = tree.get_root_hash()
@ -247,23 +259,10 @@ def sign(signature_hex: str | None, force: bool) -> None:
f"Stored in signatures.json: {signature_file['current']['merkle_root']}" f"Stored in signatures.json: {signature_file['current']['merkle_root']}"
) )
if signature_hex is None: # Update signature file data. It will be written only if the signature verifies.
if signature_file["current"]["signature"] is None: tree_info = make_tree_info(root)
raise click.ClickException("Please provide a signature.") signed_info = sign_info(tree_info, bytes.fromhex(signature_hex))
signature_hex = signature_file["current"]["signature"] signature_file["history"].insert(0, signed_info)
elif (
not force
and signature_file["current"]["signature"] is not None
and signature_file["current"]["signature"] != signature_hex
):
raise click.ClickException(
"A different signature is already present in signatures.json\n"
"Use --force to overwrite it."
)
else:
# Update signature file data. It will be written only if the signature verifies.
signature_file["current"]["signature"] = signature_hex
signature_file["current"]["datetime"] = datetime.datetime.utcnow().isoformat()
signature_bytes = bytes.fromhex(signature_hex) signature_bytes = bytes.fromhex(signature_hex)
sigmask, signature = signature_bytes[0], signature_bytes[1:] sigmask, signature = signature_bytes[0], signature_bytes[1:]

@ -1,7 +1,6 @@
{ {
"current": { "current": {
"merkle_root": "d349550e9ad7be0b63b06fc43c6b764d008bf4497a8e1d0374cb92119b242160", "merkle_root": "d349550e9ad7be0b63b06fc43c6b764d008bf4497a8e1d0374cb92119b242160",
"signature": null,
"datetime": "2024-03-25T14:23:51.859562", "datetime": "2024-03-25T14:23:51.859562",
"commit": "ef11039a818bb0941191af49e0c9a0f7aae9324a" "commit": "ef11039a818bb0941191af49e0c9a0f7aae9324a"
}, },

Loading…
Cancel
Save