From 05fea506c1ff86b557dc05295b33a662daba4b16 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Sat, 6 May 2023 18:20:11 +0200 Subject: [PATCH] input to set private key trust level --- .github/workflows/ci.yml | 63 ++++++++++++++++++++++++++++++-- README.md | 78 +++++++++++++++++++++++++++++----------- __tests__/gpg.test.ts | 16 +++++++-- action.yml | 3 ++ src/context.ts | 2 ++ src/gpg.ts | 14 ++++++++ src/main.ts | 8 +++++ 7 files changed, 158 insertions(+), 26 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e09f05..21cbf77 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,6 +55,7 @@ jobs: if (!fs.existsSync(gnupgfolder)){ fs.mkdirSync(gnupgfolder); } + fs.chmodSync(gnupgfolder, '0700'); fs.copyFile('__tests__/fixtures/gpg.conf', `${gnupgfolder}/gpg.conf`, (err) => { if (err) throw err; }); @@ -69,11 +70,11 @@ jobs: core.setOutput('passphrase', fs.readFileSync('__tests__/fixtures/${{ matrix.key }}.pass', {encoding: 'utf8'})); - name: Import GPG - id: import_gpg uses: ./ with: gpg_private_key: ${{ steps.test.outputs.pgp }} passphrase: ${{ steps.test.outputs.passphrase }} + trust_level: 5 git_config_global: ${{ matrix.global }} git_user_signingkey: true git_commit_gpgsign: true @@ -116,7 +117,6 @@ jobs: core.setOutput('passphrase', fs.readFileSync('__tests__/fixtures/${{ matrix.key }}.pass', {encoding: 'utf8'})); - name: Import GPG - id: import_gpg uses: ./ with: gpg_private_key: ${{ steps.test.outputs.pgp-base64 }} @@ -126,3 +126,62 @@ jobs: git_tag_gpgsign: true git_push_gpgsign: if-asked fingerprint: ${{ matrix.fingerprint }} + + trust: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + key: + - test-key + level: + - '' + - 5 + - 4 + - 3 + - 2 + - 1 + os: + - ubuntu-latest + - macOS-latest + - windows-latest + steps: + - + name: Checkout + uses: actions/checkout@v3 + - + name: GPG conf + uses: actions/github-script@v6 + with: + script: | + const fs = require('fs'); + const gnupgfolder = `${require('os').homedir()}/.gnupg`; + if (!fs.existsSync(gnupgfolder)){ + fs.mkdirSync(gnupgfolder); + } + fs.chmodSync(gnupgfolder, '0700'); + fs.copyFile('__tests__/fixtures/gpg.conf', `${gnupgfolder}/gpg.conf`, (err) => { + if (err) throw err; + }); + - + name: Get test key and passphrase + uses: actions/github-script@v6 + id: test + with: + script: | + const fs = require('fs'); + core.setOutput('pgp', fs.readFileSync('__tests__/fixtures/${{ matrix.key }}.pgp', {encoding: 'utf8'})); + core.setOutput('passphrase', fs.readFileSync('__tests__/fixtures/${{ matrix.key }}.pass', {encoding: 'utf8'})); + - + name: Import GPG + id: import_gpg + uses: ./ + with: + gpg_private_key: ${{ steps.test.outputs.pgp }} + passphrase: ${{ steps.test.outputs.passphrase }} + trust_level: ${{ matrix.level }} + - + name: List trust values + run: | + gpg --export-ownertrust + shell: bash diff --git a/README.md b/README.md index 760c8ac..e007604 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ ___ * [Workflow](#workflow) * [Sign commits](#sign-commits) * [Use a subkey](#use-a-subkey) + * [Set key's trust level](#set-keys-trust-level) * [Customizing](#customizing) * [inputs](#inputs) * [outputs](#outputs) @@ -76,7 +77,6 @@ jobs: uses: actions/checkout@v3 - name: Import GPG key - id: import_gpg uses: crazy-max/ghaction-import-gpg@v5 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} @@ -139,7 +139,6 @@ jobs: uses: actions/checkout@v3 - name: Import GPG key - id: import_gpg uses: crazy-max/ghaction-import-gpg@v5 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} @@ -164,26 +163,63 @@ sub ed25519 2021-09-24 [S] You can use the subkey with signing capability whose fingerprint is `C17D11ADF199F12A30A0910F1F80449BE0B08CB8`. +### Set key's trust level + +With the `trust_level` input, you can specify the trust level of the GPG key. + +Valid values are: +* `1`: unknown +* `2`: never +* `3`: marginal +* `4`: full +* `5`: ultimate + +```yaml +name: import-gpg + +on: + push: + branches: master + +jobs: + import-gpg: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v3 + - + name: Import GPG key + uses: crazy-max/ghaction-import-gpg@v5 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.PASSPHRASE }} + trust_level: 5 +``` + ## Customizing ### inputs Following inputs can be used as `step.with` keys -| Name | Type | Description | -|---------------------------------------|---------|------------------------------------------------| -| `gpg_private_key` | String | GPG private key exported as an ASCII armored version or its base64 encoding (**required**) | -| `passphrase` | String | Passphrase of the GPG private key | -| `git_config_global` | Bool | Set Git config global (default `false`) | -| `git_user_signingkey` | Bool | Set GPG signing keyID for this Git repository (default `false`) | -| `git_commit_gpgsign` | Bool | Sign all commits automatically. (default `false`) | -| `git_tag_gpgsign` | Bool | Sign all tags automatically. (default `false`) | -| `git_push_gpgsign` | String | Sign all pushes automatically. (default `if-asked`) | -| `git_committer_name` | String | Set commit author's name (defaults to the name associated with the GPG key) | -| `git_committer_email` | String | Set commit author's email (defaults to the email address associated with the GPG key) | -| `workdir` | String | Working directory (below repository root) (default `.`) | -| `fingerprint` | String | Specific fingerprint to use (subkey) | +| Name | Type | Description | +|-----------------------|--------|--------------------------------------------------------------------------------------------| +| `gpg_private_key` | String | GPG private key exported as an ASCII armored version or its base64 encoding (**required**) | +| `passphrase` | String | Passphrase of the GPG private key | +| `trust_level` | String | Set key's trust level | +| `git_config_global` | Bool | Set Git config global (default `false`) | +| `git_user_signingkey` | Bool | Set GPG signing keyID for this Git repository (default `false`) | +| `git_commit_gpgsign` | Bool | Sign all commits automatically. (default `false`) | +| `git_tag_gpgsign` | Bool | Sign all tags automatically. (default `false`) | +| `git_push_gpgsign` | String | Sign all pushes automatically. (default `if-asked`) | +| `git_committer_name` | String | Set commit author's name (defaults to the name associated with the GPG key) | +| `git_committer_email` | String | Set commit author's email (defaults to the email address associated with the GPG key) | +| `workdir` | String | Working directory (below repository root) (default `.`) | +| `fingerprint` | String | Specific fingerprint to use (subkey) | +> **Note** +> > `git_user_signingkey` needs to be enabled for `git_commit_gpgsign`, `git_tag_gpgsign`, > `git_push_gpgsign`, `git_committer_name`, `git_committer_email` inputs. @@ -191,12 +227,12 @@ Following inputs can be used as `step.with` keys Following outputs are available -| Name | Type | Description | -|---------------|---------|---------------------------------------| -| `fingerprint` | String | Fingerprint of the GPG key (recommended as [user ID](https://www.gnupg.org/documentation/manuals/gnupg/Specify-a-User-ID.html)) | -| `keyid` | String | Low 64 bits of the X.509 certificate SHA-1 fingerprint | -| `name` | String | Name associated with the GPG key | -| `email` | String | Email address associated with the GPG key | +| Name | Type | Description | +|---------------|--------|---------------------------------------------------------------------------------------------------------------------------------| +| `fingerprint` | String | Fingerprint of the GPG key (recommended as [user ID](https://www.gnupg.org/documentation/manuals/gnupg/Specify-a-User-ID.html)) | +| `keyid` | String | Low 64 bits of the X.509 certificate SHA-1 fingerprint | +| `name` | String | Name associated with the GPG key | +| `email` | String | Email address associated with the GPG key | ## Contributing diff --git a/__tests__/gpg.test.ts b/__tests__/gpg.test.ts index d7356d5..46b96be 100644 --- a/__tests__/gpg.test.ts +++ b/__tests__/gpg.test.ts @@ -107,10 +107,10 @@ for (const userInfo of userInfos) { describe('getKeygrip', () => { it('returns the keygrip for a given fingerprint', async () => { await gpg.importKey(userInfo.pgp); - for (const [i, fingerprint] of userInfo.fingerprints.entries()) { + for (const {idx, fingerprint} of userInfo.fingerprints.map((fingerprint, idx) => ({idx, fingerprint}))) { await gpg.getKeygrip(fingerprint).then(keygrip => { - expect(keygrip.length).toEqual(userInfo.keygrips[i].length); - expect(keygrip).toEqual(userInfo.keygrips[i]); + expect(keygrip.length).toEqual(userInfo.keygrips[idx].length); + expect(keygrip).toEqual(userInfo.keygrips[idx]); }); } }); @@ -128,6 +128,16 @@ for (const userInfo of userInfos) { }); }); + describe('setTrustLevel', () => { + it('set trust level', async () => { + await gpg.importKey(userInfo.pgp); + await gpg.configureAgent(gpg.agentConfig); + expect(() => { + gpg.setTrustLevel(userInfo.keyID, '5'); + }).not.toThrow(); + }); + }); + describe('deleteKey', () => { // eslint-disable-next-line jest/expect-expect it('removes key from GnuPG', async () => { diff --git a/action.yml b/action.yml index c81a2b7..7eb90f4 100644 --- a/action.yml +++ b/action.yml @@ -13,6 +13,9 @@ inputs: passphrase: description: 'Passphrase of the GPG private key' required: false + trust_level: + description: "Set key's trust level" + required: false git_config_global: description: 'Set Git config global' default: 'false' diff --git a/src/context.ts b/src/context.ts index 3145373..4409e70 100644 --- a/src/context.ts +++ b/src/context.ts @@ -3,6 +3,7 @@ import * as core from '@actions/core'; export interface Inputs { gpgPrivateKey: string; passphrase: string; + trustLevel: string; gitConfigGlobal: boolean; gitUserSigningkey: boolean; gitCommitGpgsign: boolean; @@ -18,6 +19,7 @@ export async function getInputs(): Promise { return { gpgPrivateKey: core.getInput('gpg_private_key', {required: true}), passphrase: core.getInput('passphrase'), + trustLevel: core.getInput('trust_level'), gitConfigGlobal: core.getBooleanInput('git_config_global'), gitUserSigningkey: core.getBooleanInput('git_user_signingkey'), gitCommitGpgsign: core.getBooleanInput('git_commit_gpgsign'), diff --git a/src/gpg.ts b/src/gpg.ts index 10ee1db..b2d1077 100644 --- a/src/gpg.ts +++ b/src/gpg.ts @@ -206,6 +206,20 @@ export const presetPassphrase = async (keygrip: string, passphrase: string): Pro return await gpgConnectAgent(`KEYINFO ${keygrip}`); }; +export const setTrustLevel = async (keyID: string, trust: string): Promise => { + await exec + .getExecOutput('gpg', ['--batch', '--no-tty', '--command-fd', '0', '--edit-key', keyID], { + ignoreReturnCode: true, + silent: true, + input: Buffer.from(`trust\n${trust}\ny\nquit\n`) + }) + .then(res => { + if (res.stderr.length > 0 && res.exitCode != 0) { + throw new Error(res.stderr); + } + }); +}; + export const deleteKey = async (fingerprint: string): Promise => { await exec .getExecOutput('gpg', ['--batch', '--yes', '--delete-secret-keys', fingerprint], { diff --git a/src/main.ts b/src/main.ts index 46c3db9..8e04296 100644 --- a/src/main.ts +++ b/src/main.ts @@ -81,6 +81,14 @@ async function run(): Promise { }); } + if (inputs.trustLevel) { + await core.group(`Setting key's trust level`, async () => { + await gpg.setTrustLevel(privateKey.keyID, inputs.trustLevel).then(() => { + core.info(`Trust level set to ${inputs.trustLevel} for ${privateKey.keyID}`); + }); + }); + } + await core.group(`Setting outputs`, async () => { core.info(`fingerprint=${fingerprint}`); core.setOutput('fingerprint', fingerprint);