Skip to content

Commit 03014ea

Browse files
committed
parse output line-by-line
1 parent d7c4318 commit 03014ea

File tree

2 files changed

+99
-12
lines changed

2 files changed

+99
-12
lines changed

lib/gpg.js

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ global.fetch = require('node-fetch'); // needed for opengpg lookups
1515
const pgp = new HKP('https://keyserver.pgp.com');
1616
const mit = new HKP('https://pgp.mit.edu');
1717

18-
const FINGERPRINT_REGEXP = /[0-9A-F]{40}/m;
19-
2018
async function exe() /* : Promise<string> */ {
2119
try {
2220
return await promisify(which)('gpg2');
@@ -47,27 +45,64 @@ async function getUsernames(keyId /* : string */) {
4745
);
4846
}
4947

50-
async function listKnownPublicKeys() {
48+
async function listKnownPublicKeys() /* : Promise<string[]> */ {
5149
const { all, stdout } = await execa(await exe(), [
5250
'--list-keys',
5351
'--fingerprint',
5452
'--textmode',
5553
]);
56-
debug('listKnownPublicKeys(): gpg --list-keys --fingerprint --textmode\n%s', all);
57-
58-
return stdout
59-
.split('\n\n')
60-
.map(entry => {
61-
const [keyId] = entry.match(FINGERPRINT_REGEXP) || [];
62-
return keyId || '';
63-
})
64-
.filter(keyId => !!keyId);
54+
debug(
55+
'listKnownPublicKeys(): gpg --list-keys --fingerprint --textmode\n%s',
56+
all,
57+
);
58+
59+
return parseListing(stdout).map(entry => entry.fingerprint);
6560
}
6661

6762
async function lookupPublicKey(keyId /* : string */) {
6863
return (await mit.lookup({ keyId })) || (await pgp.lookup({ keyId }));
6964
}
7065

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+
71106
async function parsePublicKeys(
72107
asciiArmor /* :? string */,
73108
) /* : Promise<ParsedKey> */ {
@@ -87,4 +122,5 @@ module.exports = {
87122
listKnownPublicKeys,
88123
lookupPublicKey,
89124
parsePublicKeys,
125+
parseListing,
90126
};

lib/gpg.test.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const {
1515
getUsernames,
1616
listKnownPublicKeys,
1717
lookupPublicKey,
18+
parseListing,
1819
parsePublicKeys,
1920
} = require('./gpg');
2021

@@ -114,6 +115,56 @@ describe('gpg', () => {
114115
});
115116
});
116117

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+
117168
describe('parsePublicKeys()', () => {
118169
it('parses ASCII Armor for Alice or Bob', async () => {
119170
const keyIds = await listKnownPublicKeys();

0 commit comments

Comments
 (0)