diff --git a/build/azure-pipelines/darwin/codesign.ts b/build/azure-pipelines/darwin/codesign.ts
index 263f96cd2ff..2cc826d60c8 100644
--- a/build/azure-pipelines/darwin/codesign.ts
+++ b/build/azure-pipelines/darwin/codesign.ts
@@ -10,27 +10,54 @@ async function main() {
const arch = e('VSCODE_ARCH');
const esrpCliDLLPath = e('EsrpCliDllPath');
const pipelineWorkspace = e('PIPELINE_WORKSPACE');
+ const buildSourcesDirectory = e('BUILD_SOURCESDIRECTORY');
- const folder = `${pipelineWorkspace}/vscode_client_darwin_${arch}_archive`;
+ const clientFolder = `${pipelineWorkspace}/vscode_client_darwin_${arch}_archive`;
const dmgFolder = `${pipelineWorkspace}/vscode_client_darwin_${arch}_dmg`;
- const glob = `VSCode-darwin-${arch}.zip`;
+ const clientGlob = `VSCode-darwin-${arch}.zip`;
const dmgGlob = `VSCode-darwin-${arch}.dmg`;
- // Codesign
- const archiveCodeSignTask = spawnCodesignProcess(esrpCliDLLPath, 'sign-darwin', folder, glob);
- const dmgCodeSignTask = spawnCodesignProcess(esrpCliDLLPath, 'sign-darwin', dmgFolder, dmgGlob);
- printBanner('Codesign Archive');
- await streamProcessOutputAndCheckResult('Codesign Archive', archiveCodeSignTask);
- printBanner('Codesign DMG');
- await streamProcessOutputAndCheckResult('Codesign DMG', dmgCodeSignTask);
+ const serverFolder = `${buildSourcesDirectory}/.build/darwin/server`;
+ const serverGlob = `vscode-server-darwin-${arch}.zip`;
+ const webGlob = `vscode-server-darwin-${arch}-web.zip`;
+
+ // Start codesign processes in parallel
+ const codeSignClientTask = spawnCodesignProcess(esrpCliDLLPath, 'sign-darwin', clientFolder, clientGlob);
+ const codeSignDmgTask = spawnCodesignProcess(esrpCliDLLPath, 'sign-darwin', dmgFolder, dmgGlob);
+ const codeSignServerTask = spawnCodesignProcess(esrpCliDLLPath, 'sign-darwin', serverFolder, serverGlob);
+ const codeSignWebTask = spawnCodesignProcess(esrpCliDLLPath, 'sign-darwin', serverFolder, webGlob);
+
+ // Await codesign results
+ printBanner('Codesign client');
+ await streamProcessOutputAndCheckResult('Codesign client', codeSignClientTask);
+
+ printBanner('Codesign DMG');
+ await streamProcessOutputAndCheckResult('Codesign DMG', codeSignDmgTask);
+
+ printBanner('Codesign server');
+ await streamProcessOutputAndCheckResult('Codesign server', codeSignServerTask);
+
+ printBanner('Codesign web');
+ await streamProcessOutputAndCheckResult('Codesign web', codeSignWebTask);
+
+ // Start notarize processes in parallel (after codesigning is complete)
+ const notarizeClientTask = spawnCodesignProcess(esrpCliDLLPath, 'notarize-darwin', clientFolder, clientGlob);
+ const notarizeDmgTask = spawnCodesignProcess(esrpCliDLLPath, 'notarize-darwin', dmgFolder, dmgGlob);
+ const notarizeServerTask = spawnCodesignProcess(esrpCliDLLPath, 'notarize-darwin', serverFolder, serverGlob);
+ const notarizeWebTask = spawnCodesignProcess(esrpCliDLLPath, 'notarize-darwin', serverFolder, webGlob);
+
+ // Await notarize results
+ printBanner('Notarize client');
+ await streamProcessOutputAndCheckResult('Notarize client', notarizeClientTask);
- // Notarize
- const archiveNotarizeTask = spawnCodesignProcess(esrpCliDLLPath, 'notarize-darwin', folder, glob);
- const dmgNotarizeTask = spawnCodesignProcess(esrpCliDLLPath, 'notarize-darwin', dmgFolder, dmgGlob);
- printBanner('Notarize Archive');
- await streamProcessOutputAndCheckResult('Notarize Archive', archiveNotarizeTask);
printBanner('Notarize DMG');
- await streamProcessOutputAndCheckResult('Notarize DMG', dmgNotarizeTask);
+ await streamProcessOutputAndCheckResult('Notarize DMG', notarizeDmgTask);
+
+ printBanner('Notarize server');
+ await streamProcessOutputAndCheckResult('Notarize server', notarizeServerTask);
+
+ printBanner('Notarize web');
+ await streamProcessOutputAndCheckResult('Notarize web', notarizeWebTask);
}
main().then(() => {
diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml
index 37282e92146..339fe6209ae 100644
--- a/build/azure-pipelines/darwin/product-build-darwin.yml
+++ b/build/azure-pipelines/darwin/product-build-darwin.yml
@@ -57,14 +57,14 @@ jobs:
sbomPackageVersion: $(Build.SourceVersion)
- output: pipelineArtifact
targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_server_darwin_$(VSCODE_ARCH)_archive/vscode-server-darwin-$(VSCODE_ARCH).zip
- artifactName: vscode_server_darwin_$(VSCODE_ARCH)_archive-unsigned
+ artifactName: vscode_server_darwin_$(VSCODE_ARCH)_archive
displayName: Publish server archive
sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH)
sbomPackageName: "VS Code macOS $(VSCODE_ARCH) Server"
sbomPackageVersion: $(Build.SourceVersion)
- output: pipelineArtifact
targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_web_darwin_$(VSCODE_ARCH)_archive/vscode-server-darwin-$(VSCODE_ARCH)-web.zip
- artifactName: vscode_web_darwin_$(VSCODE_ARCH)_archive-unsigned
+ artifactName: vscode_web_darwin_$(VSCODE_ARCH)_archive
displayName: Publish web server archive
sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH)-web
sbomPackageName: "VS Code macOS $(VSCODE_ARCH) Web"
diff --git a/build/azure-pipelines/darwin/server-entitlements.plist b/build/azure-pipelines/darwin/server-entitlements.plist
new file mode 100644
index 00000000000..4efe1ce508f
--- /dev/null
+++ b/build/azure-pipelines/darwin/server-entitlements.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ com.apple.security.cs.allow-jit
+
+
+
diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml
index e20ef8deb01..7604d54909f 100644
--- a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml
+++ b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml
@@ -134,10 +134,6 @@ steps:
set -e
npm run gulp vscode-reh-darwin-$(VSCODE_ARCH)-min-ci
mv ../vscode-reh-darwin-$(VSCODE_ARCH) ../vscode-server-darwin-$(VSCODE_ARCH) # TODO@joaomoreno
- ARCHIVE_PATH=".build/darwin/server/vscode-server-darwin-$(VSCODE_ARCH).zip"
- mkdir -p $(dirname $ARCHIVE_PATH)
- (cd .. && zip -Xry $(Build.SourcesDirectory)/$ARCHIVE_PATH vscode-server-darwin-$(VSCODE_ARCH))
- echo "##vso[task.setvariable variable=SERVER_PATH]$ARCHIVE_PATH"
env:
GITHUB_TOKEN: "$(github-distro-mixin-password)"
displayName: Build server
@@ -146,10 +142,6 @@ steps:
set -e
npm run gulp vscode-reh-web-darwin-$(VSCODE_ARCH)-min-ci
mv ../vscode-reh-web-darwin-$(VSCODE_ARCH) ../vscode-server-darwin-$(VSCODE_ARCH)-web # TODO@joaomoreno
- ARCHIVE_PATH=".build/darwin/server/vscode-server-darwin-$(VSCODE_ARCH)-web.zip"
- mkdir -p $(dirname $ARCHIVE_PATH)
- (cd .. && zip -Xry $(Build.SourcesDirectory)/$ARCHIVE_PATH vscode-server-darwin-$(VSCODE_ARCH)-web)
- echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH"
env:
GITHUB_TOKEN: "$(github-distro-mixin-password)"
displayName: Build server (web)
@@ -224,6 +216,13 @@ steps:
DEBUG=electron-osx-sign* node build/darwin/sign.ts $(agent.builddirectory)
displayName: Set Hardened Entitlements
+ - script: |
+ set -e
+ export CODESIGN_IDENTITY=$(security find-identity -v -p codesigning $(agent.tempdirectory)/buildagent.keychain | grep -oEi "([0-9A-F]{40})" | head -n 1)
+ node build/darwin/sign-server.ts $(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH)
+ node build/darwin/sign-server.ts $(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH)-web
+ displayName: Sign server binaries
+
- script: |
set -e
# Needed for https://github.com/dmgbuild/dmgbuild/blob/main/src/dmgbuild/badge.py
@@ -245,6 +244,22 @@ steps:
condition: eq(variables['BUILT_CLIENT'], 'true')
displayName: Re-package client after entitlement
+ - script: |
+ set -e
+ ARCHIVE_PATH=".build/darwin/server/vscode-server-darwin-$(VSCODE_ARCH).zip"
+ mkdir -p $(dirname $ARCHIVE_PATH)
+ (cd .. && zip -Xry $(Build.SourcesDirectory)/$ARCHIVE_PATH vscode-server-darwin-$(VSCODE_ARCH))
+ echo "##vso[task.setvariable variable=SERVER_PATH]$ARCHIVE_PATH"
+ displayName: Package server
+
+ - script: |
+ set -e
+ ARCHIVE_PATH=".build/darwin/server/vscode-server-darwin-$(VSCODE_ARCH)-web.zip"
+ mkdir -p $(dirname $ARCHIVE_PATH)
+ (cd .. && zip -Xry $(Build.SourcesDirectory)/$ARCHIVE_PATH vscode-server-darwin-$(VSCODE_ARCH)-web)
+ echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH"
+ displayName: Package server (web)
+
- task: UseDotNet@2
inputs:
version: 6.x
@@ -273,6 +288,7 @@ steps:
env:
EsrpCliDllPath: $(EsrpCliDllPath)
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ BUILD_SOURCESDIRECTORY: $(Build.SourcesDirectory)
displayName: ✍️ Codesign & Notarize
- ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}:
diff --git a/build/darwin/sign-server.ts b/build/darwin/sign-server.ts
new file mode 100644
index 00000000000..308258b5e52
--- /dev/null
+++ b/build/darwin/sign-server.ts
@@ -0,0 +1,82 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { spawn } from '@malept/cross-spawn-promise';
+import fs from 'fs';
+import path from 'path';
+
+const MACHO_MAGIC_NUMBERS = new Set([
+ 0xFEEDFACE, // MH_MAGIC (32-bit)
+ 0xCEFAEDFE, // MH_CIGAM (32-bit, byte-swapped)
+ 0xFEEDFACF, // MH_MAGIC_64 (64-bit)
+ 0xCFFAEDFE, // MH_CIGAM_64 (64-bit, byte-swapped)
+ 0xCAFEBABE, // FAT_MAGIC (universal binary)
+ 0xBEBAFECA, // FAT_CIGAM (universal binary, byte-swapped)
+]);
+
+function isMachOBinary(filePath: string): boolean {
+ try {
+ let fd: number | undefined;
+ try {
+ fd = fs.openSync(filePath, 'r');
+ const buffer = Buffer.alloc(4);
+ fs.readSync(fd, buffer, 0, 4, 0);
+ const magic = buffer.readUInt32BE(0);
+ return MACHO_MAGIC_NUMBERS.has(magic);
+ } finally {
+ if (fd !== undefined) {
+ fs.closeSync(fd);
+ }
+ }
+ } catch {
+ return false;
+ }
+}
+
+async function main(serverDir: string): Promise {
+ if (!serverDir || !fs.existsSync(serverDir)) {
+ throw new Error('Server directory argument is required');
+ }
+
+ const tempDir = process.env['AGENT_TEMPDIRECTORY'];
+ if (!tempDir) {
+ throw new Error('$AGENT_TEMPDIRECTORY not set');
+ }
+
+ const identity = process.env['CODESIGN_IDENTITY'];
+ if (!identity) {
+ throw new Error('$CODESIGN_IDENTITY not set');
+ }
+
+ const keychain = path.join(tempDir, 'buildagent.keychain');
+ const baseDir = path.dirname(import.meta.dirname);
+ const entitlementsPath = path.join(baseDir, 'azure-pipelines', 'darwin', 'server-entitlements.plist');
+
+ console.log(`Signing Mach-O binaries in: ${serverDir}`);
+ for (const entry of fs.readdirSync(serverDir, { withFileTypes: true, recursive: true })) {
+ if (entry.isFile()) {
+ const filePath = path.join(entry.parentPath, entry.name);
+ if (isMachOBinary(filePath)) {
+ console.log(`Signing: ${filePath}`);
+ await spawn('codesign', [
+ '--sign', identity,
+ '--keychain', keychain,
+ '--options', 'runtime',
+ '--timestamp',
+ '--force',
+ '--entitlements', entitlementsPath,
+ filePath
+ ]);
+ }
+ }
+ }
+}
+
+if (import.meta.main) {
+ main(process.argv[2]).catch(err => {
+ console.error(err);
+ process.exit(1);
+ });
+}