OpenMiInk/third_party/ci-notary-service/sftpnotary/macappsigner.py
Hellings b8c8ff98e2 Hi
2026-10-16 09:05:56 -04:00

138 lines
5.7 KiB
Python

# SPDX-License-Identifier: BSD-2-Clause
# SPDX-FileCopyrightText: 2023 KDE e.V.
# SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
# SPDX-FileCopyrightText: 2023 Julius Künzel <jk.kdedev@smartlab.uber.space>
import dataclasses
import logging
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Optional
import sftpnotary.log
from sftpnotary import appleutils, config, projects, util
from sftpnotary.core import SignFileResponse, SingleFileRequest, Worker
from sftpnotary.entitlements_whitelist import validate_entitlements_file
from sftpnotary.exceptions import InvalidFileName
from sftpnotary.sftp import SFTPClient
log = logging.getLogger(__name__)
@dataclasses.dataclass
class SignMacAppRequest(SingleFileRequest):
"""Extends SingleFileRequest to include optional entitlements file name for mac app signing."""
entitlementsFileName: Optional[str] = None
def validate(self):
super().validate()
if self.entitlementsFileName:
util.validateFileName(self.entitlementsFileName)
# Must end with .entitlements
if not self.entitlementsFileName.endswith(".entitlements"):
raise InvalidFileName(f"Entitlements file must end with .entitlements: {self.entitlementsFileName}")
# Must match <projectname>.entitlements pattern
if self.projectPath:
expectedName = f"{self.projectPath.split('/')[-1]}.entitlements"
if self.entitlementsFileName != expectedName:
raise InvalidFileName(
f"Entitlements file name must match project: "
f"expected {expectedName}, got {self.entitlementsFileName}"
)
SignMacAppResponse = SignFileResponse
class SignMacAppJob(appleutils.AppleJob):
def __init__(
self,
sftp: SFTPClient,
macAppFilePath: Path,
logFolder: Path,
entitlementsFilePath: Optional[Path] = None,
token: Optional[str] = None,
projectPath: Optional[str] = None,
branch: Optional[str] = None,
):
super().__init__(sftp, macAppFilePath, logFolder)
self.entitlementsFilePath = entitlementsFilePath
self.request = SignMacAppRequest(
fileName=macAppFilePath.name,
entitlementsFileName=(entitlementsFilePath.name if entitlementsFilePath else None),
token=token,
projectPath=projectPath,
branch=branch,
)
self.responseClass = SignMacAppResponse
def start(self):
self.uploadUnsignedFile()
if self.entitlementsFilePath:
remotePath = self.task.path() / self.entitlementsFilePath.name
self.sftp.upload(self.entitlementsFilePath, remotePath)
self.task.putRequest(self.request)
self.taskQueue.commitTask(self.task)
class SignMacAppProcessor(appleutils.AppleTaskProcessor):
requestClass = SignMacAppRequest
responseClass = SignMacAppResponse
resultNameSuffix = "signed"
def doProcess(self):
remoteFilePath = self.task.path() / self.request.fileName
with TemporaryDirectory() as workPathName:
workPath = Path(workPathName)
localFilePath = self.downloadApp(workPath, remoteFilePath)
localSignedFilePath = workPath / f"processed{localFilePath.suffix}"
# Download entitlements if provided
entitlementsPath = None
if self.request.entitlementsFileName:
remoteEntitlementsPath = self.task.path() / self.request.entitlementsFileName
entitlementsPath = workPath / self.request.entitlementsFileName
self.sftp.download(remoteEntitlementsPath, entitlementsPath)
validate_entitlements_file(entitlementsPath)
log.info(f"Validated entitlements: {self.request.entitlementsFileName}")
allowedApplicationId = projects.settings.get(self.request.projectPath, self.request.branch, "applicationid")
appleutils.verifyApplicationId(localFilePath, allowedApplicationId)
self.signMacApp(localFilePath, localSignedFilePath.name, entitlementsPath)
return self.uploadApp(workPath, localSignedFilePath, remoteFilePath, localFilePath)
def signMacApp(self, sourceFilePath, signedFileName, entitlementsPath: Optional[Path] = None):
workingDirectory = sourceFilePath.parent
sourceFileName = sourceFilePath.name
rcodesignPath = config.settings.getPath("AppleTools", "RCodesignPath") or "rcodesign"
keystore = config.settings.getPath("MacAppSigning", "KeyStore")
keystorepass = config.settings.get("MacAppSigning", "KeyStorePass")
command = [rcodesignPath, "sign", "-v", "--for-notarization"]
if keystore and keystore.suffix in [".pem", ".crt", ".key"]:
command += ["--pem-file", Path(keystore)]
elif keystore and keystore.suffix in [".p12", ".pfx"]:
command += ["--p12-file", Path(keystore)]
if keystorepass:
if keystorepass.startswith("file:"):
keystorepass = Path(keystorepass[5:]).expanduser()
command += ["--p12-password-file", keystorepass]
else:
command += ["--p12-password", keystorepass]
# Apply entitlements if provided
if entitlementsPath and entitlementsPath.exists():
command += ["--entitlements-xml-path", entitlementsPath]
command += [sourceFileName, signedFileName]
commandToLog = sftpnotary.log.maskFollowingItem(command, "--p12-password")
util.runCommand(command, commandToLog=commandToLog, cwd=workingDirectory)
class SignMacAppWorker(Worker):
processorClass = SignMacAppProcessor