mirror of
https://github.com/actions/setup-java.git
synced 2026-10-15 15:14:53 +00:00
a78ec39f12
* Initial plan * Stop GPG agent before verification home cleanup Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>
171 lines
4.6 KiB
TypeScript
171 lines
4.6 KiB
TypeScript
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import {randomUUID} from 'crypto';
|
|
import * as io from '@actions/io';
|
|
import * as exec from '@actions/exec';
|
|
import * as tc from '@actions/tool-cache';
|
|
import * as util from './util.js';
|
|
import type {ExecOptions} from '@actions/exec';
|
|
import type {SignatureVerificationKey} from './distributions/base-models.js';
|
|
|
|
export const GPG_HOME_PREFIX = 'setup-java-gpg-';
|
|
const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-';
|
|
|
|
export async function isGpgAvailable(): Promise<boolean> {
|
|
return Boolean(await io.which('gpg', false));
|
|
}
|
|
|
|
// Convert a Windows path (D:\a\_temp\...) to a POSIX path (/d/a/_temp/...).
|
|
// The Git-bundled GPG on Windows (MSYS2-based) uses POSIX path conventions
|
|
// internally. Passing Windows paths with backslashes can cause fatal GPG errors
|
|
// (exit code 2), so all paths passed to GPG must be in POSIX format on Windows.
|
|
export function toGpgPath(p: string): string {
|
|
if (process.platform !== 'win32') return p;
|
|
return p
|
|
.replace(/\\/g, '/')
|
|
.replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`);
|
|
}
|
|
|
|
function createGpgHome(
|
|
prefix: string,
|
|
tempDir: string = util.getTempDir()
|
|
): string {
|
|
const gpgHome = fs.mkdtempSync(path.join(tempDir, prefix));
|
|
if (process.platform !== 'win32') {
|
|
fs.chmodSync(gpgHome, 0o700);
|
|
}
|
|
return gpgHome;
|
|
}
|
|
|
|
export async function importKey(privateKey: string): Promise<string> {
|
|
const gpgHome = createGpgHome(GPG_HOME_PREFIX);
|
|
const privateKeyFile = path.join(gpgHome, `private-key-${randomUUID()}.asc`);
|
|
|
|
try {
|
|
fs.writeFileSync(privateKeyFile, privateKey, {
|
|
encoding: 'utf-8',
|
|
flag: 'wx',
|
|
mode: 0o600
|
|
});
|
|
|
|
try {
|
|
await exec.exec(
|
|
'gpg',
|
|
[
|
|
'--homedir',
|
|
toGpgPath(gpgHome),
|
|
'--batch',
|
|
'--import',
|
|
toGpgPath(privateKeyFile)
|
|
],
|
|
{silent: true}
|
|
);
|
|
} finally {
|
|
fs.rmSync(privateKeyFile, {force: true});
|
|
}
|
|
|
|
return gpgHome;
|
|
} catch (error) {
|
|
await removeGpgHome(gpgHome);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function removeGpgHome(gpgHome: string): Promise<void> {
|
|
if (!gpgHome) {
|
|
return;
|
|
}
|
|
|
|
const resolvedGpgHome = path.resolve(gpgHome);
|
|
const resolvedTempDir = path.resolve(util.getTempDir());
|
|
if (
|
|
path.dirname(resolvedGpgHome) !== resolvedTempDir ||
|
|
!path.basename(resolvedGpgHome).startsWith(GPG_HOME_PREFIX)
|
|
) {
|
|
throw new Error(`Refusing to remove unexpected GPG home: ${gpgHome}`);
|
|
}
|
|
|
|
if (!fs.existsSync(resolvedGpgHome)) {
|
|
return;
|
|
}
|
|
|
|
await stopGpgAgent(resolvedGpgHome);
|
|
await io.rmRF(resolvedGpgHome);
|
|
}
|
|
|
|
async function stopGpgAgent(gpgHome: string): Promise<void> {
|
|
try {
|
|
await exec.exec(
|
|
'gpgconf',
|
|
['--homedir', toGpgPath(gpgHome), '--kill', 'gpg-agent'],
|
|
{silent: true, ignoreReturnCode: true}
|
|
);
|
|
} catch {
|
|
// gpgconf may be unavailable, but directory removal must still be attempted.
|
|
}
|
|
}
|
|
|
|
export async function verifyPackageSignature(
|
|
archivePath: string,
|
|
signatureUrl: string,
|
|
publicKeyContent: SignatureVerificationKey
|
|
) {
|
|
const signaturePath = await tc.downloadTool(signatureUrl);
|
|
let gpgHome: string;
|
|
try {
|
|
// Both RUNNER_TEMP and TMPDIR can exceed macOS's 104-byte agent socket limit.
|
|
const tempDir = process.platform === 'darwin' ? '/tmp' : util.getTempDir();
|
|
gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX, tempDir);
|
|
} catch (error) {
|
|
try {
|
|
await io.rmRF(signaturePath);
|
|
} catch {
|
|
// ignore cleanup failures
|
|
}
|
|
throw new Error(
|
|
`Failed to create temporary GPG home directory for signature verification: ${
|
|
(error as Error).message
|
|
}`,
|
|
{cause: error}
|
|
);
|
|
}
|
|
try {
|
|
const publicKeys = Array.isArray(publicKeyContent)
|
|
? publicKeyContent
|
|
: [publicKeyContent];
|
|
const publicKeyFiles = publicKeys.map((publicKey, index) => {
|
|
const publicKeyFile = path.join(gpgHome, `public-key-${index}.asc`);
|
|
fs.writeFileSync(publicKeyFile, publicKey, {encoding: 'utf-8'});
|
|
return toGpgPath(publicKeyFile);
|
|
});
|
|
const options: ExecOptions = {silent: true};
|
|
await exec.exec(
|
|
'gpg',
|
|
[
|
|
'--homedir',
|
|
toGpgPath(gpgHome),
|
|
'--batch',
|
|
'--import',
|
|
...publicKeyFiles
|
|
],
|
|
options
|
|
);
|
|
await exec.exec(
|
|
'gpg',
|
|
[
|
|
'--homedir',
|
|
toGpgPath(gpgHome),
|
|
'--batch',
|
|
'--verify',
|
|
toGpgPath(signaturePath),
|
|
toGpgPath(archivePath)
|
|
],
|
|
options
|
|
);
|
|
} finally {
|
|
await stopGpgAgent(gpgHome);
|
|
await io.rmRF(signaturePath);
|
|
await io.rmRF(gpgHome);
|
|
}
|
|
}
|