Received: (at 50814) by debbugs.gnu.org; 18 Oct 2021 15:58:12 +0000
From debbugs-submit-bounces@debbugs.gnu.org Mon Oct 18 11:58:12 2021
Received: from localhost ([127.0.0.1]:48321 helo=debbugs.gnu.org)
by debbugs.gnu.org with esmtp (Exim 4.84_2)
(envelope-from <debbugs-submit-bounces@debbugs.gnu.org>)
id 1mcV1f-00070S-Cc
for submit@debbugs.gnu.org; Mon, 18 Oct 2021 11:58:12 -0400
Received: from mail-ed1-f47.google.com ([209.85.208.47]:36553)
by debbugs.gnu.org with esmtp (Exim 4.84_2)
(envelope-from <attila.lendvai@gmail.com>) id 1mcV1S-0006yx-TU
for 50814@debbugs.gnu.org; Mon, 18 Oct 2021 11:57:59 -0400
Received: by mail-ed1-f47.google.com with SMTP id d3so1096147edp.3
for <50814@debbugs.gnu.org>; Mon, 18 Oct 2021 08:57:58 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112;
h=sender:from:to:cc:subject:date:message-id:in-reply-to:references
:mime-version:content-transfer-encoding;
bh=mssy46LG9pPfC05ohM4Vd1lxt6KHdiMy0L/zng1m38U=;
b=AnwPvnX+jdrfgo7FpAXZUHKCsSj9sKfjKm5lQDYj5uOF3sZiXxGT3yIGr4jbTJCKPG
luZJ6FOiBVP1tflbHZ44og/jcuSm2+ZMH+5guVyLbZFzjXC8itXSrnt0iLW1tMoUJSpT
K0lfuNgU5PZOr0qNLn3L4UcNUq3MXaXJ2qjzIpVdvgcKsBNZTKGgONjn7MQ3FG+DbyKN
5psWUSLtgNjC7S3qrW/r5+sujm/jmYOhSX2zhgAVMIjtpNifzKgQSWV9iEqHpYJJIRGc
hiOamz4YGVO9uM0RwrcyEeICtSZex48e9hxB4pOtkzJAVQeBvw/P324FWX1oZqofYm5K
9b3A==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=1e100.net; s=20210112;
h=x-gm-message-state:sender:from:to:cc:subject:date:message-id
:in-reply-to:references:mime-version:content-transfer-encoding;
bh=mssy46LG9pPfC05ohM4Vd1lxt6KHdiMy0L/zng1m38U=;
b=zw5zIOcGvJ/ZNRg4YsTdiN0212z4M1FLnPVCeK8HeKWHMJC1o2KnOTpTWaCsmpiTIZ
tHOxuvIqVMTqsfaOF8fRxvGMqT8I9cG/6idzBoEHPdfjLqlZndxnZlMkiAZ1dPjKLSUm
bylhpQ7xMvI7wRtVrEy1n3I/dlqd6jQzn7SgHDV6tauXpm4nyqs0cEj/ypoZpqlYnOUI
mwHLmMnPWP4YiVorFdJSXaQuLfIz0qNFJn+PvPN7K73HoFYVwYqY8yrX6yjevZkGmRF7
FZEQt4NIBGLp17VkaN7uToXEhvUzD3tvScjdd7SkdB4lJSbKiDB0GJ5DRumnocXLgOgD
QyMA==
X-Gm-Message-State: AOAM531dCLzBq3mHsfyEzU93+lB0V7aDiPCg9xFavblxSe4OludOSd8b
VM4RHtxegFiE0UMd96ezVHDwCxsjeVk=
X-Google-Smtp-Source: ABdhPJw58/43/P+URwtPfEa0NS9qndzH4pg8YLUju3JWKndadlofqrDOKHu+05TOp2VlVyyT0DcCDw==
X-Received: by 2002:a17:906:712:: with SMTP id
y18mr29707425ejb.408.1634572671460;
Mon, 18 Oct 2021 08:57:51 -0700 (PDT)
Received: from localhost.localdomain
([2a02:ab88:3710:6480:8fb4:66e9:57c0:8a0a])
by smtp.gmail.com with ESMTPSA id n22sm8762059eja.120.2021.10.18.08.57.50
(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
Mon, 18 Oct 2021 08:57:51 -0700 (PDT)
From: Attila Lendvai <attila@lendvai.name>
To: 50814@debbugs.gnu.org
Subject: [PATCH 4/5] guix: git-authenticate: Fix authenticate-repository.
Date: Mon, 18 Oct 2021 17:57:33 +0200
Message-Id: <20211018155734.5175-4-attila@lendvai.name>
X-Mailer: git-send-email 2.33.0
In-Reply-To: <20211018155734.5175-1-attila@lendvai.name>
References: <20211018155734.5175-1-attila@lendvai.name>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: 0.5 (/)
X-Debbugs-Envelope-To: 50814
Cc: Attila Lendvai <attila@lendvai.name>
X-BeenThere: debbugs-submit@debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>,
<mailto:debbugs-submit-request@debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit@debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request@debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>,
<mailto:debbugs-submit-request@debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces@debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces@debbugs.gnu.org>
X-Spam-Score: -0.5 (/)
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.
Signal a continuable compound-condition (with type &warning included) 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 | 158 +++++++++++++++++++++++---------------
2 files changed, 96 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 @@ (define (make-reporter start-commit end-commit 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..a667863d65 100644
--- a/guix/git-authenticate.scm
+++ b/guix/git-authenticate.scm
@@ -30,6 +30,7 @@ (define-module (guix git-authenticate)
#: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)
@@ -37,7 +38,10 @@ (define-module (guix git-authenticate)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
#:use-module (rnrs bytevectors)
+ #:use-module ((rnrs exceptions)
+ #:select (raise-continuable))
#: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 +163,12 @@ (define (read-authorizations port)
(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-value)
+ "Return the list of authorized key fingerprints in REPOSITORY as encoded in
+the '.guix-authorizations' file at the point denoted by COMMIT. If the file is
+not present, then assert that it has never been there (i.e. do not allow
+its removal), and return DEFAULT-VALUE."
+
(define (parents-have-authorizations-file? commit)
;; Return true if at least one of the parents of COMMIT has the
;; '.guix-authorizations' file.
@@ -185,28 +190,35 @@ (define (assert-parents-lack-authorizations commit)
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-VALUE.
+ (assert-parents-lack-authorizations commit)
+ default-value)
+ (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 +248,8 @@ (define signing-key
(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 +368,8 @@ (define (repository-cache-key repository)
(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 +377,26 @@ (define actual-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=?)
+ (raise-continuable
+ (make-compound-condition
+ (condition (&warning))
+ (formatted-message (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 +406,12 @@ (define* (authenticate-repository repository start signer
(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 +420,10 @@ (define* (authenticate-repository repository start signer
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 +433,37 @@ (define keyring
(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
Debbugs is free software and licensed under the terms of the
GNU Public License version 2. The current version can be
obtained from https://bugs.debian.org/debbugs-source/.