openSUSE:ALP/Workgroups/Git-Packaging-Workflow/WorkPackages/Multi-signature review handling idea

Jump to: navigation, search
How can we have a cryptographically secure merge request, reviewed by multiple people/entities, that can later be verified without having having a wall of commits or tags? The idea is to sign the same commit multiple times with multiple keys prior to merging to target branch.


Prerequisites: Read the Commits are snapshots, not diffs and Multiple signatures in a git commit

Please refer to Help:Editing in order to write a quality approved article.

The central problem that we are trying to solve has the following requirements

  • cryptographically secure and immutable reviews
  • out of the way -- not visible to regular git users
  • part of the git repository and its history -- no additional infrastructure

The solution is to use a GPG signature as a means of approving a git commit. The caveat is we will need multiple reviews which requires multiple GPG signatures and this document describes how to do this.

What is a signed commit?

Anatomy of a commit message

First, we need to look at what is a commit message. Let's look at what is a typical commit

> git cat-file -t ab3743373248a8758f07903e2b7eb9239d53bf73
commit

> git cat-file -p ab3743373248a8758f07903e2b7eb9239d53bf73
tree f05af273ba36fe5176e5eaab349661a56b3d27a0
author Adam Majer <amajer@suse.de> 1663841134 +0200
committer Adam Majer <amajer@suse.de> 1663841134 +0200

Initial unsigned commit

So, the commit hash id is ab3743373248a8758f07903e2b7eb9239d53bf73, this is not actually part of the commit message. The commit message looks like a mail message, consisting of headers like tree, author, committer, and the actual text of the commit message that follows. This is the text that is actually signed and thus it is impossible to modify this message after it's signed. This means, no "signed-off" tags can be added after it's signed.

The signature

Let's look and manually verify an actual signed commit with

git commit -S
> git cat-file -p 2b549e034613c67f32fb3875dd61cdd7bd585052
tree 1ddaed3346e45f5f50468c1118a88b1e65547b4f
parent ab3743373248a8758f07903e2b7eb9239d53bf73
author Adam Majer <amajer@suse.de> 1663841363 +0200
committer Adam Majer <amajer@suse.de> 1663841427 +0200
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQTtsx4h0JQo2B9B5shla2XtpJuLkAUCYyw0lAAKCRBla2XtpJuL
 kDiuAP9H9OJt5FuGIggzVKtTdYI2BtlJxOAcev98RtVVJ3AWywEAxIZ4GurUVyTV
 s/ozaDjC707BSHcBbL3mzsMhTvgbOQ4=
 =bfsh
 -----END PGP SIGNATURE-----

Adding a new line, because the best!

So, now we can separate the two parts of this, the gpgsig and the rest. Notice that gpgsig is a multi-line header and its value is an armored signature indented with single space. So, splitting the two yields,

tree 1ddaed3346e45f5f50468c1118a88b1e65547b4f
parent ab3743373248a8758f07903e2b7eb9239d53bf73
author Adam Majer <amajer@suse.de> 1663841363 +0200
committer Adam Majer <amajer@suse.de> 1663841427 +0200

Adding a new line, because the best!

and a detached signature

-----BEGIN PGP SIGNATURE-----

iHUEABYKAB0WIQTtsx4h0JQo2B9B5shla2XtpJuLkAUCYyw0lAAKCRBla2XtpJuL
kDiuAP9H9OJt5FuGIggzVKtTdYI2BtlJxOAcev98RtVVJ3AWywEAxIZ4GurUVyTV
s/ozaDjC707BSHcBbL3mzsMhTvgbOQ4=
=bfsh
-----END PGP SIGNATURE-----

which can now be verified,

gpg --verify data.sig data
gpg: Signature made Thu 22 Sep 2022 12:10:28 PM CEST
gpg:                using EDDSA key EDB31E21D09428D81F41E6C8656B65EDA49B8B90
gpg: Good signature from "test2" [ultimate]

Appending additional signatures

Signing

Since the signature is detached, we can actually combine multiple signatures into one. Here we sign with test1 key while the previous commit is signed with test2 key.

gpg --dearmor data.sig
gpg -u test1 -o data.sig2.gpg --detach-sign data
cat data.sig.gpg data.sig2.gpg | gpg --enarmor > data.newsig

And now the newsig is,

-----BEGIN PGP ARMORED FILE-----
Comment: Use "gpg --dearmor" for unpacking

iHUEABYKAB0WIQTtsx4h0JQo2B9B5shla2XtpJuLkAUCYyw0lAAKCRBla2XtpJuL
kDiuAP9H9OJt5FuGIggzVKtTdYI2BtlJxOAcev98RtVVJ3AWywEAxIZ4GurUVyTV
s/ozaDjC707BSHcBbL3mzsMhTvgbOQ6IdQQAFgoAHRYhBEUByzN6sbYKKVuun+E1
Qd3Y6dOVBQJjLHbtAAoJEOE1Qd3Y6dOVSCMA/1ynEhW6L8yB3eZpyfApO98jor3G
+B7duoI1+HRy6er0AQDJKogPbsRMWmwJfMF3J7j1aesCTYU8tPjmTNlTI7g/BQ==
=dVSf
-----END PGP ARMORED FILE-----

which contains both signatures

> gpg --verify data.newsig data
gpg: Signature made Thu 22 Sep 2022 12:10:28 PM CEST
gpg:                using EDDSA key EDB31E21D09428D81F41E6C8656B65EDA49B8B90
gpg: Good signature from "test2" [ultimate]
gpg: Signature made Thu 22 Sep 2022 04:53:33 PM CEST
gpg:                using EDDSA key 4501CB337AB1B60A295BAE9FE13541DDD8E9D395
gpg: Good signature from "test1" [ultimate]

Now we can re-insert this back onto the commit message

> cat data.new
tree 1ddaed3346e45f5f50468c1118a88b1e65547b4f
parent ab3743373248a8758f07903e2b7eb9239d53bf73
author Adam Majer <amajer@suse.de> 1663841363 +0200
committer Adam Majer <amajer@suse.de> 1663841427 +0200
gpgsig -----BEGIN PGP ARMORED FILE-----
 Comment: Use "gpg --dearmor" for unpacking
 
 iHUEABYKAB0WIQTtsx4h0JQo2B9B5shla2XtpJuLkAUCYyw0lAAKCRBla2XtpJuL
 kDiuAP9H9OJt5FuGIggzVKtTdYI2BtlJxOAcev98RtVVJ3AWywEAxIZ4GurUVyTV
 s/ozaDjC707BSHcBbL3mzsMhTvgbOQ6IdQQAFgoAHRYhBEUByzN6sbYKKVuun+E1
 Qd3Y6dOVBQJjLDdpAAoJEOE1Qd3Y6dOVco4A/3NTnsqPJuFFKtEEEuyGXTZrpuGq
 RgJEBfLzNUMDAvndAQCE8RyGtrHnZnBMp9/SULS8JC/TYoJgED3xsKyZ6ztZCA==
 =SCfn
 -----END PGP ARMORED FILE-----

Adding a new line, because the best!

And insert it into the git tree

git hash-object -t commit -w data.new
bd5956bc92721000b19dca778c4dbb9d884d39e0

> git cat-file -p bd5956bc92721000b19dca778c4dbb9d884d39e0
tree 1ddaed3346e45f5f50468c1118a88b1e65547b4f
parent ab3743373248a8758f07903e2b7eb9239d53bf73
author Adam Majer <amajer@suse.de> 1663841363 +0200
committer Adam Majer <amajer@suse.de> 1663841427 +0200
gpgsig -----BEGIN PGP ARMORED FILE-----
 Comment: Use "gpg --dearmor" for unpacking
 
 iHUEABYKAB0WIQTtsx4h0JQo2B9B5shla2XtpJuLkAUCYyw0lAAKCRBla2XtpJuL
 kDiuAP9H9OJt5FuGIggzVKtTdYI2BtlJxOAcev98RtVVJ3AWywEAxIZ4GurUVyTV
 s/ozaDjC707BSHcBbL3mzsMhTvgbOQ6IdQQAFgoAHRYhBEUByzN6sbYKKVuun+E1
 Qd3Y6dOVBQJjLDdpAAoJEOE1Qd3Y6dOVco4A/3NTnsqPJuFFKtEEEuyGXTZrpuGq
 RgJEBfLzNUMDAvndAQCE8RyGtrHnZnBMp9/SULS8JC/TYoJgED3xsKyZ6ztZCA==
 =SCfn
 -----END PGP ARMORED FILE-----

Adding a new line, because the best!

NOTE: Every time you add a signature, you change the commit message. This will this change the commit id which will require any branch to be reset although the original signature will remain valid.

Verification

Verification via git-verify-commit fails since it explicitly now refuses multi-signed messages. But manual verification is quite simple. For example, assuming we have multiple keyrings. One keyring for reviewers and one keyring for bots. Then we can verify whether signatures are completed with gpg, this time let's use status messages of gpgv2

 1. extract the signature from the commit message
 2. run gpgv2 with correct keyring to check if it was signed by the appropriate reviewer
 3. when all reviewers have signed, the commit is verified.

So using data in above,

> gpgv2 --keyring ./keyring.bot data.newsig data
gpgv: Signature made Thu 22 Sep 2022 12:10:28 PM CEST
gpgv:                using EDDSA key EDB31E21D09428D81F41E6C8656B65EDA49B8B90
gpgv: Good signature from "test2"
gpgv: Signature made Thu 22 Sep 2022 04:53:33 PM CEST
gpgv:                using EDDSA key 4501CB337AB1B60A295BAE9FE13541DDD8E9D395
gpgv: Can't check signature: No public key

> gpgv2 --keyring ./keyring.audit data.newsig data
gpgv: Signature made Thu 22 Sep 2022 12:10:28 PM CEST
gpgv:                using EDDSA key EDB31E21D09428D81F41E6C8656B65EDA49B8B90
gpgv: Can't check signature: No public key
gpgv: Signature made Thu 22 Sep 2022 04:53:33 PM CEST
gpgv:                using EDDSA key 4501CB337AB1B60A295BAE9FE13541DDD8E9D395
gpgv: Good signature from "test1"

In machine-readable format,

> gpgv2 --keyring ./keyring.bot --status-fd 1 data.newsig data 2> /dev/null
[GNUPG:] NEWSIG
[GNUPG:] KEY_CONSIDERED EDB31E21D09428D81F41E6C8656B65EDA49B8B90 0
[GNUPG:] SIG_ID TsXgTrBHSsIjQkHE5b8S5isAdtw 2022-09-22 1663841428
[GNUPG:] KEY_CONSIDERED EDB31E21D09428D81F41E6C8656B65EDA49B8B90 0
[GNUPG:] GOODSIG 656B65EDA49B8B90 test2
[GNUPG:] VALIDSIG EDB31E21D09428D81F41E6C8656B65EDA49B8B90 2022-09-22 1663841428 0 4 0 22 10 00 EDB31E21D09428D81F41E6C8656B65EDA49B8B90
[GNUPG:] NEWSIG
[GNUPG:] ERRSIG E13541DDD8E9D395 22 10 00 1663858413 9 4501CB337AB1B60A295BAE9FE13541DDD8E9D395
[GNUPG:] NO_PUBKEY E13541DDD8E9D395

> gpgv2 --keyring ./keyring.audit --status-fd 1 data.newsig data 2> /dev/null
[GNUPG:] NEWSIG
[GNUPG:] ERRSIG 656B65EDA49B8B90 22 10 00 1663841428 9 EDB31E21D09428D81F41E6C8656B65EDA49B8B90
[GNUPG:] NO_PUBKEY 656B65EDA49B8B90
[GNUPG:] NEWSIG
[GNUPG:] KEY_CONSIDERED 4501CB337AB1B60A295BAE9FE13541DDD8E9D395 0
[GNUPG:] SIG_ID 2VZOyE4QY+46/m3HJ+FhuZgsiAk 2022-09-22 1663858413
[GNUPG:] KEY_CONSIDERED 4501CB337AB1B60A295BAE9FE13541DDD8E9D395 0
[GNUPG:] GOODSIG E13541DDD8E9D395 test1
[GNUPG:] VALIDSIG 4501CB337AB1B60A295BAE9FE13541DDD8E9D395 2022-09-22 1663858413 0 4 0 22 10 00 4501CB337AB1B60A295BAE9FE13541DDD8E9D395

As long as we have signatures of all the required entities, we can merge this request. More importantly, it's not possible to add additional signatures once the commit is merged onto the target.

Advantages

  1. Immutable once submitted to review since signatures will fail if the tree object is modified
  2. No additional commits to pollute the 'git log'
  3. As long as no conflicts, it can be "rebased" in a merge commit
  4. You can submit the same signed commit to multiple branches and reviews are then added independently (by definition)

Challenges

Tooling

Multiply signed commits are not supported by 'git verify-commit' because they are simply not used. This is small tooling issue. The main counter-argument here is that reviews are done either with UI or signatures can be merged on the backend. We do not need to rely on external tool support here. This makes this less of a challenge.

Even simple reviews where signature is replaced via a resigned, amended commit, can be simply merged on the backend,

gpg --amend -S
git push gitea new_main:PR1233
...here we can merge sigs in a server-side commit hook...

Keyring management

Additionally, 'git verify-commit' does not support specifying keyrings which makes it less useful than doing this verification manually. At very least, we will have to manage the keyrings containing the keys of the bots and reviewers. The keys of submitters are less important, just like in our current reviews. See hhttps://gitlab.com/source-security/git-verify/ for a tool that set out to support managing keyrings.