2020-05-03 15:42:55 -04:00
|
|
|
import * as fs from 'fs';
|
|
|
|
import * as path from 'path';
|
|
|
|
import * as os from 'os';
|
2020-05-04 10:17:14 -04:00
|
|
|
import * as exec from './exec';
|
2020-05-04 13:55:53 -04:00
|
|
|
import which from 'which';
|
2020-05-04 10:17:14 -04:00
|
|
|
|
2020-05-04 10:32:30 -04:00
|
|
|
export const agentConfig = `default-cache-ttl 7200
|
2020-05-04 10:17:14 -04:00
|
|
|
max-cache-ttl 31536000
|
|
|
|
allow-preset-passphrase`;
|
2020-05-03 14:46:05 -04:00
|
|
|
|
|
|
|
export interface Version {
|
|
|
|
gnupg: string;
|
|
|
|
libgcrypt: string;
|
|
|
|
}
|
|
|
|
|
2020-05-04 10:17:14 -04:00
|
|
|
export interface Dirs {
|
|
|
|
libdir: string;
|
2020-05-04 10:40:21 -04:00
|
|
|
libexecdir: string;
|
2020-05-04 10:17:14 -04:00
|
|
|
datadir: string;
|
|
|
|
homedir: string;
|
|
|
|
}
|
2020-05-03 14:46:05 -04:00
|
|
|
|
2020-05-04 13:17:01 -04:00
|
|
|
const getGpgPresetPassphrasePath = async (): Promise<string> => {
|
2020-05-04 13:55:53 -04:00
|
|
|
let gpgPresetPassphrasePath: string;
|
|
|
|
|
|
|
|
gpgPresetPassphrasePath = await which('gpg-preset-passphrase').then(resolvedPath => {
|
|
|
|
return resolvedPath;
|
|
|
|
});
|
|
|
|
if (gpgPresetPassphrasePath != '') {
|
|
|
|
return gpgPresetPassphrasePath;
|
|
|
|
}
|
2020-05-04 13:39:17 -04:00
|
|
|
|
2020-05-04 13:55:53 -04:00
|
|
|
const {libexecdir: libexecdir} = await getDirs();
|
|
|
|
gpgPresetPassphrasePath = path.join(libexecdir, 'gpg-preset-passphrase');
|
2020-05-04 13:39:17 -04:00
|
|
|
if (await fs.existsSync(gpgPresetPassphrasePath)) {
|
|
|
|
return gpgPresetPassphrasePath;
|
|
|
|
}
|
|
|
|
|
|
|
|
gpgPresetPassphrasePath = path.join(process.env.HOMEDRIVE || '', libexecdir, 'gpg-preset-passphrase.exe');
|
|
|
|
if (await fs.existsSync(gpgPresetPassphrasePath)) {
|
|
|
|
return gpgPresetPassphrasePath;
|
2020-05-04 13:17:01 -04:00
|
|
|
}
|
2020-05-04 13:39:17 -04:00
|
|
|
|
|
|
|
gpgPresetPassphrasePath = path.join(`C:\\Program Files\\Git`, libexecdir, 'gpg-preset-passphrase.exe');
|
|
|
|
if (await fs.existsSync(gpgPresetPassphrasePath)) {
|
|
|
|
return gpgPresetPassphrasePath;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error('gpg-preset-passphrase not found');
|
2020-05-04 13:17:01 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
const getGnupgHome = async (): Promise<string> => {
|
|
|
|
if (process.env.GNUPGHOME) {
|
|
|
|
return process.env.GNUPGHOME;
|
|
|
|
}
|
|
|
|
let homedir: string = path.join(process.env.HOME || '', '.gnupg');
|
|
|
|
if (os.platform() == 'win32' && !process.env.HOME) {
|
|
|
|
homedir = path.join(process.env.USERPROFILE || '', '.gnupg');
|
|
|
|
}
|
|
|
|
return homedir;
|
|
|
|
};
|
|
|
|
|
2020-05-03 14:46:05 -04:00
|
|
|
export const getVersion = async (): Promise<Version> => {
|
2020-05-04 10:17:14 -04:00
|
|
|
return await exec.exec('gpg', ['--version'], true).then(res => {
|
|
|
|
if (res.stderr != '') {
|
|
|
|
throw new Error(res.stderr);
|
|
|
|
}
|
2020-05-03 14:46:05 -04:00
|
|
|
|
2020-05-04 10:17:14 -04:00
|
|
|
let gnupgVersion: string = '';
|
|
|
|
let libgcryptVersion: string = '';
|
|
|
|
|
|
|
|
for (let line of res.stdout.replace(/\r/g, '').trim().split(/\n/g)) {
|
2020-05-03 14:46:05 -04:00
|
|
|
if (line.startsWith('gpg (GnuPG) ')) {
|
|
|
|
gnupgVersion = line.substr('gpg (GnuPG) '.length).trim();
|
|
|
|
} else if (line.startsWith('gpg (GnuPG/MacGPG2) ')) {
|
|
|
|
gnupgVersion = line.substr('gpg (GnuPG/MacGPG2) '.length).trim();
|
|
|
|
} else if (line.startsWith('libgcrypt ')) {
|
|
|
|
libgcryptVersion = line.substr('libgcrypt '.length).trim();
|
|
|
|
}
|
|
|
|
}
|
2020-05-04 10:17:14 -04:00
|
|
|
|
|
|
|
return {
|
|
|
|
gnupg: gnupgVersion,
|
|
|
|
libgcrypt: libgcryptVersion
|
|
|
|
};
|
2020-05-03 14:46:05 -04:00
|
|
|
});
|
2020-05-04 10:17:14 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
export const getDirs = async (): Promise<Dirs> => {
|
|
|
|
return await exec.exec('gpgconf', ['--list-dirs'], true).then(res => {
|
|
|
|
if (res.stderr != '' && !res.success) {
|
|
|
|
throw new Error(res.stderr);
|
|
|
|
}
|
2020-05-03 14:46:05 -04:00
|
|
|
|
2020-05-04 10:17:14 -04:00
|
|
|
let libdir: string = '';
|
2020-05-04 10:40:21 -04:00
|
|
|
let libexecdir: string = '';
|
2020-05-04 10:17:14 -04:00
|
|
|
let datadir: string = '';
|
|
|
|
let homedir: string = '';
|
|
|
|
|
|
|
|
for (let line of res.stdout.replace(/\r/g, '').trim().split(/\n/g)) {
|
|
|
|
if (line.startsWith('libdir:')) {
|
|
|
|
libdir = line.substr('libdir:'.length).replace('%3a', ':').trim();
|
2020-05-04 10:40:21 -04:00
|
|
|
} else if (line.startsWith('libexecdir:')) {
|
|
|
|
libexecdir = line.substr('libexecdir:'.length).replace('%3a', ':').trim();
|
2020-05-04 10:17:14 -04:00
|
|
|
} else if (line.startsWith('datadir:')) {
|
|
|
|
datadir = line.substr('datadir:'.length).replace('%3a', ':').trim();
|
|
|
|
} else if (line.startsWith('homedir:')) {
|
|
|
|
homedir = line.substr('homedir:'.length).replace('%3a', ':').trim();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
2020-05-04 13:17:01 -04:00
|
|
|
libdir: libdir,
|
|
|
|
libexecdir: libexecdir,
|
|
|
|
datadir: datadir,
|
|
|
|
homedir: homedir
|
2020-05-04 10:17:14 -04:00
|
|
|
};
|
|
|
|
});
|
2020-05-03 14:46:05 -04:00
|
|
|
};
|
|
|
|
|
2020-05-04 10:17:14 -04:00
|
|
|
export const importKey = async (armoredText: string): Promise<string> => {
|
2020-05-03 15:42:55 -04:00
|
|
|
const keyFolder: string = fs.mkdtempSync(path.join(os.tmpdir(), 'ghaction-import-gpg-'));
|
|
|
|
const keyPath: string = `${keyFolder}/key.pgp`;
|
|
|
|
fs.writeFileSync(keyPath, armoredText, {mode: 0o600});
|
2020-05-03 14:46:05 -04:00
|
|
|
|
2020-05-04 10:17:14 -04:00
|
|
|
return await exec
|
|
|
|
.exec('gpg', ['--import', '--batch', '--yes', keyPath], true)
|
|
|
|
.then(res => {
|
|
|
|
if (res.stderr != '' && !res.success) {
|
|
|
|
throw new Error(res.stderr);
|
|
|
|
}
|
|
|
|
if (res.stderr != '') {
|
|
|
|
return res.stderr.trim();
|
|
|
|
}
|
|
|
|
return res.stdout.trim();
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
fs.unlinkSync(keyPath);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
export const getKeygrip = async (fingerprint: string): Promise<string> => {
|
|
|
|
return await exec
|
|
|
|
.exec('gpg', ['--batch', '--with-colons', '--with-keygrip', '--list-secret-keys', fingerprint], true)
|
|
|
|
.then(res => {
|
|
|
|
if (res.stderr != '' && !res.success) {
|
|
|
|
throw new Error(res.stderr);
|
|
|
|
}
|
|
|
|
let keygrip: string = '';
|
|
|
|
for (let line of res.stdout.replace(/\r/g, '').trim().split(/\n/g)) {
|
|
|
|
if (line.startsWith('grp')) {
|
|
|
|
keygrip = line.replace(/(grp|:)/g, '').trim();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return keygrip;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
export const configureAgent = async (config: string): Promise<void> => {
|
2020-05-04 13:17:01 -04:00
|
|
|
const gpgAgentConf = path.join(await getGnupgHome(), 'gpg-agent.conf');
|
2020-05-04 10:17:14 -04:00
|
|
|
await fs.writeFile(gpgAgentConf, config, function (err) {
|
|
|
|
if (err) throw err;
|
|
|
|
});
|
2020-05-04 10:32:30 -04:00
|
|
|
|
2020-05-04 10:17:14 -04:00
|
|
|
await exec.exec(`gpg-connect-agent "RELOADAGENT" /bye`, [], true).then(res => {
|
|
|
|
if (res.stderr != '' && !res.success) {
|
|
|
|
throw new Error(res.stderr);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
export const presetPassphrase = async (keygrip: string, passphrase: string): Promise<string> => {
|
|
|
|
await exec
|
2020-05-04 13:17:01 -04:00
|
|
|
.exec(
|
2020-05-04 13:55:53 -04:00
|
|
|
`"${await getGpgPresetPassphrasePath()}" --verbose --preset --passphrase "${passphrase}" ${keygrip}`,
|
|
|
|
[],
|
2020-05-04 13:17:01 -04:00
|
|
|
true
|
|
|
|
)
|
2020-05-04 10:17:14 -04:00
|
|
|
.then(res => {
|
|
|
|
if (res.stderr != '' && !res.success) {
|
|
|
|
throw new Error(res.stderr);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return await exec.exec(`gpg-connect-agent "KEYINFO ${keygrip}" /bye`, [], true).then(res => {
|
|
|
|
if (res.stderr != '' && !res.success) {
|
|
|
|
throw new Error(res.stderr);
|
|
|
|
}
|
|
|
|
for (let line of res.stdout.replace(/\r/g, '').trim().split(/\n/g)) {
|
|
|
|
if (line.startsWith('ERR')) {
|
|
|
|
throw new Error(line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res.stdout.trim();
|
2020-05-03 14:46:05 -04:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
export const deleteKey = async (fingerprint: string): Promise<void> => {
|
2020-05-04 10:17:14 -04:00
|
|
|
await exec.exec('gpg', ['--batch', '--yes', '--delete-secret-keys', fingerprint], true).then(res => {
|
|
|
|
if (res.stderr != '' && !res.success) {
|
|
|
|
throw new Error(res.stderr);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
await exec.exec('gpg', ['--batch', '--yes', '--delete-keys', fingerprint], true).then(res => {
|
|
|
|
if (res.stderr != '' && !res.success) {
|
|
|
|
throw new Error(res.stderr);
|
|
|
|
}
|
|
|
|
});
|
2020-05-03 14:46:05 -04:00
|
|
|
};
|