Repository navigation
Expand file tree
/
Copy pathgpg.ts
More file actions
170 lines (156 loc) · 4.58 KB
/
Copy pathgpg.ts
File metadata and controls
170 lines (156 loc) · 4.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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);
}
}