1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
| | title: Authenticate your Git checkouts!
author: Ludovic Courtès
tags: Security, Software development
date: 2024-05-06 14:14
---
You clone a Git repository, then pull from it. How can you tell its
contents are “authentic”—i.e., coming from the “genuine” project you
think you’re pulling from, written by the fine human beings you've been
working with? With commit signatures and “verified” badges ✅
flourishing, you’d think this has long been solved—but nope!
Four years after Guix [deployed its own
tool](https://guix.gnu.org/en/blog/2020/securing-updates/) to allow
users to authenticate updates fetched with `guix pull` (which uses Git
under the hood), the situation hasn’t changed all that much: the vast
majority of developers using Git simply do not authenticate the code
they pull. That’s pretty bad. It’s the modern-day equivalent of
sharing unsigned tarballs and packages like we’d blissfully do in the
past century.
The authentication mechanism Guix uses for
[channels](https://guix.gnu.org/manual/devel/en/html_node/Channels.html)
is available to any Git user through the [`guix git
authenticate`](https://guix.gnu.org/manual/devel/en/html_node/Invoking-guix-git-authenticate.html)
command. This post is a guide for Git users who are not necessarily
Guix users but are interested in using this command for their own
repositories. Before looking into the command-line interface and how we
improved it to make it more convenient, let’s dispel any
misunderstandings or misconceptions.
# Why you should care
When you run `git pull`, you’re fetching a bunch of commits from a
server. If it’s over HTTPS, you’re authenticating *the server* itself,
which is nice, but that does not tell you who the code actually comes
from—the server might be compromised and an attacker pushed code to the
repository. Not helpful. At all.
But hey, maybe you think you’re good because everyone on your project is
signing commits and tags, and because you’re disciplined, you routinely
run `git log --show-signature` and check those “Good signature” GPG
messages. Maybe you even have those fancy “✅ verified” badges as found
[on
GitLab](https://docs.gitlab.com/ee/user/project/repository/signed_commits/gpg.html)
and [on
GitHub](https://docs.github.com/en/authentication/managing-commit-signature-verification).
Signing commits is part of the solution, but it’s not enough to
_authenticate_ a set of commits that you pull; all it shows is that,
well, those commits are signed. Badges aren’t much better: the presence
of a “verified” badge only shows that the commit is signed by the
OpenPGP key *currently registered* for the corresponding GitLab/GitHub
account. If you register a new key, or if you leave the project, your
commits lose their badge. Not helpful either, not to mention that this
is all tied to the hosting site you’re on, outside of Git’s control.
Being able to ensure that when you run `git pull`, you’re getting code
that _genuinely_ comes from authorized developers of the project is
basic security hygiene. Obviously it cannot protect against efforts to
infiltrate a project to eventually get commit access and insert
malicious code—the kind of multi-year plot that led to the [xz
backdoor](https://tukaani.org/xz-backdoor/)—but if you don’t even
protect against unauthorized commits, then all bets are off.
Authentication is something we naturally expect from `apt update`,
`pip`, `guix pull`, and similar tools; why not treat `git pull` to the
same standard?
# Initial setup
The [`guix git
authenticate`](https://guix.gnu.org/manual/devel/en/html_node/Invoking-guix-git-authenticate.html)
command authenticates Git checkouts, unsurprisingly. It’s currently
part of Guix because that’s where it was brought to life, but it can be
used on any Git repository. This section focuses on how to use it; you
can learn about the motivation, its design, and its implementation in
[the 2020 blog
post](https://guix.gnu.org/en/blog/2020/securing-updates/), in the 2022
peer-reviewed academic paper entitled [_Building a Secure Software
Supply Chain with
GNU Guix_](https://doi.org/10.22152/programming-journal.org/2023/7/1),
or in this 20mn
[presentation](https://archive.fosdem.org/2023/schedule/event/security_where_does_that_code_come_from/).
To support authentication of your repository with `guix git
authenticate`, you need to follow these steps:
0. Enable commit signing on your repo: `git config commit.gpgSign
true`. (Git now supports other signing methods but here we need
OpenPGP signatures.)
1. Create a `keyring` branch containing all the OpenPGP keys of all
the committers, along these lines:
```
git checkout --orphan keyring
git reset --hard
gpg --export alice@example.org > alice.key
…
git add *.key
git commit -a -m "Add committer keys."
```
All the files must end in `.key`. You must never remove keys from
that branch: keys of users who left the project are necessary to
authenticate past commits.
2. Back to the main branch, add a `.guix-authorizations` file, listing
the OpenPGP keys of authorized committers—we’ll get back to its
format below.
3. Commit! This becomes the _introductory commit_ from which
authentication can proceed. The _introduction_ of your repository
is the ID of this commit and the OpenPGP fingerprint of the key
used to sign it.
That’s it. From now on, anyone who clones the repository can
authenticate it. The first time, run:
```
guix git authenticate COMMIT SIGNER
```
… where `COMMIT` is the commit ID of the introductory commit, and
`SIGNER` is the OpenPGP fingerprint of the key used to sign that commit
(make sure to enclose it in double quotes if there are spaces!). As a
repo maintainer, you must advertise this introductory commit ID and
fingerprint on a web page or in a `README` file so others know what to
pass to `guix git authenticate`.
The commit and signer are now recorded on the first run in
`.git/config`; next time, you can run it without any arguments:
```
guix git authenticate
```
The other new feature is that the first time you run it, the command
installs *pre-push and pre-merge hooks* (unless preexisting hooks are
found) such that your repository is automatically authenticated from
there on every time you run `git pull` or `git push`.
`guix git authenticate` exits with a non-zero code and an error message
when it stumbles upon a commit that lacks a signature, that is signed by
a key not in the `keyring` branch, or that is signed by a key not listed
in `.guix-authorizations`.
# Maintaining the list of authorized committers
The `.guix-authorizations` file in the repository is central: it lists
the OpenPGP fingerprints of authorized committers. Any commit that is
*not* signed by a key listed in the `.guix-authorizations` file of its
parent commit(s) is considered inauthentic—and an error is reported.
The [format of
`.guix-authorizations`](https://guix.gnu.org/manual/devel/en/html_node/Specifying-Channel-Authorizations.html#channel_002dauthorizations)
is based on [S-expressions](https://en.wikipedia.org/wiki/S-expression)
and looks like this:
```scheme
;; Example '.guix-authorizations' file.
(authorizations
(version 0) ;current file format version
(("AD17 A21E F8AE D8F1 CC02 DBD9 F8AE D8F1 765C 61E3"
(name "alice"))
("2A39 3FFF 68F4 EF7A 3D29 12AF 68F4 EF7A 22FB B2D5"
(name "bob"))
("CABB A931 C0FF EEC6 900D 0CFB 090B 1199 3D9A EBB5"
(name "charlie"))))
```
The `name` bits are hints and do not have any effect; what matters is
the fingerprints that are listed. You can obtain them with GnuPG by
running commands like:
```
gpg --fingerprint charlie@example.org
```
At any time you can add or remove keys from `.guix-authorizations` and
commit the changes; those changes take effect for child commits. For
example, if we add Billie’s fingerprint to the file in commit _A_, then
Billie becomes an authorized committer in *descendants* of commit _A_
(we must make sure to add Billie’s key as a file in the `keyring`
branch, too, as we saw above); Billie is still unauthorized in branches
that lack _A_. If we remove Charlie’s key from the file in commit _B_,
then Charlie is no longer an authorized committer, except in branches
that start before _B_. This should feel rather natural.
That’s pretty much all you need to know to get started! [Check the
manual](https://guix.gnu.org/manual/devel/en/html_node/Invoking-guix-git-authenticate.html)
for more info.
All the information needed to authenticate the repository is contained
in the repository itself—it does not depend on a forge or key server.
That’s a good property to allow anyone to authenticate it, to ensure
determinism and transparency, and to avoid lock-in.
# Interested? You can help!
`guix git authenticate` is a great tool that you can start using today
so you and fellow co-workers can be sure you’re getting the right code!
It solves an important problem that, to my knowledge, hasn’t really been
addressed by any other tool.
Maybe you’re interested but don’t feel like installing Guix “just” for
this tool. Maybe you’re not into Scheme and Lisp and would rather use a
tool written in your favorite language. Or maybe you think—and
rightfully so—that such a tool ought to be part of Git proper.
That’s OK, we can talk! We’re open to discussing with folks who’d like
to come up with alternative implementations—check out the articles
mentioned above if you’d like to take that route. And we’re open to
contributing to a standardization effort. Let’s [get in
touch](https://guix.gnu.org/contact/)!
|