diff --git a/digsigserver/server.py b/digsigserver/server.py index 5a48c20..0a37120 100644 --- a/digsigserver/server.py +++ b/digsigserver/server.py @@ -198,26 +198,39 @@ 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) + s = FitImageSigner(app, workdir, req.form.get("machine") or "imx") 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: + 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, + 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 75c9692..ebdfeb5 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 @@ -10,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)) @@ -20,14 +22,77 @@ 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)) + 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 ]