From: Attila Lendvai <attila@lendvai.name>
To: 50814@debbugs.gnu.org
Cc: Attila Lendvai <attila@lendvai.name>
Subject: [bug#50814] [PATCH 4/4] guix: git-authenticate: Fix authenticate-repository.
Date: Tue, 28 Sep 2021 03:05:38 +0200 [thread overview]
Message-ID: <20210928010537.4241-4-attila@lendvai.name> (raw)
In-Reply-To: <20210928010537.4241-1-attila@lendvai.name>
Always verify the channel introduction commit, so that no commit can slip
through that was signed with a different key.
Always update the cache, because it affects the behavior of later calls.
Warn when a channel introduction commit doesn't also update the
'.guix-authentications' file.
* guix/git-authenticate.scm (authenticate-commit): Reword and extend the error
message to point to the relevant part of the manual.
(authenticate-repository): Eliminate optimizations to make the code path less
dependent on the input. Always trust the intro-commit itself. Always call
verify-introductory-commit.
(verify-introductory-commit): Check if the commit contains the key that was
used to sign it, and issue a warning otherwise. This is to avoid the confusion
caused by only the *second* commit yielding an error, because intro-commits
are always trusted.
(authenticate-commit): Clarify error message.
(authorized-keys-at-commit): Factored out to the toplevel from
commit-authorized-keys.
---
guix/channels.scm | 4 +-
guix/git-authenticate.scm | 153 ++++++++++++++++++++++----------------
2 files changed, 91 insertions(+), 66 deletions(-)
diff --git a/guix/channels.scm b/guix/channels.scm
index e4e0428eb5..b84064537f 100644
--- a/guix/channels.scm
+++ b/guix/channels.scm
@@ -347,8 +347,8 @@ commits)...~%")
(progress-reporter/bar (length commits)))
(define authentic-commits
- ;; Consider the currently-used commit of CHANNEL as authentic so
- ;; authentication can skip it and all its closure.
+ ;; Optimization: consider the currently-used commit of CHANNEL as
+ ;; authentic, so that authentication can skip it and all its closure.
(match (find (lambda (candidate)
(eq? (channel-name candidate) (channel-name channel)))
(current-channels))
diff --git a/guix/git-authenticate.scm b/guix/git-authenticate.scm
index ab3fcd8b2f..713642d2ea 100644
--- a/guix/git-authenticate.scm
+++ b/guix/git-authenticate.scm
@@ -30,6 +30,7 @@
#:select (cache-directory with-atomic-file-output))
#:use-module ((guix build utils)
#:select (mkdir-p))
+ #:use-module (guix diagnostics)
#:use-module (guix progress)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-11)
@@ -38,6 +39,7 @@
#:use-module (srfi srfi-35)
#:use-module (rnrs bytevectors)
#:use-module (rnrs io ports)
+ #:use-module (ice-9 exceptions)
#:use-module (ice-9 match)
#:autoload (ice-9 pretty-print) (pretty-print)
#:export (read-authorizations
@@ -159,11 +161,10 @@ return a list of authorized fingerprints."
(string-downcase (string-filter char-set:graphic fingerprint))))
fingerprints))))
-(define* (commit-authorized-keys repository commit
- #:optional (default-authorizations '()))
- "Return the list of OpenPGP fingerprints authorized to sign COMMIT, based on
-authorizations listed in its parent commits. If one of the parent commits
-does not specify anything, fall back to DEFAULT-AUTHORIZATIONS."
+(define (authorized-keys-at-commit repository commit default-authorizations)
+ "Return the list of authorized key fingerprints from the '.guix-authorizations'
+file at the given commit."
+
(define (parents-have-authorizations-file? commit)
;; Return true if at least one of the parents of COMMIT has the
;; '.guix-authorizations' file.
@@ -185,28 +186,35 @@ does not specify anything, fall back to DEFAULT-AUTHORIZATIONS."
to remove '.guix-authorizations' file")
(oid->string (commit-id commit)))))))
- (define (commit-authorizations commit)
- (catch 'git-error
- (lambda ()
- (let* ((tree (commit-tree commit))
- (entry (tree-entry-bypath tree ".guix-authorizations"))
- (blob (blob-lookup repository (tree-entry-id entry))))
- (read-authorizations
- (open-bytevector-input-port (blob-content blob)))))
- (lambda (key error)
- (if (= (git-error-code error) GIT_ENOTFOUND)
- (begin
- ;; Prevent removal of '.guix-authorizations' since it would make
- ;; it trivial to force a fallback to DEFAULT-AUTHORIZATIONS.
- (assert-parents-lack-authorizations commit)
- default-authorizations)
- (throw key error)))))
+ (catch 'git-error
+ (lambda ()
+ (let* ((tree (commit-tree commit))
+ (entry (tree-entry-bypath tree ".guix-authorizations"))
+ (blob (blob-lookup repository (tree-entry-id entry))))
+ (read-authorizations
+ (open-bytevector-input-port (blob-content blob)))))
+ (lambda (key error)
+ (if (= (git-error-code error) GIT_ENOTFOUND)
+ (begin
+ ;; Prevent removal of '.guix-authorizations' since it would make
+ ;; it trivial to force a fallback to DEFAULT-AUTHORIZATIONS.
+ (assert-parents-lack-authorizations commit)
+ default-authorizations)
+ (throw key error)))))
+(define* (commit-authorized-keys repository commit
+ #:optional (default-authorizations '()))
+ "Return the list of OpenPGP fingerprints authorized to sign COMMIT, based on
+authorizations listed in its parent commits. If one of the parent commits
+does not specify anything, fall back to DEFAULT-AUTHORIZATIONS."
(match (commit-parents commit)
(() default-authorizations)
(parents
(apply lset-intersection bytevector=?
- (map commit-authorizations parents)))))
+ (map (lambda (commit)
+ (authorized-keys-at-commit repository commit
+ default-authorizations))
+ parents)))))
(define* (authenticate-commit repository commit keyring
#:key (default-authorizations '()))
@@ -236,8 +244,8 @@ not specify anything, fall back to DEFAULT-AUTHORIZATIONS."
(condition
(&unauthorized-commit-error (commit id)
(signing-key signing-key)))
- (formatted-message (G_ "commit ~a not signed by an authorized \
-key: ~a")
+ (formatted-message (G_ "commit ~a is signed by an unauthorized \
+key: ~a\nSee info guix \"Specifying Channel Authorizations\".")
(oid->string id)
(openpgp-format-fingerprint
(openpgp-public-key-fingerprint
@@ -356,7 +364,8 @@ authenticated (only COMMIT-ID is written to cache, though)."
(base64-encode
(sha256 (string->utf8 (repository-directory repository))))))
-(define (verify-introductory-commit repository keyring commit expected-signer)
+(define (verify-introductory-commit repository commit expected-signer keyring
+ authorizations)
"Look up COMMIT in REPOSITORY, and raise an exception if it is not signed by
EXPECTED-SIGNER."
(define actual-signer
@@ -364,13 +373,25 @@ EXPECTED-SIGNER."
(commit-signing-key repository (commit-id commit) keyring)))
(unless (bytevector=? expected-signer actual-signer)
- (raise (formatted-message (G_ "initial commit ~a is signed by '~a' \
+ (raise (make-compound-condition
+ (condition (&unauthorized-commit-error (commit (commit-id commit))
+ (signing-key actual-signer)))
+ (formatted-message (G_ "initial commit ~a is signed by '~a' \
instead of '~a'")
- (oid->string (commit-id commit))
- (openpgp-format-fingerprint actual-signer)
- (openpgp-format-fingerprint expected-signer)))))
-
-(define* (authenticate-repository repository start signer
+ (oid->string (commit-id commit))
+ (openpgp-format-fingerprint actual-signer)
+ (openpgp-format-fingerprint expected-signer)))))
+ (unless (member actual-signer
+ (authorized-keys-at-commit repository commit authorizations)
+ bytevector=?)
+ ;; FIXME Is this the right way to tell the user about this situation? It
+ ;; would also be nice if the tests could assert for this warning.
+ (warning (G_ "initial commit ~a does not add \
+the key it is signed with (~a) to the '.guix-authorizations' file.")
+ (oid->string (commit-id commit))
+ (openpgp-format-fingerprint actual-signer))))
+
+(define* (authenticate-repository repository intro-commit-hash intro-signer
#:key
(keyring-reference "keyring")
(cache-key (repository-cache-key repository))
@@ -380,11 +401,12 @@ instead of '~a'")
(historical-authorizations '())
(make-reporter
(const progress-reporter/silent)))
- "Authenticate REPOSITORY up to commit END, an OID. Authentication starts
-with commit START, an OID, which must be signed by SIGNER; an exception is
-raised if that is not the case. Commits listed in AUTHENTIC-COMMITS and their
-closure are considered authentic. Return an alist mapping OpenPGP public keys
-to the number of commits signed by that key that have been traversed.
+ "Authenticate REPOSITORY up to commit END, an OID. Authentication starts with
+commit INTRO-COMMIT-HASH, an OID, which must be signed by INTRO-SIGNER; an
+exception is raised if that is not the case. Commits listed in
+AUTHENTIC-COMMITS and their closure are considered authentic. Return an
+alist mapping OpenPGP public keys to the number of commits signed by that
+key that have been traversed.
The OpenPGP keyring is loaded from KEYRING-REFERENCE in REPOSITORY, where
KEYRING-REFERENCE is the name of a branch. The list of authenticated commits
@@ -393,8 +415,10 @@ is cached in the authentication cache under CACHE-KEY.
HISTORICAL-AUTHORIZATIONS must be a list of OpenPGP fingerprints (bytevectors)
denoting the authorized keys for commits whose parent lack the
'.guix-authorizations' file."
- (define start-commit
- (commit-lookup repository start))
+
+ (define intro-commit
+ (commit-lookup repository intro-commit-hash))
+
(define end-commit
(commit-lookup repository end))
@@ -404,36 +428,37 @@ denoting the authorized keys for commits whose parent lack the
(define authenticated-commits
;; Previously-authenticated commits that don't need to be checked again.
(filter-map (lambda (id)
+ ;; We need to tolerate when cached commits disappear due to
+ ;; --allow-downgrades.
(false-if-git-not-found
(commit-lookup repository (string->oid id))))
(append (previously-authenticated-commits cache-key)
- authentic-commits)))
+ authentic-commits
+ ;; The intro commit is unconditionally trusted.
+ (list (oid->string intro-commit-hash)))))
(define commits
;; Commits to authenticate, excluding the closure of
;; AUTHENTICATED-COMMITS.
- (commit-difference end-commit start-commit
- authenticated-commits))
-
- ;; When COMMITS is empty, it's because END-COMMIT is in the closure of
- ;; START-COMMIT and/or AUTHENTICATED-COMMITS, in which case it's known to
- ;; be authentic already.
- (if (null? commits)
- '()
- (let ((reporter (make-reporter start-commit end-commit commits)))
- ;; If it's our first time, verify START-COMMIT's signature.
- (when (null? authenticated-commits)
- (verify-introductory-commit repository keyring
- start-commit signer))
-
- (let ((stats (call-with-progress-reporter reporter
- (lambda (report)
- (authenticate-commits repository commits
- #:keyring keyring
- #:default-authorizations
- historical-authorizations
- #:report-progress report)))))
- (cache-authenticated-commit cache-key
- (oid->string (commit-id end-commit)))
-
- stats))))
+ (commit-difference end-commit intro-commit
+ authenticated-commits))
+
+ (verify-introductory-commit repository intro-commit
+ intro-signer keyring
+ historical-authorizations)
+
+ (let* ((reporter (make-reporter intro-commit end-commit commits))
+ (stats (call-with-progress-reporter reporter
+ (lambda (report)
+ (authenticate-commits repository commits
+ #:keyring keyring
+ #:default-authorizations
+ historical-authorizations
+ #:report-progress report)))))
+ ;; Note that this will make the then current end commit of any channel,
+ ;; that has been used/trusted in the past with a channel introduction,
+ ;; remain trusted until the cache is cleared.
+ (cache-authenticated-commit cache-key
+ (oid->string (commit-id end-commit)))
+
+ stats))
--
2.33.0
next prev parent reply other threads:[~2021-09-28 1:08 UTC|newest]
Thread overview: 38+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-09-26 10:19 [bug#50814] [PATCH] guix: git-authenticate: Also authenticate the channel intro commit Attila Lendvai
2021-09-26 18:02 ` Leo Famulari
2021-10-09 13:44 ` Ludovic Courtès
2021-10-12 15:17 ` Leo Famulari
2021-09-26 18:14 ` Maxime Devos
2021-09-27 18:01 ` Attila Lendvai
2021-09-27 18:45 ` Attila Lendvai
2021-09-28 10:02 ` Maxime Devos
2021-09-28 1:05 ` [bug#50814] [PATCH 1/4] tests: Smarten up git repository testing framework Attila Lendvai
2021-09-28 1:05 ` [bug#50814] [PATCH 2/4] tests: Move keys into ./tests/keys/ and add a third ed25519 key Attila Lendvai
2021-09-28 1:05 ` [bug#50814] [PATCH 3/4] tests: Add failing test for .guix-authorizations and channel intro Attila Lendvai
2021-09-29 13:58 ` Maxime Devos
2021-09-28 1:05 ` Attila Lendvai [this message]
2021-09-28 16:24 ` [bug#50814] [PATCH 1/5] tests: Smarten up git repository testing framework Attila Lendvai
2021-09-28 16:24 ` [bug#50814] [PATCH 2/5] tests: Move keys into ./tests/keys/ and add a third ed25519 key Attila Lendvai
2021-09-28 16:24 ` [bug#50814] [PATCH 3/5] tests: Add failing test for .guix-authorizations and channel intro Attila Lendvai
2021-09-28 16:24 ` [bug#50814] [PATCH 4/5] guix: Prepare the UI for continuable &warning exceptions Attila Lendvai
2021-09-29 14:13 ` Maxime Devos
2021-09-29 14:50 ` Attila Lendvai
2021-09-29 20:36 ` Maxime Devos
2021-09-29 21:22 ` Attila Lendvai
2021-09-29 22:03 ` Maxime Devos
2021-09-28 16:24 ` [bug#50814] [PATCH 5/5] guix: git-authenticate: Fix authenticate-repository Attila Lendvai
2021-09-29 23:14 ` Maxime Devos
2021-10-09 13:53 ` [bug#50814] [PATCH] guix: git-authenticate: Also authenticate the channel intro commit Ludovic Courtès
2021-10-09 15:31 ` Attila Lendvai
2021-10-12 9:39 ` Ludovic Courtès
2021-10-17 10:09 ` Attila Lendvai
2021-10-18 9:10 ` Ludovic Courtès
2021-10-18 15:27 ` Attila Lendvai
2021-10-10 14:15 ` [bug#50814] [PATCH] tests: Add test for .guix-authorizations and channel intro Attila Lendvai
2021-10-18 15:57 ` [bug#50814] [PATCH 1/5] tests: Smarten up git repository testing framework Attila Lendvai
2021-10-18 15:57 ` [bug#50814] [PATCH 2/5] tests: Move keys into ./tests/keys/ and add a third ed25519 key Attila Lendvai
2021-10-18 15:57 ` [bug#50814] [PATCH 3/5] guix: Prepare the UI for continuable &warning exceptions Attila Lendvai
2021-10-18 15:57 ` [bug#50814] [PATCH 4/5] guix: git-authenticate: Fix authenticate-repository Attila Lendvai
2021-10-18 15:57 ` [bug#50814] [PATCH 5/5] tests: Add test for .guix-authorizations and channel intro Attila Lendvai
2022-01-10 14:53 ` [bug#50814] [PATCH] guix: git-authenticate: Also authenticate the channel intro commit Ludovic Courtès
2022-04-04 6:47 ` Attila Lendvai
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210928010537.4241-4-attila@lendvai.name \
--to=attila@lendvai.name \
--cc=50814@debbugs.gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/guix.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.