UNPKG

signing-enclave

Version:

A code signing and notarization tool for macOS and Windows applications. Uses Azure Key Vault for secret and certificate management.

319 lines 13 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.handleNewFile = exports.notarizationTask = exports.codeSigningTask = exports.updateReceipt = exports.createReceipt = void 0; exports.getCertTask = getCertTask; exports.getSecretTask = getSecretTask; exports.getNotarizationCredentialsTask = getNotarizationCredentialsTask; exports.doWatchFolder = doWatchFolder; const path_1 = __importDefault(require("path")); const crypto_1 = require("crypto"); const os_1 = __importDefault(require("os")); const utils_1 = require("./utils"); const promises_1 = __importStar(require("fs/promises")); const macOSSigning_1 = require("./signing/macOSSigning"); const macOSNotarization_1 = require("./signing/macOSNotarization"); const windowsSigning_1 = require("./signing/windowsSigning"); const secretClient_1 = require("./signing/secretClient"); const chokidar_1 = __importDefault(require("chokidar")); const events_1 = require("events"); async function readRequest(filePath) { return (0, utils_1.readJson)(filePath); } async function readReceipt(filePath) { return (0, utils_1.readJson)(filePath); } async function writeReceipt(filePath, receipt) { return (0, utils_1.writeJson)(filePath, receipt); } let watchFolder; let options; const createReceipt = async (request, requestPath, // for tests generateUUID = crypto_1.randomUUID, writeReceiptFn = writeReceipt) => { const receipt = { id: generateUUID().slice(-6), originalFile: request.path, receiptFile: path_1.default.basename(requestPath.replace(".request.json", ".receipt.json")), requestFile: path_1.default.basename(requestPath), platform: request.platform, type: request.type, provisioningProfile: request.provisioningProfile, status: "Pending", secretName: request.secretName, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; await writeReceiptFn(getFullPath(receipt.receiptFile), receipt); console.log(`Receipt created: ${receipt.receiptFile}`); return receipt; }; exports.createReceipt = createReceipt; const updateReceipt = async (receipt, updates) => { try { const updatedReceipt = { ...receipt, ...updates, updatedAt: new Date().toISOString(), }; await writeReceipt(getFullPath(receipt.receiptFile), updatedReceipt); console.log(`Receipt updated: ${receipt.receiptFile} | Status: ${updatedReceipt.status}`); return updatedReceipt; } catch (err) { console.error(`Failed to update receipt: ${receipt.receiptFile}`, err); throw err; } }; exports.updateReceipt = updateReceipt; const codeSigningTask = async (receipt) => { const fileToSign = getFullPath(receipt.originalFile); const platform = receipt.platform; console.log(`Starting code signing for: ${path_1.default.basename(fileToSign)}`); if (platform === "mac" && options.macCert) { return (0, macOSSigning_1.macOsSign)(fileToSign, options.macCert); } else if (platform === "mas") { return (0, macOSSigning_1.macOsSign)(fileToSign, "todesktop-2108257l5oobyoe-mac-cert"); } else if (platform === "macPkg") { return (0, macOSSigning_1.macOsSign)(fileToSign, "todesktop-2108257l5oobyoe-mac-cert"); } else if (platform === "win" && options.windowsCert) { return (0, windowsSigning_1.windowsSign)(fileToSign, options.windowsCert); } }; exports.codeSigningTask = codeSigningTask; const notarizationTask = async (receipt) => { const fileToNotarize = getFullPath(receipt.originalFile); const platform = receipt.platform; if (platform === "mac" && options.macNotarize) { console.log(`Notarizing: ${JSON.stringify(options.macNotarize)}, ${fileToNotarize}`); return (0, macOSNotarization_1.notarizeWithTimeout)(options.macNotarize, // or // { // $appleApiKey: "todesktop-2108257l5oobyoe-mac-api-key", // appleApiKeyId: "T9GPZ92M7K", // appleApiIssuer: "c055ca8c-e5a8-4836-b61d-aa5794eeb3f4", // } fileToNotarize); } }; exports.notarizationTask = notarizationTask; function getCertName(receipt) { if (receipt.platform === "mac") { if (!options.macCert) { throw new Error("No mac certificate found"); } return options.macCert; } else if (receipt.platform === "mas" && receipt.type === "distribution") { if (!options.masDistributionCert) { throw new Error("No mas distribution certificate found"); } return options.masDistributionCert; } else if (receipt.platform === "mas" && receipt.type === "development") { if (!options.masInstallerCert) { throw new Error("No mas installer certificate found"); } return options.masInstallerCert; } else if (receipt.platform === "mas" && receipt.type === "installer") { if (!options.masInstallerCert) { throw new Error("No mas installer certificate found"); } return options.masInstallerCert; } else if (receipt.platform === "macPkg") { if (!options.macInstallerCert) { throw new Error("No mac installer certificate found"); } return options.macInstallerCert; } throw new Error(`Unsupported platform: ${receipt.platform}`); } async function getCertTask(receipt) { const certPath = getFullPath(receipt.originalFile); return (0, secretClient_1.downloadCertificateFile)(getCertName(receipt), { filePath: certPath, }); } async function getSecretTask(receipt) { const { secretName } = receipt; if (!secretName) { throw new Error("No secret name found"); } const secretPath = getFullPath(receipt.originalFile); if (!Array.isArray(options.secrets)) { throw new Error("No secrets found"); } const matchedSecret = options.secrets.find((obj) => obj[secretName])?.[secretName]; if (!matchedSecret) { throw new Error(`No secret found for: ${receipt.secretName}`); } await (0, promises_1.writeFile)(secretPath, await (0, secretClient_1.getSecret)(matchedSecret)); return { secretFilePath: secretPath, cleanup: () => promises_1.default.unlink(secretPath), }; } async function getNotarizationCredentialsTask(receipt) { if (!options.macNotarize) { throw new Error("No notarization details found"); } const secretPath = getFullPath(receipt.originalFile); const credentials = await (0, macOSNotarization_1.getNotarizationSecretsFromHSM)(options.macNotarize); await (0, promises_1.writeFile)(secretPath, JSON.stringify(credentials)); return { secretFilePath: secretPath, cleanup: () => promises_1.default.unlink(secretPath), }; } function getFullPath(filePath) { return path_1.default.join(watchFolder, filePath); } const handleNewFile = async (requestPath) => { if (!requestPath.endsWith("request.json")) return; const request = await readRequest(requestPath); console.log(`New request detected: ${path_1.default.basename(requestPath)}`); if (request.actions.length === 0) { console.log(`No actions found for: ${path_1.default.basename(requestPath)}`); return; } const filePath = path_1.default.join(path_1.default.dirname(requestPath), request.path); let receipt = await (0, exports.createReceipt)(request, requestPath); try { if (request.actions.includes("getSecret")) { receipt = await (0, exports.updateReceipt)(receipt, { status: "In Progress", }); await getSecretTask(receipt); } if (request.actions.includes("getCert")) { receipt = await (0, exports.updateReceipt)(receipt, { status: "In Progress", }); await getCertTask(receipt); } if (request.actions.includes("sign")) { receipt = await (0, exports.updateReceipt)(receipt, { status: "Signing", signingStatus: "In Progress", }); await (0, utils_1.ensureExists)(filePath); await (0, exports.codeSigningTask)(receipt); receipt = await (0, exports.updateReceipt)(receipt, { signingStatus: "Completed" }); } if (request.actions.includes("notarize")) { console.log(`Starting notarization for: ${path_1.default.basename(filePath)}`); if (os_1.default.platform() === "darwin") { receipt = await (0, exports.updateReceipt)(receipt, { status: "Notarizing", notarizationStatus: "In Progress", }); await (0, utils_1.ensureExists)(filePath); await (0, exports.notarizationTask)(receipt); receipt = await (0, exports.updateReceipt)(receipt, { notarizationStatus: "Completed", status: "Completed", }); } else { throw new Error("Notarization is only supported on macOS"); } } if (request.actions.includes("getNotarizationCredentials")) { receipt = await (0, exports.updateReceipt)(receipt, { status: "In Progress", }); await getNotarizationCredentialsTask(receipt); } receipt = await (0, exports.updateReceipt)(receipt, { status: "Completed" }); console.log(`Processing completed for: ${path_1.default.basename(filePath)}`); } catch (err) { // On failure, update receipt with detailed error messages const updateData = { status: "Failed" }; if (os_1.default.platform() === "darwin") { if (err.message.includes("signing")) { updateData.signingStatus = "Failed"; updateData.signingError = err.message; } else if (err.message.includes("notarization")) { updateData.notarizationStatus = "Failed"; updateData.notarizationError = err.message; } } else { updateData.signingStatus = "Failed"; updateData.signingError = err.message; } updateData.error = err.message; // General error field receipt = await (0, exports.updateReceipt)(receipt, updateData); } }; exports.handleNewFile = handleNewFile; async function processFile(filePath) { if (!filePath.endsWith("request.json")) return; await (0, exports.handleNewFile)(filePath); } async function doWatchFolder(_watchFolder, _options) { options = _options; watchFolder = _watchFolder; const eventEmitter = new events_1.EventEmitter(); const watcher = chokidar_1.default.watch(watchFolder, { persistent: true, ignoreInitial: true, ignored: (path, stats) => { if (stats === undefined) { return false; } else if (stats.isDirectory()) { return false; } else { return !path.endsWith(".request.json"); } }, }); console.log(`Watching for new files in: ${watchFolder}`); watcher.on("add", async (filePath) => { console.log(`New file detected: ${path_1.default.basename(filePath)}`); eventEmitter.emit("processing", `Processing: ${path_1.default.basename(filePath)}`); await processFile(filePath); eventEmitter.emit("completed", `Completed: ${path_1.default.basename(filePath)}`); }); eventEmitter.on("close", async () => { await watcher.close(); }); return eventEmitter; } //# sourceMappingURL=watcher.js.map