Files
setup-java/src/gpg.ts
T

151 lines
3.8 KiB
TypeScript
Raw Normal View History

2020-05-02 04:33:15 -07:00
import * as fs from 'fs';
import * as path from 'path';
import {randomUUID} from 'crypto';
2020-05-02 04:33:15 -07:00
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 {ExecOptions} from '@actions/exec';
2020-05-02 04:33:15 -07:00
export const GPG_HOME_PREFIX = 'setup-java-gpg-';
const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-';
2020-05-02 04:33:15 -07:00
// 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): string {
const gpgHome = fs.mkdtempSync(path.join(util.getTempDir(), prefix));
if (process.platform !== 'win32') {
fs.chmodSync(gpgHome, 0o700);
}
return gpgHome;
2020-05-02 04:33:15 -07:00
}
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;
}
try {
await exec.exec(
'gpgconf',
['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'],
{silent: true, ignoreReturnCode: true}
);
} catch {
// gpgconf may be unavailable, but directory removal must still be attempted.
}
await io.rmRF(resolvedGpgHome);
2020-05-02 04:33:15 -07:00
}
export async function verifyPackageSignature(
archivePath: string,
signatureUrl: string,
publicKeyContent: string
) {
const signaturePath = await tc.downloadTool(signatureUrl);
let gpgHome: string;
try {
gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX);
} 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 publicKeyFile = path.join(gpgHome, 'public-key.asc');
fs.writeFileSync(publicKeyFile, publicKeyContent, {encoding: 'utf-8'});
const options: ExecOptions = {silent: true};
await exec.exec(
'gpg',
[
'--homedir',
toGpgPath(gpgHome),
'--batch',
'--import',
toGpgPath(publicKeyFile)
],
options
);
await exec.exec(
'gpg',
[
'--homedir',
toGpgPath(gpgHome),
'--batch',
'--verify',
toGpgPath(signaturePath),
toGpgPath(archivePath)
],
options
);
} finally {
await io.rmRF(signaturePath);
await io.rmRF(gpgHome);
}
}