From e44f71482963eb94aaaed564ec5457e33da21b1c Mon Sep 17 00:00:00 2001 From: Dylan Audius Date: Fri, 14 Aug 2026 16:31:42 -0700 Subject: [PATCH] fix(desktop): rebuild the ASC API key PEM before notarizing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit notarytool rejects a malformed .p8 with a bare `invalidPEMDocument`, which says nothing about what was wrong — the Mac build hit exactly this once the signing cert was fixed and it finally got far enough to notarize. Secret stores routinely mangle PEMs, either flattening the newlines to literal \n or stripping them entirely, and the previous code trusted the stored formatting as long as the BEGIN marker was present. Pull out the base64 body and rebuild the PEM instead, so all the usual shapes converge on a valid key, and throw a message that names the actual problem when the secret isn't a PKCS#8 key at all. Verified against six mangling modes (raw, literal \n, no newlines, CRLF, and base64 of the first two) plus a garbage input, checking each result parses with `openssl pkey -check`. Co-Authored-By: Claude Opus 5 --- packages/web/scripts/dist.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/web/scripts/dist.js b/packages/web/scripts/dist.js index e4a45efad9b..bf0d288c7ad 100644 --- a/packages/web/scripts/dist.js +++ b/packages/web/scripts/dist.js @@ -41,14 +41,38 @@ program * Writes the App Store Connect API key (.p8) to a temp file, since notarytool * takes a path rather than the key contents. The secret may hold either the raw * PEM contents or a base64 encoding of them. + * + * notarytool parses the .p8 strictly and rejects anything malformed with a bare + * `invalidPEMDocument`, which says nothing about what was wrong. Secret stores + * mangle PEMs in two routine ways — flattening the newlines to literal `\n`, or + * stripping them entirely — so rather than trusting the stored formatting, pull + * out the base64 body and rebuild the PEM from scratch. * @returns {string} path to the written .p8 file */ const writeApiKeyFile = () => { const rawKey = process.env.APP_STORE_CONNECT_API_KEY_KEY - const key = rawKey.includes('BEGIN PRIVATE KEY') + const decoded = rawKey.includes('BEGIN PRIVATE KEY') ? rawKey : Buffer.from(rawKey, 'base64').toString('utf8') + const body = decoded + .replace(/-----(BEGIN|END) PRIVATE KEY-----/g, '') + .replace(/\\r\\n|\\n/g, '') + .replace(/\s/g, '') + if (!body || !/^[A-Za-z0-9+/]+={0,2}$/.test(body)) { + throw new Error( + 'APP_STORE_CONNECT_API_KEY_KEY is not a PKCS#8 private key. Expected the ' + + 'contents of the .p8 downloaded from App Store Connect (or a base64 ' + + 'encoding of it).' + ) + } + const key = [ + '-----BEGIN PRIVATE KEY-----', + ...body.match(/.{1,64}/g), + '-----END PRIVATE KEY-----', + '' + ].join('\n') + const keyDir = fs.mkdtempSync(path.join(os.tmpdir(), 'asc-api-key-')) const keyPath = path.join( keyDir,