| 1 | import { execFileSync } from 'node:child_process'; |
| 2 | import { mkdtempSync, rmSync |
| 3 | import { tmpdir } |
| 4 | import path from 'node:path |
| 5 | import { afterEach, beforeEach |
| 6 | |
| 7 | import { |
| 8 | SIGNED_COMMIT_OBJECT, |
| 9 | SIGNING_PUBLIC_KEY, |
| 10 | } from '../../gpg/__fixtures__/signed-commit.js'; |
| 11 | import { verifySignature } |
| 12 | import { readSignedCommits, splitSignature |
| 13 | |
| 14 | describe('splitSignature', () |
| 15 | it('splits a real git signature from what it covers', |
| 16 | const signed = |
| 17 | |
| 18 | expect(signed?.signature |
| 19 | true, |
| 20 | ); |
| 21 | expect( |
| 22 | signed?.signature.trimEnd |
| 23 | ).toBe(true); |
| 24 | // The payload is the commit as it would read without the header. |
| 25 | expect(signed?.payload |
| 26 | expect(signed?.payload |
| 27 | expect(signed?.payload |
| 28 | }); |
| 29 | |
| 30 | it('verifies against the key that signed it', |
| 31 | const signed = |
| 32 | |
| 33 | await expect( |
| 34 | verifySignature({ |
| 35 | payload: signed!.payload |
| 36 | armoredSignature: signed!.signature |
| 37 | armoredKey: SIGNING_PUBLIC_KEY, |
| 38 | }), |
| 39 | ).resolves.toBe( |
| 40 | }); |
| 41 | |
| 42 | it('rejects a payload that was edited after signing', |
| 43 | const signed = |
| 44 | |
| 45 | await expect( |
| 46 | verifySignature({ |
| 47 | payload: signed!.payload |
| 48 | armoredSignature: signed!.signature |
| 49 | armoredKey: SIGNING_PUBLIC_KEY, |
| 50 | }), |
| 51 | ).resolves.toBe( |
| 52 | }); |
| 53 | |
| 54 | it('is null for an unsigned commit', |
| 55 | expect( |
| 56 | splitSignature( |
| 57 | 'tree abc\nauthor A <a@b.c> 1 +0000\n\nmentions gpgsig in the body\n' |
| 58 | ), |
| 59 | ).toBeNull(); |
| 60 | }); |
| 61 | |
| 62 | it('ignores an SSH signature, which this does not verify', |
| 63 | const commit = |
| 64 | '-----BEGIN PGP SIGNATURE-----', |
| 65 | '-----BEGIN SSH SIGNATURE-----', |
| 66 | ); |
| 67 | |
| 68 | expect(splitSignature(commit |
| 69 | }); |
| 70 | }); |
| 71 | |
| 72 | describe('readSignedCommits', () |
| 73 | let root: |
| 74 | let gitDir: |
| 75 | let git: |
| 76 | |
| 77 | beforeEach(() => |
| 78 | root = mkdtempSync( |
| 79 | gitDir = path. |
| 80 | const env = |
| 81 | delete env.GIT_DIR; |
| 82 | git = (... |
| 83 | execFileSync('git', |
| 84 | |
| 85 | execFileSync('git', |
| 86 | git('config', |
| 87 | git('config', |
| 88 | // The machine running the tests may sign by default. |
| 89 | git('commit', |
| 90 | }); |
| 91 | |
| 92 | afterEach(() => |
| 93 | |
| 94 | it('returns only the commits that carry a signature', |
| 95 | const unsigned = |
| 96 | // The fixture object is written straight in, so the test needs no gpg. |
| 97 | const signed = |
| 98 | 'git', |
| 99 | ['hash-object', '-t' |
| 100 | { cwd: root, |
| 101 | ).trim(); |
| 102 | |
| 103 | const found = |
| 104 | gitDir, |
| 105 | shas: [unsigned, |
| 106 | }); |
| 107 | |
| 108 | expect([...found. |
| 109 | expect(found.get |
| 110 | }); |
| 111 | |
| 112 | it('is empty when nothing was asked for', |
| 113 | await expect(readSignedCommits |
| 114 | new Map(), |
| 115 | ); |
| 116 | }); |
| 117 | }); |