Skip to content

Commit 48a418b

Browse files
authored
feat(git-node): verify tag with official keyring (#989)
1 parent c2a1e1e commit 48a418b

File tree

1 file changed

+39
-24
lines changed

1 file changed

+39
-24
lines changed

lib/promote_release.js

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import path from 'node:path';
22
import fs from 'node:fs/promises';
3+
import { tmpdir } from 'node:os';
4+
import { pipeline } from 'node:stream/promises';
35
import semver from 'semver';
46
import * as gst from 'git-secure-tag';
57

@@ -196,31 +198,44 @@ export default class ReleasePromotion extends Session {
196198

197199
async verifyTagSignature(version) {
198200
const { cli } = this;
199-
const verifyTagPattern = /gpg:[^\n]+\ngpg:\s+using \w+ key ([^\n]+)\ngpg:\s+issuer "([^"]+)"\ngpg:\s+Good signature from (?:"[^"]+"(?: \[ultimate\])?\ngpg:\s+aka )*"([^<]+) <\2>"/;
200-
const [verifyTagOutput, haystack] = await Promise.all([forceRunAsync(
201-
'git', ['--no-pager',
202-
'verify-tag',
203-
`v${version}`
204-
], { ignoreFailure: false, captureStderr: true }), fs.readFile('README.md')]);
205-
const match = verifyTagPattern.exec(verifyTagOutput);
206-
if (match == null) {
207-
cli.warn('git was not able to verify the tag:');
208-
cli.info(verifyTagOutput);
209-
} else {
210-
const [, keyID, email, name] = match;
211-
const needle = `* **${name}** <<${email}>>\n ${'`'}${keyID}${'`'}`;
212-
if (haystack.includes(needle)) {
213-
return;
201+
202+
cli.startSpinner('Downloading active releasers keyring from nodejs/release-keys...');
203+
const [keyRingStream, [GNUPGHOME, keyRingFd]] = await Promise.all([
204+
fetch('https://github.com/nodejs/release-keys/raw/HEAD/gpg-only-active-keys/pubring.kbx'),
205+
fs.mkdtemp(path.join(tmpdir(), 'ncu-'))
206+
.then(async d => [d, await fs.open(path.join(d, 'pubring.kbx'), 'w')]),
207+
]);
208+
if (!keyRingStream.ok) throw new Error('Failed to download keyring', { cause: keyRingStream });
209+
await pipeline(keyRingStream.body, keyRingFd.createWriteStream());
210+
cli.stopSpinner('Active releasers keyring stored in temp directory');
211+
212+
try {
213+
await forceRunAsync(
214+
'git', ['--no-pager',
215+
'verify-tag',
216+
`v${version}`
217+
], {
218+
ignoreFailure: false,
219+
spawnArgs: { env: { ...process.env, GNUPGHOME } },
220+
});
221+
cli.ok('git tag signature verified');
222+
} catch (cause) {
223+
cli.error('git was not able to verify the tag');
224+
cli.warn('This means that either the tag was signed with the wrong key,');
225+
cli.warn('or that nodejs/release-keys contains outdated information.');
226+
cli.warn('The release should not proceed.');
227+
if (!await cli.prompt('Do you want to proceed anyway?', { defaultAnswer: false })) {
228+
if (await cli.prompt('Do you want to delete the local tag?')) {
229+
await forceRunAsync('git', ['tag', '-d', `v${version}`]);
230+
} else {
231+
cli.info(`Run 'git tag -d v${version}' to remove the local tag.`);
232+
}
233+
throw new Error('Aborted', { cause });
214234
}
215-
cli.warn('Tag was signed with an undocumented identity/key pair!');
216-
cli.info('Expected to find the following entry in the README:');
217-
cli.info(needle);
218-
cli.info('If you are using a subkey, it might be OK.');
219-
}
220-
cli.info(`If that doesn't sound right, consider removing the tag (git tag -d v${version
221-
}), check your local config, and start the process over.`);
222-
if (!await cli.prompt('Do you want to proceed anyway?', { defaultAnswer: false })) {
223-
throw new Error('Aborted');
235+
} finally {
236+
cli.startSpinner('Cleaning up temp files');
237+
await fs.rm(GNUPGHOME, { force: true, recursive: true });
238+
cli.stopSpinner('Temp files removed');
224239
}
225240
}
226241

0 commit comments

Comments
 (0)