Hi Liliana! Liliana Marie Prikler <liliana.prik...@gmail.com> writes:
> For most use cases, this is a non-issue. Assuming you are a single > committer to your fork, you can always rebase your changes on top of > Guix (if you're willing to bump the introductory commit) The idea of authentication is that once you trust the channel introduction, you can be sure that everything you pull after that is authentic. The introduction only needs to be trusted once. If you're bumping the introduction every time, then you need to obtain and verify the introduction every time. You're going from 'Trust On First Use' to 'Trust On Every Use'. Not ideal IMO. > or sign the changes to Guix with your own key (if you are willing to > accept that this changes the history). With multiple committers, you > will need to do the latter. While this could actually work, without changing the history, the problem is that there is no easy way to authenticate upstream commits. You could do it like this: 0) Before creating your fork, authenticate every commit in the Guix checkout (as described in the manual). 1) Switch to your branch that tracks upstream. 2) Pull from upstream. 3) Run `guix git authenticate`, supplying Guix's channel introduction as arguments. 4) After this succeeds, create and switch to a branch from the current tip of your upstream-tracking branch. Edit .guix_authorizations to add your key, and create a signed commit. 5) Merge this branch into your fork branch. 6) Switch back to your fork branch. 7) Delete the [guix "authentication"] section from .git/config. 8) Run `guix git authenticate` with the introduction of your fork branch, to authenticate the merge commit. That's a lot of manual steps for every pull from upstream! While I do have to give you credit for this idea - at least we now have a workaround for people who are determined enough - I'm guessing a lot of people will probably just skip authentication if it's going to be this annoying. Authenticating a fresh clone from scratch will be even more annoying, especially if you have multiple fork branches (eg. you're tracking someone else's fork). We could create a script to do all the steps for us, but if and when it fails on whatever insane edge cases people are able to come up with, they're going to need to understand all the steps involved anyway. Abstraction is not a substitute for a clean underlying design. Also I just want to point out that rebasing /will/ change the history. The `guix pull` after every time you update your fork will need to be a force-pull (--allow-downgrades [1]). > Of course, you can also keep your own fork unauthenticated, which > might be preferable if you only do local work anyway, but that's > besides the issue here. Yes, to be clear, I'm talking about the use-case where your fork is hosted remotely, and you or someone else needs to pull changes from it. For example, my prospective use case would be quickly bootstrapping Guix on a new machine - I build my own installation image, and I'd want it to pull from my fork. I can include my introduction into my installer, just like the official one. But if the introduction changes before I use my installer, then the first pull can't be authenticated. > This does not state how the additional introductions are used, if at > all. It may mean that the additional introductions are pointless other > than for blocking case 4. My bad, I guess I forgot to explain that. The purpose of the additional introductions is to make it so that signed commits from upstream Guix, or commits from other people's forks, can still be authenticated. As I mentioned above, the current design is not suited to this. To go a bit more into detail - we will accomplish authentication by doing a postorder traversal of the commit tree, considering the latest commit as the root node. We traverse its parents recursively until we reach a commit whose parent is one of the channel introductions (primary or additional). Then that commit and all its children are authenticated from the introduction that we encountered. In this way, every commit is authenticated from the introduction that is its most recent ancestor. > I think this might still hide a serious flaw. With the way *upstream* > authentication works. Let's flip the example in [6] around a little > bit and construct the following: > > -A---B---C---D > \ \ > \ \-E---F---💀 > \ / > \----G--H--I*-/ > > Both A and I* are introductory commits on their various branches. In > 💀, any committer who has valid keys in both F and I* can merge a > branch with unsigned commits, effectively voiding the invariant of > BCEF, e.g. by undoing any changes that happened there. Of course, they > can do so with signed commits as well, given that they have commit > access to the main repository, but the point still holds that they may > introduce unsigned commits to any fork where their key is valid in. So, my design enables an attacker who can make authorized signed commits to also introduce changes made in unsigned commits. Hmm. I don't think this compromises our current security guarantees, though? If the attacker can already make trusted commits, then any attack they can perform in the way you described can also be done directly with signed commits onto F, as you pointed out. And the latter way would be far simpler for them. Also, the branch they merged into would not contain any unsigned commits; the commit '💀' is still signed with a key authorized for F's branch. So at most, we can say that the attacker can introduce /changes made in/ unsigned commits, not 'introduce unsigned commits'. Once you manage to revoke their commit access, you'd just revert the '💀' commit and delete the GHI branch (which is the one that contains unsigned commits). The same way you'd recover from them directly making malicious changes on master. [1] https://issues.guix.gnu.org/41604#16-lineno14