From 7b7cb42941eaf96830cc5544c0ba3490f01c946c Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Thu, 21 May 2026 15:14:34 -0400 Subject: [PATCH] Add public signing key --- dist/index.js | 181 +++++++++++++++++- dist/linux-signing-key.asc | 49 +++++ .../cli-installer/linux-signature.test.ts | 39 ++-- .../cli-installer/linux-signature.ts | 49 +++-- .../cli-installer/linux-signing-key.asc | 49 +++++ .../cli-installer/windows-signature.ts | 6 +- 6 files changed, 329 insertions(+), 44 deletions(-) create mode 100644 dist/linux-signing-key.asc create mode 100644 src/op-cli-installer/github-action/cli-installer/linux-signing-key.asc diff --git a/dist/index.js b/dist/index.js index 753d234..91f4ef9 100644 --- a/dist/index.js +++ b/dist/index.js @@ -35369,8 +35369,60 @@ class CliInstaller { } } +;// CONCATENATED MODULE: ./src/op-cli-installer/github-action/cli-installer/linux-signature.ts + + + + + +const execFileAsync = (0,external_util_.promisify)(external_child_process_.execFile); +// 1Password's code-signing GPG key fingerprint. See +// https://www.1password.dev/cli/verify. +const ONEPASSWORD_GPG_KEY_FINGERPRINT = "3FEF9748469ADBE15DA7CA80AC2D62742012EA22"; +// Bundled 1Password code-signing public key `linux-signing-key.asc` in +// this directory. Bundled to avoid a runtime keyserver/URL dependency. +// Source: https://downloads.1password.com/linux/keys/1password.asc +const ONEPASSWORD_GPG_PUBLIC_KEY_PATH = __nccwpck_require__.ab + "linux-signing-key.asc"; +const defaultGpgRunner = async (args) => { + const { stdout } = await execFileAsync("gpg", args); + return stdout; +}; +// Throws unless the binary at opPath carries a valid GPG signature (at +// sigPath) from the pinned 1Password key. The key is bundled with the action +const verifyLinuxSignature = async (opPath, sigPath, runGpg = defaultGpgRunner) => { + const gpgHome = external_fs_.mkdtempSync(external_path_.join(external_os_.tmpdir(), "op-verify-")); + try { + const baseArgs = ["--homedir", gpgHome, "--batch", "--no-tty"]; + // Import the bundled key into the temp keyring. + await runGpg([...baseArgs, "--import", __nccwpck_require__.ab + "linux-signing-key.asc"]); + // Confirm we imported the pinned key. + const keyringListing = await runGpg([ + ...baseArgs, + "--list-keys", + "--with-colons", + ]); + if (!keyringListing.includes(`${ONEPASSWORD_GPG_KEY_FINGERPRINT}:`)) { + throw new Error(`bundled GPG key does not match expected fingerprint ${ONEPASSWORD_GPG_KEY_FINGERPRINT}.`); + } + // Verify op.sig against op using the imported key. + await runGpg([...baseArgs, "--verify", sigPath, opPath]); + } + catch (err) { + const message = err instanceof Error ? err.message : String(err); + throw new Error(`1Password CLI signature verification failed: ${message}. ` + + "If 1Password has rotated their GPG signing key, this action needs to be updated — please file an issue at https://github.com/1Password/load-secrets-action/issues."); + } + finally { + external_fs_.rmSync(gpgHome, { recursive: true, force: true }); + } +}; + ;// CONCATENATED MODULE: ./src/op-cli-installer/github-action/cli-installer/linux.ts + + + + class LinuxInstaller extends CliInstaller { platform = "linux"; // Node.js platform identifier for Linux constructor(version) { @@ -35378,10 +35430,77 @@ class LinuxInstaller extends CliInstaller { } async installCli() { const urlBuilder = cliUrlBuilder[this.platform]; - await super.install(urlBuilder(this.version, this.arch)); + await this.install(urlBuilder(this.version, this.arch)); + } + async install(url) { + console.info(`Downloading 1Password CLI from: ${url}`); + const downloadPath = await downloadTool(url); + console.info("Installing 1Password CLI"); + const extractedPath = await extractZip(downloadPath); + info("Verifying 1Password CLI signature"); + await verifyLinuxSignature(external_path_.join(extractedPath, "op"), external_path_.join(extractedPath, "op.sig")); + info("1Password CLI signature verified"); + addPath(extractedPath); + info("1Password CLI installed"); } } +;// CONCATENATED MODULE: ./src/op-cli-installer/github-action/cli-installer/macos-signature.ts + + +const macos_signature_execFileAsync = (0,external_util_.promisify)(external_child_process_.execFile); +// See https://www.1password.dev/cli/verify. +const APPLE_DEVELOPER_TEAM_ID = "2BUA8C4S2C"; +// Append-only: old certs stay listed so historical `op` versions still verify. +// See https://www.1password.dev/cli/verify. +const ALLOWED_MACOS_SIGNING_CERT_FINGERPRINTS = [ + "CAB578061B0209FB70934DA344EF6FEBCD3279B1C074C54B0D7D555743B9D89F", + "141DD87B2B231211F1440849798007DF621DE6EB3DAB985BC964EE9704C4A1C1", +]; +const defaultPkgutilRunner = async (pkgPath) => { + const { stdout } = await macos_signature_execFileAsync("pkgutil", [ + "--check-signature", + pkgPath, + ]); + return stdout; +}; +// Returns just entry 1 (the signer cert) from the chain. +const extractSignerCertSection = (pkgutilOutput) => { + const chainStart = pkgutilOutput.indexOf("Certificate Chain:"); + if (chainStart === -1) { + return null; + } + const chainBody = pkgutilOutput.slice(chainStart); + const secondCert = /\n\s*2\.\s/.exec(chainBody); + return secondCert ? chainBody.slice(0, secondCert.index) : chainBody; +}; +const parseSignerFingerprint = (signerSection) => { + const match = /SHA256 Fingerprint:\s*\n((?:[ \t]+[0-9A-Fa-f ]+\n?)+)/.exec(signerSection); + const captured = match?.[1]; + return captured ? captured.replace(/\s+/g, "").toUpperCase() : null; +}; +// Hard-fails if the .pkg at pkgPath is not signed by AgileBits Inc. +// (2BUA8C4S2C) with a certificate on the allowlist above. Must run +// before any extraction of the .pkg contents. +const verifyMacOsPackageSignature = async (pkgPath, runPkgutil = defaultPkgutilRunner) => { + const stdout = await runPkgutil(pkgPath); + const signerSection = extractSignerCertSection(stdout); + if (!signerSection) { + throw new Error(`1Password CLI signature verification failed: could not locate certificate chain in pkgutil output.\npkgutil output:\n${stdout}`); + } + if (!signerSection.includes(`(${APPLE_DEVELOPER_TEAM_ID})`)) { + throw new Error(`1Password CLI signature verification failed: expected developer team ID ${APPLE_DEVELOPER_TEAM_ID} not found in signer certificate.\npkgutil output:\n${stdout}`); + } + const signerFingerprint = parseSignerFingerprint(signerSection); + if (!signerFingerprint) { + throw new Error(`1Password CLI signature verification failed: could not parse signer cert SHA-256 fingerprint.\npkgutil output:\n${stdout}`); + } + if (!ALLOWED_MACOS_SIGNING_CERT_FINGERPRINTS.includes(signerFingerprint)) { + throw new Error(`1Password CLI signature verification failed: signer cert SHA-256 fingerprint ${signerFingerprint} is not on the allowlist. ` + + "If 1Password has rotated their installer signing cert, this action needs to be updated — please file an issue at https://github.com/1Password/load-secrets-action/issues."); + } +}; + ;// CONCATENATED MODULE: ./src/op-cli-installer/github-action/cli-installer/macos.ts @@ -35391,7 +35510,8 @@ class LinuxInstaller extends CliInstaller { -const execFileAsync = (0,external_util_.promisify)(external_child_process_.execFile); + +const macos_execFileAsync = (0,external_util_.promisify)(external_child_process_.execFile); class MacOsInstaller extends CliInstaller { platform = "darwin"; // Node.js platform identifier for macOS constructor(version) { @@ -35407,8 +35527,11 @@ class MacOsInstaller extends CliInstaller { const pkgPath = await downloadTool(downloadUrl); const pkgWithExtension = `${pkgPath}.pkg`; external_fs_.renameSync(pkgPath, pkgWithExtension); + info("Verifying 1Password CLI signature"); + await verifyMacOsPackageSignature(pkgWithExtension); + info("1Password CLI signature verified"); const expandDir = "temp-pkg"; - await execFileAsync("pkgutil", ["--expand", pkgWithExtension, expandDir]); + await macos_execFileAsync("pkgutil", ["--expand", pkgWithExtension, expandDir]); const payloadPath = external_path_.join(expandDir, "op.pkg", "Payload"); console.info("Installing 1Password CLI"); const cliPath = await extractTar(payloadPath); @@ -35419,11 +35542,60 @@ class MacOsInstaller extends CliInstaller { } } +;// CONCATENATED MODULE: ./src/op-cli-installer/github-action/cli-installer/windows-signature.ts + + +const windows_signature_execFileAsync = (0,external_util_.promisify)(external_child_process_.execFile); +// Identifying field of 1Password's Authenticode signing cert for op.exe. +// See https://www.1password.dev/cli/verify. +const WINDOWS_SIGNER_SUBJECT_CN = "Agilebits"; +const defaultPowerShellRunner = async (script) => { + const { stdout } = await windows_signature_execFileAsync("powershell.exe", [ + "-NoProfile", + "-NonInteractive", + "-Command", + script, + ]); + return stdout; +}; +// Verifies op.exe's Authenticode signature against 1Password's signing cert. +// Throws unless the signature is cryptographically valid and the signer is AgileBits. +const verifyAuthenticodeSignature = async (opExePath, runPowerShell = defaultPowerShellRunner) => { + const escapedPath = opExePath.replace(/'/g, "''"); + const script = [ + `$sig = Get-AuthenticodeSignature -FilePath '${escapedPath}'`, + `"Status=$($sig.Status)"`, + `"Subject=$($sig.SignerCertificate.Subject)"`, + ].join("; "); + const output = await runPowerShell(script); + const outputLines = output.split("\n").map((l) => l.trim()); + const fieldValue = (prefix) => { + const matchingLine = outputLines.find((l) => l.startsWith(prefix)); + if (!matchingLine) { + return undefined; + } + return matchingLine.slice(prefix.length); + }; + // Reject unsigned or tampered binaries. + const status = fieldValue("Status="); + if (status !== "Valid") { + throw new Error(`Authenticode status is ${status ?? "unknown"}, expected Valid.\nGet-AuthenticodeSignature output:\n${output}`); + } + // Confirm the signer is AgileBits, not some other publisher. + const subject = fieldValue("Subject=") ?? ""; + if (!subject.includes(`CN=${WINDOWS_SIGNER_SUBJECT_CN}`)) { + throw new Error(`1Password CLI signature verification failed: signer Subject (${subject}) does not contain CN=${WINDOWS_SIGNER_SUBJECT_CN}. ` + + "If 1Password has rotated or renamed their signing identity, this action needs to be updated — please file an issue at https://github.com/1Password/load-secrets-action/issues."); + } +}; + ;// CONCATENATED MODULE: ./src/op-cli-installer/github-action/cli-installer/windows.ts + + class WindowsInstaller extends CliInstaller { platform = "win32"; // Node.js platform identifier for Windows constructor(version) { @@ -35442,6 +35614,9 @@ class WindowsInstaller extends CliInstaller { external_fs_.renameSync(downloadPath, zipPath); console.info("Installing 1Password CLI"); const extractedPath = await extractZip(zipPath); + info("Verifying 1Password CLI signature"); + await verifyAuthenticodeSignature(external_path_.join(extractedPath, "op.exe")); + info("1Password CLI signature verified"); addPath(extractedPath); info("1Password CLI installed"); } diff --git a/dist/linux-signing-key.asc b/dist/linux-signing-key.asc new file mode 100644 index 0000000..4bd3975 --- /dev/null +++ b/dist/linux-signing-key.asc @@ -0,0 +1,49 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +P4I7jHwe0BN3iNAG1nKbGg== +=+LeX +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/op-cli-installer/github-action/cli-installer/linux-signature.test.ts b/src/op-cli-installer/github-action/cli-installer/linux-signature.test.ts index fffb85a..15f8832 100644 --- a/src/op-cli-installer/github-action/cli-installer/linux-signature.test.ts +++ b/src/op-cli-installer/github-action/cli-installer/linux-signature.test.ts @@ -1,12 +1,13 @@ import { ONEPASSWORD_GPG_KEY_FINGERPRINT, - ONEPASSWORD_GPG_KEYSERVER, verifyLinuxSignature, } from "./linux-signature"; describe("verifyLinuxSignature", () => { const OP_PATH = "/tmp/op"; const SIG_PATH = `${OP_PATH}.sig`; + const CORRECT_FPR = `fpr:::::::::${ONEPASSWORD_GPG_KEY_FINGERPRINT}:\n`; + const WRONG_FPR = `fpr:::::::::DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF:\n`; const gpgRunner = (...responses: (string | Error)[]) => { const runner = jest.fn, [readonly string[]]>(); @@ -22,38 +23,34 @@ describe("verifyLinuxSignature", () => { const subcommandsCalled = (runner: ReturnType) => runner.mock.calls.map(([args]: [readonly string[]]) => - args.find((a) => a === "--recv-keys" || a === "--verify"), + args.find( + (a) => a === "--import" || a === "--list-keys" || a === "--verify", + ), ); - it("fetches the pinned key by fingerprint and verifies the signature", async () => { - const runner = gpgRunner("", ""); + it("imports the bundled key and verifies the signature", async () => { + const runner = gpgRunner("", CORRECT_FPR, ""); await expect( verifyLinuxSignature(OP_PATH, SIG_PATH, runner), ).resolves.toBeUndefined(); - expect(subcommandsCalled(runner)).toEqual(["--recv-keys", "--verify"]); - - const recvKeysArgs = runner.mock.calls[0]![0]; - expect(recvKeysArgs).toEqual( - expect.arrayContaining([ - "--keyserver", - ONEPASSWORD_GPG_KEYSERVER, - "--recv-keys", - ONEPASSWORD_GPG_KEY_FINGERPRINT, - ]), - ); + expect(subcommandsCalled(runner)).toEqual([ + "--import", + "--list-keys", + "--verify", + ]); }); - it("throws if recv-keys fails (e.g., wrong fingerprint or keyserver unreachable)", async () => { - const runner = gpgRunner(new Error("No data")); + it("throws and skips --verify when the imported key has the wrong fingerprint", async () => { + const runner = gpgRunner("", WRONG_FPR); await expect( verifyLinuxSignature(OP_PATH, SIG_PATH, runner), - ).rejects.toThrow(/No data/); - expect(subcommandsCalled(runner)).toEqual(["--recv-keys"]); + ).rejects.toThrow(/does not match expected/); + expect(subcommandsCalled(runner)).toEqual(["--import", "--list-keys"]); }); - it("throws if gpg --verify rejects the signature", async () => { - const runner = gpgRunner("", new Error("BAD signature")); + it("throws when gpg --verify rejects the signature", async () => { + const runner = gpgRunner("", CORRECT_FPR, new Error("BAD signature")); await expect( verifyLinuxSignature(OP_PATH, SIG_PATH, runner), ).rejects.toThrow(/BAD signature/); diff --git a/src/op-cli-installer/github-action/cli-installer/linux-signature.ts b/src/op-cli-installer/github-action/cli-installer/linux-signature.ts index 54bac82..edc3f42 100644 --- a/src/op-cli-installer/github-action/cli-installer/linux-signature.ts +++ b/src/op-cli-installer/github-action/cli-installer/linux-signature.ts @@ -6,12 +6,18 @@ import { promisify } from "util"; const execFileAsync = promisify(execFile); -// 1Password's code-signing GPG key fingerprint. Used to verify the detached -// `op.sig` inside the Linux release zip. -// See https://www.1password.dev/cli/verify. +// 1Password's code-signing GPG key fingerprint. See +// https://www.1password.dev/cli/verify. export const ONEPASSWORD_GPG_KEY_FINGERPRINT = "3FEF9748469ADBE15DA7CA80AC2D62742012EA22"; -export const ONEPASSWORD_GPG_KEYSERVER = "keyserver.ubuntu.com"; + +// Bundled 1Password code-signing public key `linux-signing-key.asc` in +// this directory. Bundled to avoid a runtime keyserver/URL dependency. +// Source: https://downloads.1password.com/linux/keys/1password.asc +const ONEPASSWORD_GPG_PUBLIC_KEY_PATH = path.join( + __dirname, + "linux-signing-key.asc", +); const defaultGpgRunner = async (args: readonly string[]): Promise => { const { stdout } = await execFileAsync("gpg", args); @@ -19,10 +25,7 @@ const defaultGpgRunner = async (args: readonly string[]): Promise => { }; // Throws unless the binary at opPath carries a valid GPG signature (at -// sigPath) from the pinned 1Password key. -// -// gpg --keyserver keyserver.ubuntu.com --recv-keys -// gpg --verify +// sigPath) from the pinned 1Password key. The key is bundled with the action export const verifyLinuxSignature = async ( opPath: string, sigPath: string, @@ -32,17 +35,29 @@ export const verifyLinuxSignature = async ( try { const baseArgs = ["--homedir", gpgHome, "--batch", "--no-tty"]; - // Fetch the 1Password public key by fingerprint. gpg only accepts a - // key whose fingerprint matches the requested value. - await runGpg([ - ...baseArgs, - "--keyserver", - ONEPASSWORD_GPG_KEYSERVER, - "--recv-keys", - ONEPASSWORD_GPG_KEY_FINGERPRINT, - ]); + // Import the bundled key into the temp keyring. + await runGpg([...baseArgs, "--import", ONEPASSWORD_GPG_PUBLIC_KEY_PATH]); + // Confirm we imported the pinned key. + const keyringListing = await runGpg([ + ...baseArgs, + "--list-keys", + "--with-colons", + ]); + if (!keyringListing.includes(`${ONEPASSWORD_GPG_KEY_FINGERPRINT}:`)) { + throw new Error( + `bundled GPG key does not match expected fingerprint ${ONEPASSWORD_GPG_KEY_FINGERPRINT}.`, + ); + } + + // Verify op.sig against op using the imported key. await runGpg([...baseArgs, "--verify", sigPath, opPath]); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + throw new Error( + `1Password CLI signature verification failed: ${message}. ` + + "If 1Password has rotated their GPG signing key, this action needs to be updated — please file an issue at https://github.com/1Password/load-secrets-action/issues.", + ); } finally { fs.rmSync(gpgHome, { recursive: true, force: true }); } diff --git a/src/op-cli-installer/github-action/cli-installer/linux-signing-key.asc b/src/op-cli-installer/github-action/cli-installer/linux-signing-key.asc new file mode 100644 index 0000000..4bd3975 --- /dev/null +++ b/src/op-cli-installer/github-action/cli-installer/linux-signing-key.asc @@ -0,0 +1,49 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +P4I7jHwe0BN3iNAG1nKbGg== +=+LeX +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/op-cli-installer/github-action/cli-installer/windows-signature.ts b/src/op-cli-installer/github-action/cli-installer/windows-signature.ts index f83b8d5..d4c82f5 100644 --- a/src/op-cli-installer/github-action/cli-installer/windows-signature.ts +++ b/src/op-cli-installer/github-action/cli-installer/windows-signature.ts @@ -18,8 +18,7 @@ const defaultPowerShellRunner = async (script: string): Promise => { }; // Verifies op.exe's Authenticode signature against 1Password's signing cert. -// Throws unless the signature is cryptographically valid and the signer is -// AgileBits. +// Throws unless the signature is cryptographically valid and the signer is AgileBits. export const verifyAuthenticodeSignature = async ( opExePath: string, runPowerShell: (script: string) => Promise = defaultPowerShellRunner, @@ -54,7 +53,8 @@ export const verifyAuthenticodeSignature = async ( const subject = fieldValue("Subject=") ?? ""; if (!subject.includes(`CN=${WINDOWS_SIGNER_SUBJECT_CN}`)) { throw new Error( - `signer Subject (${subject}) does not contain CN=${WINDOWS_SIGNER_SUBJECT_CN}.`, + `1Password CLI signature verification failed: signer Subject (${subject}) does not contain CN=${WINDOWS_SIGNER_SUBJECT_CN}. ` + + "If 1Password has rotated or renamed their signing identity, this action needs to be updated — please file an issue at https://github.com/1Password/load-secrets-action/issues.", ); } };