Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions digsigserver/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
73 changes: 69 additions & 4 deletions digsigserver/signers/fitimagesign.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os
import copy
import re
import subprocess
from typing import Optional
from digsigserver.signers import Signer

Expand All @@ -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))
Expand All @@ -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-<name>.
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 ]
Expand Down