Skip to content

Commit e795927

Browse files
authored
Merge pull request #63 from jokeyrhyme/fix-gpg-tests
ci: install `gnupg2` for testing
2 parents a6f9994 + 03014ea commit e795927

File tree

5 files changed

+221
-24
lines changed

5 files changed

+221
-24
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ node_js:
55
env:
66
global:
77
- CXX=g++-4.8
8+
- DEBUG=gpg
89
addons:
910
apt:
1011
sources:
1112
- ubuntu-toolchain-r-test
1213
packages:
1314
- g++-4.8
15+
- gnupg2
1416
install:
1517
- npm install --global npm
1618
- npm install

lib/gpg.js

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
/* @flow */
22
'use strict';
33

4+
const { promisify } = require('util');
5+
6+
const debug = require('debug')('gpg');
47
const execa = require('execa');
8+
const which = require('which');
59

610
const { HKP, key } = require('openpgp');
711
global.fetch = require('node-fetch'); // needed for opengpg lookups
@@ -11,10 +15,21 @@ global.fetch = require('node-fetch'); // needed for opengpg lookups
1115
const pgp = new HKP('https://keyserver.pgp.com');
1216
const mit = new HKP('https://pgp.mit.edu');
1317

14-
const FINGERPRINT_REGEXP = /[0-9A-F]{40}/m;
18+
async function exe() /* : Promise<string> */ {
19+
try {
20+
return await promisify(which)('gpg2');
21+
} catch (err) {
22+
return await promisify(which)('gpg');
23+
}
24+
}
1525

1626
async function getLocalPublicKey(keyId /* : string */) {
17-
const { stdout } = await execa('gpg', ['--export', '--armor', keyId]);
27+
const { all, stdout } = await execa(await exe(), [
28+
'--export',
29+
'--armor',
30+
keyId,
31+
]);
32+
debug('getLocalPublicKey(): gpg --export --armor %s\n%s', keyId, all);
1833
if (!stdout.includes('-----BEGIN PGP PUBLIC KEY BLOCK-----')) {
1934
return null;
2035
}
@@ -30,22 +45,64 @@ async function getUsernames(keyId /* : string */) {
3045
);
3146
}
3247

33-
async function listKnownPublicKeys() {
34-
const { stdout } = await execa('gpg', ['--list-public-keys', '--textmode']);
48+
async function listKnownPublicKeys() /* : Promise<string[]> */ {
49+
const { all, stdout } = await execa(await exe(), [
50+
'--list-keys',
51+
'--fingerprint',
52+
'--textmode',
53+
]);
54+
debug(
55+
'listKnownPublicKeys(): gpg --list-keys --fingerprint --textmode\n%s',
56+
all,
57+
);
3558

36-
return stdout
37-
.split('\n\n')
38-
.map(entry => {
39-
const [keyId] = entry.match(FINGERPRINT_REGEXP) || [];
40-
return keyId || '';
41-
})
42-
.filter(keyId => !!keyId);
59+
return parseListing(stdout).map(entry => entry.fingerprint);
4360
}
4461

4562
async function lookupPublicKey(keyId /* : string */) {
4663
return (await mit.lookup({ keyId })) || (await pgp.lookup({ keyId }));
4764
}
4865

66+
/* ::
67+
type ListingEntry = {
68+
email: string;
69+
fingerprint: string;
70+
};
71+
*/
72+
function parseListing(listing /* : string */) /* : ListingEntry[] */ {
73+
const entries = [];
74+
let entry = { email: '', fingerprint: '' };
75+
76+
const saveOldStartNew = () => {
77+
if (entry && entry.fingerprint) {
78+
entries.push(entry);
79+
}
80+
entry = { email: '', fingerprint: '' };
81+
};
82+
83+
for (let line of listing.split('\n')) {
84+
line = line.trim();
85+
if (line.match(/^(pub|sec)\s/)) {
86+
// we are at the start of a new entry
87+
saveOldStartNew();
88+
}
89+
let noWhitespace = line.replace(/\s/g, '');
90+
if (line.startsWith('Key fingerprint = ')) {
91+
noWhitespace = noWhitespace.replace('Keyfingerprint=', '');
92+
}
93+
if (noWhitespace.match(/^[0-9A-F]{40}$/)) {
94+
entry.fingerprint = noWhitespace;
95+
}
96+
const [, email] = line.match(/<([^\s@]+@[^\s@]+)>/) || [];
97+
if (email) {
98+
entry.email = email;
99+
}
100+
}
101+
saveOldStartNew();
102+
debug('parseListing(): %O', entries);
103+
return entries;
104+
}
105+
49106
async function parsePublicKeys(
50107
asciiArmor /* :? string */,
51108
) /* : Promise<ParsedKey> */ {
@@ -59,9 +116,11 @@ async function parsePublicKeys(
59116
}
60117

61118
module.exports = {
119+
exe,
62120
getLocalPublicKey,
63121
getUsernames,
64122
listKnownPublicKeys,
65123
lookupPublicKey,
66124
parsePublicKeys,
125+
parseListing,
67126
};

lib/gpg.test.js

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,32 @@ const execa = require('execa');
1010
const rimraf = require('rimraf');
1111

1212
const {
13+
exe,
1314
getLocalPublicKey,
1415
getUsernames,
1516
listKnownPublicKeys,
1617
lookupPublicKey,
18+
parseListing,
1719
parsePublicKeys,
1820
} = require('./gpg');
1921

2022
describe('gpg', () => {
2123
let tempDir;
2224

23-
afterEach(async () => {
25+
afterAll(async () => {
2426
if (tempDir) {
2527
await promisify(rimraf)(tempDir);
2628
}
2729
delete process.env.GNUPGHOME;
2830
});
2931

30-
beforeEach(async () => {
32+
beforeAll(async () => {
3133
tempDir = await promisify(mkdtemp)(
3234
joinPath(tmpdir(), 'git-crypt-test--gpg-'),
3335
);
36+
process.env.DEBUG = 'gpg';
3437
process.env.GNUPGHOME = tempDir;
35-
await execa('gpg', ['--generate-key', '--batch'], {
38+
await execa(await exe(), ['--gen-key', '--batch'], {
3639
input: [
3740
'Key-Type: default',
3841
'Subkey-Type: default',
@@ -42,7 +45,7 @@ describe('gpg', () => {
4245
'%commit',
4346
].join('\n'),
4447
});
45-
await execa('gpg', ['--generate-key', '--batch'], {
48+
await execa(await exe(), ['--gen-key', '--batch'], {
4649
input: [
4750
'Key-Type: default',
4851
'Subkey-Type: default',
@@ -52,6 +55,10 @@ describe('gpg', () => {
5255
'%commit',
5356
].join('\n'),
5457
});
58+
59+
// eslint-disable-next-line no-console
60+
console.log('listing keys in test keyring:');
61+
await execa(await exe(), ['-K'], { stderr: 'inherit', stdout: 'inherit' });
5562
});
5663

5764
describe('getLocalPublicKey()', () => {
@@ -93,6 +100,7 @@ describe('gpg', () => {
93100

94101
describe('lookupPublicKey()', () => {
95102
it('finds the public key for Hashicorp', async () => {
103+
jest.setTimeout(30 * 1000);
96104
try {
97105
const got = await lookupPublicKey(
98106
'91A6E7F85D05C65630BEF18951852D87348FFC4C',
@@ -107,6 +115,56 @@ describe('gpg', () => {
107115
});
108116
});
109117

118+
describe('parseListing()', () => {
119+
it('parses output from gpg 2.1.11', () => {
120+
const listing = `/tmp/git-crypt-test--gpg-KHPaVv/pubring.kbx
121+
-------------------------------------------
122+
pub rsa2048/589EF98F 2019-07-21 [SC]
123+
Key fingerprint = 1D5F 7BFE 54E5 C2E9 78AF 88FF 6BC3 D9B3 589E F98F
124+
uid [ultimate] Alice <alice@example.local>
125+
sub rsa2048/73AF806B 2019-07-21 [E]
126+
pub rsa2048/CC38658E 2019-07-21 [SC]
127+
Key fingerprint = 9A21 8AA0 BBB0 E64E B6AA 7731 7282 FA72 CC38 658E
128+
uid [ultimate] Bob <bob@example.local>
129+
sub rsa2048/568A60C0 2019-07-21 [E]
130+
`;
131+
const got = parseListing(listing);
132+
expect(got).toContainEqual({
133+
email: 'alice@example.local',
134+
fingerprint: '1D5F7BFE54E5C2E978AF88FF6BC3D9B3589EF98F',
135+
});
136+
expect(got).toContainEqual({
137+
email: 'bob@example.local',
138+
fingerprint: '9A218AA0BBB0E64EB6AA77317282FA72CC38658E',
139+
});
140+
});
141+
142+
it('parses output from gpg 2.2.16', () => {
143+
const listing = `/tmp/git-crypt-test--gpg-zr7rYT/pubring.kbx
144+
-------------------------------------------
145+
sec rsa2048 2019-07-21 [SC]
146+
8E82A43990918AE0BC5AF076438FBEFBB18785ED
147+
uid [ultimate] Alice <alice@example.local>
148+
ssb rsa2048 2019-07-21 [E]
149+
150+
sec rsa2048 2019-07-21 [SC]
151+
1CC8653C86167701289AB6D398402AEC113CE2BB
152+
uid [ultimate] Bob <bob@example.local>
153+
ssb rsa2048 2019-07-21 [E]
154+
155+
`;
156+
const got = parseListing(listing);
157+
expect(got).toContainEqual({
158+
email: 'alice@example.local',
159+
fingerprint: '8E82A43990918AE0BC5AF076438FBEFBB18785ED',
160+
});
161+
expect(got).toContainEqual({
162+
email: 'bob@example.local',
163+
fingerprint: '1CC8653C86167701289AB6D398402AEC113CE2BB',
164+
});
165+
});
166+
});
167+
110168
describe('parsePublicKeys()', () => {
111169
it('parses ASCII Armor for Alice or Bob', async () => {
112170
const keyIds = await listKnownPublicKeys();

0 commit comments

Comments
 (0)