From 6b3162211312a0393842bcbedce990826145cefe Mon Sep 17 00:00:00 2001 From: David Pierret Date: Mon, 4 May 2026 17:07:17 +0200 Subject: [PATCH 1/3] feat: enhance FitImageSigner to resolve keyname from DTB and fitimage metadata Co-authored-by: Copilot Signed-off-by: David Pierret --- digsigserver/server.py | 9 +++- digsigserver/signers/fitimagesign.py | 68 +++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/digsigserver/server.py b/digsigserver/server.py index 5a48c20..0bad7d2 100644 --- a/digsigserver/server.py +++ b/digsigserver/server.py @@ -198,6 +198,7 @@ async def sign_handler_fitimage(req: request): f = validate_upload(req, "artifact") if not f: return text("Invalid artifact", status=400) + dtb = validate_upload(req, "dtb") with tempfile.TemporaryDirectory() as workdir: try: s = FitImageSigner(app, workdir) @@ -207,11 +208,17 @@ async def sign_handler_fitimage(req: request): with open(os.path.join(workdir, "artifact"), "wb") as artifact: artifact.write(f.body) + dtb_path = None + if dtb: + dtb_path = os.path.join(workdir, dtb.name or "u-boot.dtb") + with open(dtb_path, "wb") as dtb_file: + dtb_file.write(dtb.body) + outfile = tempfile.NamedTemporaryFile(delete=False) outfile.close() if await asyncio.get_running_loop().run_in_executor(None, s.sign, artifact.name, - None, + dtb_path, req.form.get("external_data_offset"), req.form.get("mark_required"), req.form.get("algo"), diff --git a/digsigserver/signers/fitimagesign.py b/digsigserver/signers/fitimagesign.py index 75c9692..b1fdda9 100644 --- a/digsigserver/signers/fitimagesign.py +++ b/digsigserver/signers/fitimagesign.py @@ -1,5 +1,7 @@ import os import copy +import re +import subprocess from typing import Optional from digsigserver.signers import Signer @@ -20,14 +22,76 @@ def _prepare_path(self) -> dict: env['PATH'] += ':' + curpath return env + @staticmethod + def _normalize_keyname(keyname: Optional[str]) -> Optional[str]: + if not keyname: + return None + resolved = keyname.strip() + if not resolved: + return None + if resolved.endswith('.key'): + resolved = resolved[:-4] + return resolved or None + + def _keyname_from_dtb(self, dtb: Optional[str]) -> Optional[str]: + if not dtb or not os.path.exists(dtb): + return None + try: + with open(dtb, 'rb') as f: + content = f.read().decode('latin-1', errors='ignore') + except OSError: + return None + + # U-Boot signature keys are typically named key-. + match = re.search(r'key-([A-Za-z0-9_.-]+)', content) + if match: + return match.group(1) + return None + + def _keyname_from_fitimage(self, fitimage: str, env: dict) -> Optional[str]: + try: + proc = subprocess.run( + ['mkimage', '-l', fitimage], + stdin=subprocess.DEVNULL, + cwd=self.workdir, + env=env, + check=True, + capture_output=True, + encoding='utf-8' + ) + except (subprocess.CalledProcessError, FileNotFoundError): + return None + + # Example: "Sign algo: sha256,rsa2048:dev_vb" + match = re.search(r'Sign algo:\s+[^:\n]+:([A-Za-z0-9_.-]+)', proc.stdout) + if match: + return match.group(1) + return None + + def _resolve_keyname(self, keyname: Optional[str], dtb: Optional[str], fitimage: str, env: dict) -> str: + resolved = self._normalize_keyname(keyname) + if resolved: + return resolved + resolved = self._keyname_from_dtb(dtb) + if resolved: + logger.info("Resolved fitImage keyname from dtb: %s", resolved) + return resolved + resolved = self._keyname_from_fitimage(fitimage, env) + if resolved: + logger.info("Resolved fitImage keyname from fitImage metadata: %s", resolved) + return resolved + logger.info("Using default fitImage keyname: dev") + return 'dev' + def sign(self, fitimage: str, dtb: Optional[str], external_data_offset: Optional[str], mark_required: Optional[bool], algo: Optional[str], - keyname: str = "dev.key") -> bool: - private_key = self.keys.get("{}.key".format(keyname)) + keyname: Optional[str] = None) -> bool: env = self._prepare_path() + selected_keyname = self._resolve_keyname(keyname, dtb, fitimage, env) + private_key = self.keys.get("{}.key".format(selected_keyname)) cmd = [ 'mkimage', '-F', '-k', os.path.dirname(private_key) ] if external_data_offset: cmd += [ '-p', external_data_offset ] From 77871337d995e61a6cd429dd2a4c146e38723e99 Mon Sep 17 00:00:00 2001 From: David Pierret Date: Mon, 4 May 2026 17:24:34 +0200 Subject: [PATCH 2/3] feat: update FitImageSigner to accept machine parameter for improved flexibility Co-authored-by: Copilot Signed-off-by: David Pierret --- digsigserver/server.py | 2 +- digsigserver/signers/fitimagesign.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/digsigserver/server.py b/digsigserver/server.py index 0bad7d2..c7594e3 100644 --- a/digsigserver/server.py +++ b/digsigserver/server.py @@ -201,7 +201,7 @@ async def sign_handler_fitimage(req: request): dtb = validate_upload(req, "dtb") with tempfile.TemporaryDirectory() as workdir: try: - s = FitImageSigner(app, workdir) + s = FitImageSigner(app, workdir, req.form.get("machine") or "imx") except ValueError: return text("Invalid parameters", status=400) diff --git a/digsigserver/signers/fitimagesign.py b/digsigserver/signers/fitimagesign.py index b1fdda9..c3e5d26 100644 --- a/digsigserver/signers/fitimagesign.py +++ b/digsigserver/signers/fitimagesign.py @@ -12,8 +12,8 @@ class FitImageSigner (Signer): keytag = 'fitimagesign' - def __init__(self, app: Sanic, workdir: str): - super().__init__(app, workdir, "imx") + def __init__(self, app: Sanic, workdir: str, machine: str = "imx"): + super().__init__(app, workdir, machine) def _prepare_path(self) -> dict: env = dict(copy.deepcopy(os.environ)) From 98f7360d0848e945a2d2b00992b3ff70e01bb54d Mon Sep 17 00:00:00 2001 From: David Pierret Date: Mon, 4 May 2026 18:53:06 +0200 Subject: [PATCH 3/3] feat: update FitImageSigner to inject signature in FIT image Signed-off-by: David Pierret --- digsigserver/server.py | 16 +++++++++++----- digsigserver/signers/fitimagesign.py | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/digsigserver/server.py b/digsigserver/server.py index c7594e3..0a37120 100644 --- a/digsigserver/server.py +++ b/digsigserver/server.py @@ -205,8 +205,9 @@ async def sign_handler_fitimage(req: request): except ValueError: return text("Invalid parameters", status=400) - with open(os.path.join(workdir, "artifact"), "wb") as artifact: - artifact.write(f.body) + fitimage_path = os.path.join(workdir, "fitImage") + with open(fitimage_path, "wb") as fitimage: + fitimage.write(f.body) dtb_path = None if dtb: @@ -217,14 +218,19 @@ async def sign_handler_fitimage(req: request): outfile = tempfile.NamedTemporaryFile(delete=False) outfile.close() if await asyncio.get_running_loop().run_in_executor(None, s.sign, - artifact.name, + fitimage_path, dtb_path, req.form.get("external_data_offset"), req.form.get("mark_required"), req.form.get("algo"), req.form.get("keyname")): - await return_file(req, artifact.name, "artifact.signed") - response = None + if dtb_path: + dtb_name = os.path.basename(dtb_path) + response = await return_tarball(req, workdir, return_filename="signed-fitImage.tar.gz", + files_to_return=["fitImage", dtb_name]) + else: + await return_file(req, fitimage_path, "fitImage.signed") + response = None else: response = text("Signing error", status=500) return response diff --git a/digsigserver/signers/fitimagesign.py b/digsigserver/signers/fitimagesign.py index c3e5d26..ebdfeb5 100644 --- a/digsigserver/signers/fitimagesign.py +++ b/digsigserver/signers/fitimagesign.py @@ -92,6 +92,7 @@ def sign(self, fitimage: str, env = self._prepare_path() selected_keyname = self._resolve_keyname(keyname, dtb, fitimage, env) private_key = self.keys.get("{}.key".format(selected_keyname)) + self.keys.get("{}.crt".format(selected_keyname)) cmd = [ 'mkimage', '-F', '-k', os.path.dirname(private_key) ] if external_data_offset: cmd += [ '-p', external_data_offset ]