mirror of
https://github.com/crazy-max/ghaction-import-gpg.git
synced 2024-11-25 06:21:03 -05:00
Set passphrase only for the fingerprint being used (#123)
* If fingerprint input is provided it sets only the passphrase for that key * Update README with how to use subkeys example
This commit is contained in:
parent
343bb932e5
commit
2724049ae2
7 changed files with 197 additions and 3 deletions
45
README.md
45
README.md
|
@ -120,6 +120,51 @@ jobs:
|
|||
git push
|
||||
```
|
||||
|
||||
### Use a subkey
|
||||
|
||||
With the input `fingerprint`, you can specify which one of the subkeys in a GPG key you want to use for signing.
|
||||
|
||||
```yaml
|
||||
name: import-gpg
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: master
|
||||
|
||||
jobs:
|
||||
import-gpg:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Import GPG key
|
||||
id: import_gpg
|
||||
uses: crazy-max/ghaction-import-gpg@v4
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.PASSPHRASE }}
|
||||
fingerprint: "C17D11ADF199F12A30A0910F1F80449BE0B08CB8"
|
||||
-
|
||||
name: List keys
|
||||
run: gpg -K
|
||||
```
|
||||
|
||||
For example, given this GPG key with a signing subkey:
|
||||
|
||||
```s
|
||||
pub ed25519 2021-09-24 [C]
|
||||
87F257B89CE462100BEC0FFE6071D218380FDCC8
|
||||
Keygrip = F5C3ABFAAB36B427FD98C4EDD0387E08EA1E8092
|
||||
uid [ unknown] Joe Bar <joe@bar.foo>
|
||||
sub ed25519 2021-09-24 [S]
|
||||
C17D11ADF199F12A30A0910F1F80449BE0B08CB8
|
||||
Keygrip = DEE0FC98F441519CA5DE5D79773CB29009695FEB
|
||||
```
|
||||
|
||||
You can use the subkey with signing capability whose fingerprint is `C17D11ADF199F12A30A0910F1F80449BE0B08CB8`.
|
||||
|
||||
## Customizing
|
||||
|
||||
### inputs
|
||||
|
|
8
__tests__/fixtures/test-key-gpg-output.txt
Normal file
8
__tests__/fixtures/test-key-gpg-output.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
tru::1:1645715610:1661267528:3:1:5
|
||||
pub:-:4096:1:7D851EB72D73BDA0:1588448672:::-:::scESC::::::23::0:
|
||||
fpr:::::::::27571A53B86AF0C799B38BA77D851EB72D73BDA0:
|
||||
grp:::::::::3E2D1142AA59E08E16B7E2C64BA6DDC773B1A627:
|
||||
uid:-::::1588448672::C1B25336F8F0F0F22BAF57137BE493ADEDA8CCAA::Joe Tester <joe@foo.bar>::::::::::0:
|
||||
sub:-:4096:1:D523BD50DD70B0BA:1588448672::::::e::::::23:
|
||||
fpr:::::::::5A282E1460C0BC419615D34DD523BD50DD70B0BA:
|
||||
grp:::::::::BA83FC8947213477F28ADC019F6564A956456163:
|
8
__tests__/fixtures/test-subkey-gpg-output.txt
Normal file
8
__tests__/fixtures/test-subkey-gpg-output.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
tru::1:1645715610:1661267528:3:1:5
|
||||
pub:-:256:22:6071D218380FDCC8:1632521434:::-:::cSC:::::ed25519:::0:
|
||||
fpr:::::::::87F257B89CE462100BEC0FFE6071D218380FDCC8:
|
||||
grp:::::::::F5C3ABFAAB36B427FD98C4EDD0387E08EA1E8092:
|
||||
uid:-::::1632521434::019F22ECD701BC0F6AFE686ABD2B010B812B828E::Joe Bar <joe@bar.foo>::::::::::0:
|
||||
sub:-:256:22:1F80449BE0B08CB8:1632521539::::::s:::::ed25519::
|
||||
fpr:::::::::C17D11ADF199F12A30A0910F1F80449BE0B08CB8:
|
||||
grp:::::::::DEE0FC98F441519CA5DE5D79773CB29009695FEB:
|
|
@ -1,5 +1,6 @@
|
|||
import * as fs from 'fs';
|
||||
import * as gpg from '../src/gpg';
|
||||
import {parseKeygripFromGpgColonsOutput} from '../src/gpg';
|
||||
|
||||
const userInfos = [
|
||||
{
|
||||
|
@ -20,6 +21,7 @@ const userInfos = [
|
|||
email: 'joe@foo.bar',
|
||||
keyID: '7D851EB72D73BDA0',
|
||||
fingerprint: '27571A53B86AF0C799B38BA77D851EB72D73BDA0',
|
||||
fingerprints: ['27571A53B86AF0C799B38BA77D851EB72D73BDA0', '5A282E1460C0BC419615D34DD523BD50DD70B0BA'],
|
||||
keygrips: ['3E2D1142AA59E08E16B7E2C64BA6DDC773B1A627', 'BA83FC8947213477F28ADC019F6564A956456163']
|
||||
},
|
||||
{
|
||||
|
@ -40,6 +42,7 @@ const userInfos = [
|
|||
email: 'joe@bar.foo',
|
||||
keyID: '6071D218380FDCC8',
|
||||
fingerprint: 'C17D11ADF199F12A30A0910F1F80449BE0B08CB8',
|
||||
fingerprints: ['87F257B89CE462100BEC0FFE6071D218380FDCC8', 'C17D11ADF199F12A30A0910F1F80449BE0B08CB8'],
|
||||
keygrips: ['F5C3ABFAAB36B427FD98C4EDD0387E08EA1E8092', 'DEE0FC98F441519CA5DE5D79773CB29009695FEB']
|
||||
}
|
||||
];
|
||||
|
@ -101,6 +104,19 @@ for (let userInfo of userInfos) {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getKeygrip', () => {
|
||||
it('returns the keygrip for a given fingerprint', async () => {
|
||||
await gpg.importKey(userInfo.pgp);
|
||||
for (let [i, fingerprint] of userInfo.fingerprints.entries()) {
|
||||
await gpg.getKeygrip(fingerprint).then(keygrip => {
|
||||
console.log(`Fingerprint: ${fingerprint}; Index: ${i}; Keygrip: ${keygrip}`);
|
||||
expect(keygrip.length).toEqual(userInfo.keygrips[i].length);
|
||||
expect(keygrip).toEqual(userInfo.keygrips[i]);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('presetPassphrase', () => {
|
||||
it('presets passphrase', async () => {
|
||||
await gpg.importKey(userInfo.pgp);
|
||||
|
@ -128,3 +144,29 @@ describe('killAgent', () => {
|
|||
await gpg.killAgent();
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseKeygripFromGpgColonsOutput', () => {
|
||||
it('returns the keygrip of a given fingerprint from a GPG command output using the option: --with-colons', async () => {
|
||||
const outputUsingTestKey = fs.readFileSync('__tests__/fixtures/test-key-gpg-output.txt', {
|
||||
encoding: 'utf8',
|
||||
flag: 'r'
|
||||
});
|
||||
|
||||
const keygripPrimaryTestKey = parseKeygripFromGpgColonsOutput(outputUsingTestKey, '27571A53B86AF0C799B38BA77D851EB72D73BDA0');
|
||||
expect(keygripPrimaryTestKey).toBe('3E2D1142AA59E08E16B7E2C64BA6DDC773B1A627');
|
||||
|
||||
const keygripSubkeyTestKey = parseKeygripFromGpgColonsOutput(outputUsingTestKey, '5A282E1460C0BC419615D34DD523BD50DD70B0BA');
|
||||
expect(keygripSubkeyTestKey).toBe('BA83FC8947213477F28ADC019F6564A956456163');
|
||||
|
||||
const outputUsingTestSubkey = fs.readFileSync('__tests__/fixtures/test-subkey-gpg-output.txt', {
|
||||
encoding: 'utf8',
|
||||
flag: 'r'
|
||||
});
|
||||
|
||||
const keygripPrimaryTestSubkey = parseKeygripFromGpgColonsOutput(outputUsingTestSubkey, '87F257B89CE462100BEC0FFE6071D218380FDCC8');
|
||||
expect(keygripPrimaryTestSubkey).toBe('F5C3ABFAAB36B427FD98C4EDD0387E08EA1E8092');
|
||||
|
||||
const keygripSubkeyTestSubkey = parseKeygripFromGpgColonsOutput(outputUsingTestSubkey, 'C17D11ADF199F12A30A0910F1F80449BE0B08CB8');
|
||||
expect(keygripSubkeyTestSubkey).toBe('DEE0FC98F441519CA5DE5D79773CB29009695FEB');
|
||||
});
|
||||
});
|
||||
|
|
45
dist/index.js
generated
vendored
45
dist/index.js
generated
vendored
|
@ -164,7 +164,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.killAgent = exports.deleteKey = exports.presetPassphrase = exports.configureAgent = exports.getKeygrips = exports.importKey = exports.getDirs = exports.getVersion = exports.agentConfig = void 0;
|
||||
exports.killAgent = exports.deleteKey = exports.presetPassphrase = exports.configureAgent = exports.getKeygrip = exports.parseKeygripFromGpgColonsOutput = exports.getKeygrips = exports.importKey = exports.getDirs = exports.getVersion = exports.agentConfig = void 0;
|
||||
const exec = __importStar(__webpack_require__(1514));
|
||||
const fs = __importStar(__webpack_require__(5747));
|
||||
const path = __importStar(__webpack_require__(5622));
|
||||
|
@ -304,6 +304,34 @@ exports.getKeygrips = (fingerprint) => __awaiter(void 0, void 0, void 0, functio
|
|||
return keygrips;
|
||||
});
|
||||
});
|
||||
exports.parseKeygripFromGpgColonsOutput = (output, fingerprint) => {
|
||||
let keygrip = '';
|
||||
let fingerPrintFound = false;
|
||||
const lines = output.replace(/\r/g, '').trim().split(/\n/g);
|
||||
for (let line of lines) {
|
||||
if (line.startsWith(`fpr:`) && line.includes(`:${fingerprint}:`)) {
|
||||
// We reach the record with the matching fingerprint.
|
||||
// The next keygrip record is the keygrip for this fingerprint.
|
||||
fingerPrintFound = true;
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith('grp:') && fingerPrintFound) {
|
||||
keygrip = line.replace(/(grp|:)/g, '').trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return keygrip;
|
||||
};
|
||||
exports.getKeygrip = (fingerprint) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
return yield exec
|
||||
.getExecOutput('gpg', ['--batch', '--with-colons', '--with-keygrip', '--list-secret-keys', fingerprint], {
|
||||
ignoreReturnCode: true,
|
||||
silent: true
|
||||
})
|
||||
.then(res => {
|
||||
return exports.parseKeygripFromGpgColonsOutput(res.stdout, fingerprint);
|
||||
});
|
||||
});
|
||||
exports.configureAgent = (config) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
const gpgAgentConf = path.join(yield getGnupgHome(), 'gpg-agent.conf');
|
||||
yield fs.writeFile(gpgAgentConf, config, function (err) {
|
||||
|
@ -424,7 +452,8 @@ function run() {
|
|||
core.info(stdout);
|
||||
});
|
||||
}));
|
||||
if (inputs.passphrase) {
|
||||
if (inputs.passphrase && !inputs.fingerprint) {
|
||||
// Set the passphrase for all subkeys
|
||||
core.info('Configuring GnuPG agent');
|
||||
yield gpg.configureAgent(gpg.agentConfig);
|
||||
yield core.group(`Getting keygrips`, () => __awaiter(this, void 0, void 0, function* () {
|
||||
|
@ -436,6 +465,18 @@ function run() {
|
|||
}
|
||||
}));
|
||||
}
|
||||
if (inputs.passphrase && inputs.fingerprint) {
|
||||
// Set the passphrase only for the subkey specified in the input `fingerprint`
|
||||
core.info('Configuring GnuPG agent');
|
||||
yield gpg.configureAgent(gpg.agentConfig);
|
||||
yield core.group(`Getting keygrip for fingerprint`, () => __awaiter(this, void 0, void 0, function* () {
|
||||
const keygrip = yield gpg.getKeygrip(fingerprint);
|
||||
core.info(`Presetting passphrase for key ${fingerprint} with keygrip ${keygrip}`);
|
||||
yield gpg.presetPassphrase(keygrip, inputs.passphrase).then(stdout => {
|
||||
core.debug(stdout);
|
||||
});
|
||||
}));
|
||||
}
|
||||
yield core.group(`Setting outputs`, () => __awaiter(this, void 0, void 0, function* () {
|
||||
core.info(`fingerprint=${fingerprint}`);
|
||||
context.setOutput('fingerprint', fingerprint);
|
||||
|
|
33
src/gpg.ts
33
src/gpg.ts
|
@ -159,6 +159,39 @@ export const getKeygrips = async (fingerprint: string): Promise<Array<string>> =
|
|||
});
|
||||
};
|
||||
|
||||
export const parseKeygripFromGpgColonsOutput = (output: string, fingerprint: string): string => {
|
||||
let keygrip = '';
|
||||
let fingerPrintFound = false;
|
||||
const lines = output.replace(/\r/g, '').trim().split(/\n/g);
|
||||
|
||||
for (let line of lines) {
|
||||
if (line.startsWith(`fpr:`) && line.includes(`:${fingerprint}:`)) {
|
||||
// We reach the record with the matching fingerprint.
|
||||
// The next keygrip record is the keygrip for this fingerprint.
|
||||
fingerPrintFound = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith('grp:') && fingerPrintFound) {
|
||||
keygrip = line.replace(/(grp|:)/g, '').trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return keygrip;
|
||||
};
|
||||
|
||||
export const getKeygrip = async (fingerprint: string): Promise<string> => {
|
||||
return await exec
|
||||
.getExecOutput('gpg', ['--batch', '--with-colons', '--with-keygrip', '--list-secret-keys', fingerprint], {
|
||||
ignoreReturnCode: true,
|
||||
silent: true
|
||||
})
|
||||
.then(res => {
|
||||
return parseKeygripFromGpgColonsOutput(res.stdout, fingerprint);
|
||||
});
|
||||
};
|
||||
|
||||
export const configureAgent = async (config: string): Promise<void> => {
|
||||
const gpgAgentConf = path.join(await getGnupgHome(), 'gpg-agent.conf');
|
||||
await fs.writeFile(gpgAgentConf, config, function (err) {
|
||||
|
|
19
src/main.ts
19
src/main.ts
|
@ -48,7 +48,9 @@ async function run(): Promise<void> {
|
|||
});
|
||||
});
|
||||
|
||||
if (inputs.passphrase) {
|
||||
if (inputs.passphrase && !inputs.fingerprint) {
|
||||
// Set the passphrase for all subkeys
|
||||
|
||||
core.info('Configuring GnuPG agent');
|
||||
await gpg.configureAgent(gpg.agentConfig);
|
||||
|
||||
|
@ -62,6 +64,21 @@ async function run(): Promise<void> {
|
|||
});
|
||||
}
|
||||
|
||||
if (inputs.passphrase && inputs.fingerprint) {
|
||||
// Set the passphrase only for the subkey specified in the input `fingerprint`
|
||||
|
||||
core.info('Configuring GnuPG agent');
|
||||
await gpg.configureAgent(gpg.agentConfig);
|
||||
|
||||
await core.group(`Getting keygrip for fingerprint`, async () => {
|
||||
const keygrip = await gpg.getKeygrip(fingerprint);
|
||||
core.info(`Presetting passphrase for key ${fingerprint} with keygrip ${keygrip}`);
|
||||
await gpg.presetPassphrase(keygrip, inputs.passphrase).then(stdout => {
|
||||
core.debug(stdout);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
await core.group(`Setting outputs`, async () => {
|
||||
core.info(`fingerprint=${fingerprint}`);
|
||||
context.setOutput('fingerprint', fingerprint);
|
||||
|
|
Loading…
Reference in a new issue