diff --git a/__tests__/gpg.test.ts b/__tests__/gpg.test.ts new file mode 100644 index 00000000..2ad659de --- /dev/null +++ b/__tests__/gpg.test.ts @@ -0,0 +1,47 @@ +import * as exec from "@actions/exec"; +import { setupKeys, verify, refreshKeys } from "../src/gpg"; + +jest.mock("@actions/exec"); + +const mockExec = exec.exec as jest.Mock; + +describe("gpg", () => { + afterEach(() => { + mockExec.mockClear(); + }); + + it("uses the first responding keyserver in the pool", async () => { + mockExec.mockImplementation(() => Promise.resolve(0)); + await refreshKeys(); + expect(mockExec).toBeCalledTimes(1); + }); + + it("uses the next keyserver in the pool if the previous fails", async () => { + const failingServers = 3; + let testedServers = 0; + + mockExec.mockImplementation(() => { + testedServers++; + if (testedServers >= failingServers) { + return Promise.resolve(0); + } else { + return Promise.resolve(1); + } + }); + + await refreshKeys(); + expect(mockExec).toBeCalledTimes(3); + }); + + it("throws an error if all servers in the pool fails", async () => { + mockExec.mockImplementation(() => Promise.resolve(1)); + + try { + await refreshKeys(); + } catch (e) { + expect(e).toEqual( + new Error("Failed to refresh keys from any server in the pool.") + ); + } + }); +}); diff --git a/src/gpg.ts b/src/gpg.ts new file mode 100644 index 00000000..4a3fa641 --- /dev/null +++ b/src/gpg.ts @@ -0,0 +1,53 @@ +import { exec } from "@actions/exec"; +import * as core from "@actions/core"; +import * as toolCache from "@actions/tool-cache"; + +export async function setupKeys() { + core.debug("Fetching verification keys"); + let path = await toolCache.downloadTool( + "https://swift.org/keys/all-keys.asc" + ); + + core.debug("Importing verification keys"); + await exec(`gpg --import "${path}"`); + + core.debug("Refreshing keys"); + await refreshKeys(); +} + +export async function verify(signaturePath: string, packagePath: string) { + core.debug("Verifying signature"); + await exec("gpg", ["--verify", signaturePath, packagePath]); +} + +export async function refreshKeys() { + const pool = [ + "hkp://pool.sks-keyservers.net", + "ha.pool.sks-keyservers.net", + "keyserver.ubuntu.com", + "hkp://keyserver.ubuntu.com", + "pgp.mit.edu", + ]; + + for (const server of pool) { + core.debug(`Refreshing keys from ${server}`); + if (await refreshKeysFromServer(server)) { + core.debug(`Refresh successful`); + return; + } + core.debug(`Refresh failed`); + } + + throw new Error("Failed to refresh keys from any server in the pool."); +} + +function refreshKeysFromServer(server: string): Promise { + return exec(`gpg --keyserver ${server} --refresh-keys Swift`) + .then((code) => code === 0) + .catch((error) => { + core.warning( + `An error occurred when trying to refresh keys from ${server}: ${error}` + ); + return false; + }); +} diff --git a/src/linux-install.ts b/src/linux-install.ts index 11b162d0..e57bf185 100644 --- a/src/linux-install.ts +++ b/src/linux-install.ts @@ -5,6 +5,7 @@ import * as core from "@actions/core"; import * as toolCache from "@actions/tool-cache"; import { System } from "./os"; import { swiftPackage, Package } from "./swift-versions"; +import { setupKeys, verify } from "./gpg"; export async function install(version: string, system: System) { if (os.platform() !== "linux") { @@ -66,21 +67,3 @@ async function unpack( core.debug("Package cached"); return cachedPath; } - -async function setupKeys() { - core.debug("Fetching verification keys"); - let path = await toolCache.downloadTool( - "https://swift.org/keys/all-keys.asc" - ); - core.debug("Importing verification keys"); - await exec(`gpg --import "${path}"`); - core.debug("Refreshing keys"); - await exec( - "gpg --keyserver hkp://pool.sks-keyservers.net --refresh-keys Swift" - ); -} - -async function verify(signaturePath: string, packagePath: string) { - core.debug("Verifying signature"); - await exec("gpg", ["--verify", signaturePath, packagePath]); -}