git-authenticate: Ensure the target is a descendant of the introductory commit.

Fixes a bug whereby authentication of a commit *not* descending from the
introductory commit could succeed, provided the commit verifies the
authorization invariant.

In the example below, A is a common ancestor of the introductory commit
I and of commit X.  Authentication of X would succeed, even though it is
not a descendant of I, as long as X is authorized according to the
'.guix-authorizations' in A:

   X   	 I
    \   /
      A

This is because, 'authenticate-repository' would not check whether X
descends from I, and the call (commit-difference X I) would return X.

In practice that only affects forks because it means that ancestors of
the introductory commit already contain a '.guix-authorizations' file.

* guix/git-authenticate.scm (authenticate-repository): Add call to
'commit-descendant?'.
* tests/channels.scm ("authenticate-channel, not a descendant of introductory commit"):
New test.
* tests/git-authenticate.scm ("authenticate-repository, target not a descendant of intro"):
New test.
* tests/guix-git-authenticate.sh: Expect earlier test to fail since
9549f0283a is not a descendant of
$intro_commit.  Add new test targeting an ancestor of the introductory
commit, and another test targeting the v1.2.0 commit.
* doc/guix.texi (Specifying Channel Authorizations): Add a sentence.
This commit is contained in:
Ludovic Courtès 2022-01-28 17:20:43 +01:00
parent 87d49346f3
commit ca87601dd9
No known key found for this signature in database
GPG key ID: 090B11993D9AEBB5
5 changed files with 136 additions and 6 deletions

View file

@ -1,6 +1,6 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2019, 2020 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2019, 2020, 2022 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
@ -525,6 +525,64 @@
#:keyring-reference-prefix "")
'failed))))))
(unless (gpg+git-available?) (test-skip 1))
(test-equal "authenticate-channel, not a descendant of introductory commit"
#t
(with-fresh-gnupg-setup (list %ed25519-public-key-file
%ed25519-secret-key-file
%ed25519-2-public-key-file
%ed25519-2-secret-key-file)
(with-temporary-git-repository directory
`((add ".guix-channel"
,(object->string
'(channel (version 0)
(keyring-reference "master"))))
(add ".guix-authorizations"
,(object->string
`(authorizations (version 0)
((,(key-fingerprint
%ed25519-public-key-file)
(name "Charlie"))))))
(add "signer.key" ,(call-with-input-file %ed25519-public-key-file
get-string-all))
(commit "first commit"
(signer ,(key-fingerprint %ed25519-public-key-file)))
(branch "alternate-branch")
(checkout "alternate-branch")
(add "something.txt" ,(random-text))
(commit "intro commit"
(signer ,(key-fingerprint %ed25519-public-key-file)))
(checkout "master")
(add "random" ,(random-text))
(commit "second commit"
(signer ,(key-fingerprint %ed25519-public-key-file))))
(with-repository directory repository
(let* ((commit1 (find-commit repository "first"))
(commit2 (find-commit repository "second"))
(commit0 (commit-lookup
repository
(reference-target
(branch-lookup repository "alternate-branch"))))
(intro (make-channel-introduction
(commit-id-string commit0)
(openpgp-public-key-fingerprint
(read-openpgp-packet
%ed25519-public-key-file))))
(channel (channel (name 'example)
(url (string-append "file://" directory))
(introduction intro))))
(guard (c ((formatted-message? c)
(and (string-contains (formatted-message-string c)
"not a descendant")
(equal? (formatted-message-arguments c)
(list
(oid->string (commit-id commit2))
(oid->string (commit-id commit0)))))))
(authenticate-channel channel directory
(commit-id-string commit2)
#:keyring-reference-prefix "")
'failed))))))
(unless (gpg+git-available?) (test-skip 1))
(test-equal "authenticate-channel, .guix-authorizations"
#t