mirror of
https://git.in.rschanz.org/ryan77627/guix.git
synced 2024-11-07 07:26:13 -05:00
channels: 'latest-channel-instance' authenticates Git checkouts.
Fixes <https://bugs.gnu.org/22883>. * guix/channels.scm (<channel>)[introduction]: New field. (<channel-introduction>): New record type. (%guix-channel-introduction): New variable. (%default-channels): Use it. (<channel-metadata>)[keyring-reference]: New field. (%default-keyring-reference): New variable. (read-channel-metadata, read-channel-metadata-from-source): Initialize the 'keyring-reference' field. (commit-short-id, verify-introductory-commit) (authenticate-channel): New procedures. (latest-channel-instance): Call 'authenticate-channel' when CHANNEL has an introduction. * tests/channels.scm (gpg+git-available?, commit-id-string): New procedures. ("authenticate-channel, wrong first commit signer"): ("authenticate-channel, .guix-authorizations"): New tests. * doc/guix.texi (Invoking guix pull): Mention authentication.
This commit is contained in:
parent
1e2b9bf2d4
commit
43badf261f
4 changed files with 304 additions and 7 deletions
|
@ -99,6 +99,7 @@
|
||||||
(eval . (put 'eventually 'scheme-indent-function 1))
|
(eval . (put 'eventually 'scheme-indent-function 1))
|
||||||
|
|
||||||
(eval . (put 'call-with-progress-reporter 'scheme-indent-function 1))
|
(eval . (put 'call-with-progress-reporter 'scheme-indent-function 1))
|
||||||
|
(eval . (put 'with-repository 'scheme-indent-function 2))
|
||||||
(eval . (put 'with-temporary-git-repository 'scheme-indent-function 2))
|
(eval . (put 'with-temporary-git-repository 'scheme-indent-function 2))
|
||||||
(eval . (put 'with-environment-variables 'scheme-indent-function 1))
|
(eval . (put 'with-environment-variables 'scheme-indent-function 1))
|
||||||
(eval . (put 'with-fresh-gnupg-setup 'scheme-indent-function 1))
|
(eval . (put 'with-fresh-gnupg-setup 'scheme-indent-function 1))
|
||||||
|
|
|
@ -3721,13 +3721,17 @@ this option is primarily useful when the daemon was running with
|
||||||
@cindex updating Guix
|
@cindex updating Guix
|
||||||
@cindex @command{guix pull}
|
@cindex @command{guix pull}
|
||||||
@cindex pull
|
@cindex pull
|
||||||
|
@cindex security, @command{guix pull}
|
||||||
|
@cindex authenticity, of code obtained with @command{guix pull}
|
||||||
Packages are installed or upgraded to the latest version available in
|
Packages are installed or upgraded to the latest version available in
|
||||||
the distribution currently available on your local machine. To update
|
the distribution currently available on your local machine. To update
|
||||||
that distribution, along with the Guix tools, you must run @command{guix
|
that distribution, along with the Guix tools, you must run @command{guix
|
||||||
pull}: the command downloads the latest Guix source code and package
|
pull}: the command downloads the latest Guix source code and package
|
||||||
descriptions, and deploys it. Source code is downloaded from a
|
descriptions, and deploys it. Source code is downloaded from a
|
||||||
@uref{https://git-scm.com, Git} repository, by default the official
|
@uref{https://git-scm.com, Git} repository, by default the official
|
||||||
GNU@tie{}Guix repository, though this can be customized.
|
GNU@tie{}Guix repository, though this can be customized. @command{guix
|
||||||
|
pull} ensures that the code it downloads is @emph{authentic} by
|
||||||
|
verifying that commits are signed by Guix developers.
|
||||||
|
|
||||||
Specifically, @command{guix pull} downloads code from the @dfn{channels}
|
Specifically, @command{guix pull} downloads code from the @dfn{channels}
|
||||||
(@pxref{Channels}) specified by one of the followings, in this order:
|
(@pxref{Channels}) specified by one of the followings, in this order:
|
||||||
|
|
|
@ -21,6 +21,11 @@
|
||||||
(define-module (guix channels)
|
(define-module (guix channels)
|
||||||
#:use-module (git)
|
#:use-module (git)
|
||||||
#:use-module (guix git)
|
#:use-module (guix git)
|
||||||
|
#:use-module (guix git-authenticate)
|
||||||
|
#:use-module ((guix openpgp)
|
||||||
|
#:select (openpgp-public-key-fingerprint
|
||||||
|
openpgp-format-fingerprint))
|
||||||
|
#:use-module (guix base16)
|
||||||
#:use-module (guix records)
|
#:use-module (guix records)
|
||||||
#:use-module (guix gexp)
|
#:use-module (guix gexp)
|
||||||
#:use-module (guix modules)
|
#:use-module (guix modules)
|
||||||
|
@ -28,6 +33,7 @@ (define-module (guix channels)
|
||||||
#:use-module (guix monads)
|
#:use-module (guix monads)
|
||||||
#:use-module (guix profiles)
|
#:use-module (guix profiles)
|
||||||
#:use-module (guix packages)
|
#:use-module (guix packages)
|
||||||
|
#:use-module (guix progress)
|
||||||
#:use-module (guix derivations)
|
#:use-module (guix derivations)
|
||||||
#:use-module (guix combinators)
|
#:use-module (guix combinators)
|
||||||
#:use-module (guix diagnostics)
|
#:use-module (guix diagnostics)
|
||||||
|
@ -48,17 +54,23 @@ (define-module (guix channels)
|
||||||
#:autoload (guix self) (whole-package make-config.scm)
|
#:autoload (guix self) (whole-package make-config.scm)
|
||||||
#:autoload (guix inferior) (gexp->derivation-in-inferior) ;FIXME: circular dep
|
#:autoload (guix inferior) (gexp->derivation-in-inferior) ;FIXME: circular dep
|
||||||
#:autoload (guix quirks) (%quirks %patches applicable-patch? apply-patch)
|
#:autoload (guix quirks) (%quirks %patches applicable-patch? apply-patch)
|
||||||
|
#:use-module (ice-9 format)
|
||||||
#:use-module (ice-9 match)
|
#:use-module (ice-9 match)
|
||||||
#:use-module (ice-9 vlist)
|
#:use-module (ice-9 vlist)
|
||||||
#:use-module ((ice-9 rdelim) #:select (read-string))
|
#:use-module ((ice-9 rdelim) #:select (read-string))
|
||||||
|
#:use-module ((rnrs bytevectors) #:select (bytevector=?))
|
||||||
#:export (channel
|
#:export (channel
|
||||||
channel?
|
channel?
|
||||||
channel-name
|
channel-name
|
||||||
channel-url
|
channel-url
|
||||||
channel-branch
|
channel-branch
|
||||||
channel-commit
|
channel-commit
|
||||||
|
channel-introduction
|
||||||
channel-location
|
channel-location
|
||||||
|
|
||||||
|
channel-introduction?
|
||||||
|
;; <channel-introduction> accessors purposefully omitted for now.
|
||||||
|
|
||||||
%default-channels
|
%default-channels
|
||||||
guix-channel?
|
guix-channel?
|
||||||
|
|
||||||
|
@ -67,6 +79,7 @@ (define-module (guix channels)
|
||||||
channel-instance-commit
|
channel-instance-commit
|
||||||
channel-instance-checkout
|
channel-instance-checkout
|
||||||
|
|
||||||
|
authenticate-channel
|
||||||
latest-channel-instances
|
latest-channel-instances
|
||||||
checkout->channel-instance
|
checkout->channel-instance
|
||||||
latest-channel-derivation
|
latest-channel-derivation
|
||||||
|
@ -104,15 +117,44 @@ (define-record-type* <channel> channel make-channel
|
||||||
(url channel-url)
|
(url channel-url)
|
||||||
(branch channel-branch (default "master"))
|
(branch channel-branch (default "master"))
|
||||||
(commit channel-commit (default #f))
|
(commit channel-commit (default #f))
|
||||||
|
(introduction channel-introduction (default #f))
|
||||||
(location channel-location
|
(location channel-location
|
||||||
(default (current-source-location)) (innate)))
|
(default (current-source-location)) (innate)))
|
||||||
|
|
||||||
|
;; Channel introductions. A "channel introduction" provides a commit/signer
|
||||||
|
;; pair that specifies the first commit of the authentication process as well
|
||||||
|
;; as its signer's fingerprint. The pair must be signed by the signer of that
|
||||||
|
;; commit so that only them may emit this introduction. Introductions are
|
||||||
|
;; used to bootstrap trust in a channel.
|
||||||
|
(define-record-type <channel-introduction>
|
||||||
|
(make-channel-introduction first-signed-commit first-commit-signer
|
||||||
|
signature)
|
||||||
|
channel-introduction?
|
||||||
|
(first-signed-commit channel-introduction-first-signed-commit) ;hex string
|
||||||
|
(first-commit-signer channel-introduction-first-commit-signer) ;bytevector
|
||||||
|
(signature channel-introduction-signature)) ;string
|
||||||
|
|
||||||
|
(define %guix-channel-introduction
|
||||||
|
;; Introduction of the official 'guix channel. The chosen commit is the
|
||||||
|
;; first one that introduces '.guix-authorizations' on the 'staging'
|
||||||
|
;; branch that was eventually merged in 'master'. Any branch starting
|
||||||
|
;; before that commit cannot be merged or it will be rejected by 'guix pull'
|
||||||
|
;; & co.
|
||||||
|
(make-channel-introduction
|
||||||
|
"9edb3f66fd807b096b48283debdcddccfea34bad" ;2020-05-26
|
||||||
|
(base16-string->bytevector
|
||||||
|
(string-downcase
|
||||||
|
(string-filter char-set:hex-digit ;mbakke
|
||||||
|
"BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA")))
|
||||||
|
#f)) ;TODO: Add an intro signature so it can be exported.
|
||||||
|
|
||||||
(define %default-channels
|
(define %default-channels
|
||||||
;; Default list of channels.
|
;; Default list of channels.
|
||||||
(list (channel
|
(list (channel
|
||||||
(name 'guix)
|
(name 'guix)
|
||||||
(branch "master")
|
(branch "master")
|
||||||
(url "https://git.savannah.gnu.org/git/guix.git"))))
|
(url "https://git.savannah.gnu.org/git/guix.git")
|
||||||
|
(introduction %guix-channel-introduction))))
|
||||||
|
|
||||||
(define (guix-channel? channel)
|
(define (guix-channel? channel)
|
||||||
"Return true if CHANNEL is the 'guix' channel."
|
"Return true if CHANNEL is the 'guix' channel."
|
||||||
|
@ -126,11 +168,16 @@ (define-record-type <channel-instance>
|
||||||
(checkout channel-instance-checkout))
|
(checkout channel-instance-checkout))
|
||||||
|
|
||||||
(define-record-type <channel-metadata>
|
(define-record-type <channel-metadata>
|
||||||
(channel-metadata directory dependencies news-file)
|
(channel-metadata directory dependencies news-file keyring-reference)
|
||||||
channel-metadata?
|
channel-metadata?
|
||||||
(directory channel-metadata-directory) ;string with leading slash
|
(directory channel-metadata-directory) ;string with leading slash
|
||||||
(dependencies channel-metadata-dependencies) ;list of <channel>
|
(dependencies channel-metadata-dependencies) ;list of <channel>
|
||||||
(news-file channel-metadata-news-file)) ;string | #f
|
(news-file channel-metadata-news-file) ;string | #f
|
||||||
|
(keyring-reference channel-metadata-keyring-reference)) ;string
|
||||||
|
|
||||||
|
(define %default-keyring-reference
|
||||||
|
;; Default value of the 'keyring-reference' field.
|
||||||
|
"keyring")
|
||||||
|
|
||||||
(define (channel-reference channel)
|
(define (channel-reference channel)
|
||||||
"Return the \"reference\" for CHANNEL, an sexp suitable for
|
"Return the \"reference\" for CHANNEL, an sexp suitable for
|
||||||
|
@ -147,7 +194,10 @@ (define (read-channel-metadata port)
|
||||||
(('channel ('version 0) properties ...)
|
(('channel ('version 0) properties ...)
|
||||||
(let ((directory (and=> (assoc-ref properties 'directory) first))
|
(let ((directory (and=> (assoc-ref properties 'directory) first))
|
||||||
(dependencies (or (assoc-ref properties 'dependencies) '()))
|
(dependencies (or (assoc-ref properties 'dependencies) '()))
|
||||||
(news-file (and=> (assoc-ref properties 'news-file) first)))
|
(news-file (and=> (assoc-ref properties 'news-file) first))
|
||||||
|
(keyring-reference
|
||||||
|
(or (and=> (assoc-ref properties 'keyring-reference) first)
|
||||||
|
%default-keyring-reference)))
|
||||||
(channel-metadata
|
(channel-metadata
|
||||||
(cond ((not directory) "/") ;directory
|
(cond ((not directory) "/") ;directory
|
||||||
((string-prefix? "/" directory) directory)
|
((string-prefix? "/" directory) directory)
|
||||||
|
@ -164,7 +214,8 @@ (define (read-channel-metadata port)
|
||||||
(url url)
|
(url url)
|
||||||
(commit (get 'commit))))))
|
(commit (get 'commit))))))
|
||||||
dependencies)
|
dependencies)
|
||||||
news-file))) ;news-file
|
news-file
|
||||||
|
keyring-reference)))
|
||||||
((and ('channel ('version version) _ ...) sexp)
|
((and ('channel ('version version) _ ...) sexp)
|
||||||
(raise (condition
|
(raise (condition
|
||||||
(&message (message "unsupported '.guix-channel' version"))
|
(&message (message "unsupported '.guix-channel' version"))
|
||||||
|
@ -188,7 +239,7 @@ (define (read-channel-metadata-from-source source)
|
||||||
read-channel-metadata))
|
read-channel-metadata))
|
||||||
(lambda args
|
(lambda args
|
||||||
(if (= ENOENT (system-error-errno args))
|
(if (= ENOENT (system-error-errno args))
|
||||||
(channel-metadata "/" '() #f)
|
(channel-metadata "/" '() #f %default-keyring-reference)
|
||||||
(apply throw args)))))
|
(apply throw args)))))
|
||||||
|
|
||||||
(define (channel-instance-metadata instance)
|
(define (channel-instance-metadata instance)
|
||||||
|
@ -212,6 +263,116 @@ (define (apply-patches checkout commit patches)
|
||||||
(apply-patch patch checkout))
|
(apply-patch patch checkout))
|
||||||
(loop rest)))))
|
(loop rest)))))
|
||||||
|
|
||||||
|
(define commit-short-id
|
||||||
|
(compose (cut string-take <> 7) oid->string commit-id))
|
||||||
|
|
||||||
|
(define (verify-introductory-commit repository introduction keyring)
|
||||||
|
"Raise an exception if the first commit described in INTRODUCTION doesn't
|
||||||
|
have the expected signer."
|
||||||
|
(define commit-id
|
||||||
|
(channel-introduction-first-signed-commit introduction))
|
||||||
|
|
||||||
|
(define actual-signer
|
||||||
|
(openpgp-public-key-fingerprint
|
||||||
|
(commit-signing-key repository (string->oid commit-id)
|
||||||
|
keyring)))
|
||||||
|
|
||||||
|
(define expected-signer
|
||||||
|
(channel-introduction-first-commit-signer introduction))
|
||||||
|
|
||||||
|
(unless (bytevector=? expected-signer actual-signer)
|
||||||
|
(raise (condition
|
||||||
|
(&message
|
||||||
|
(message (format #f (G_ "initial commit ~a is signed by '~a' \
|
||||||
|
instead of '~a'")
|
||||||
|
commit-id
|
||||||
|
(openpgp-format-fingerprint actual-signer)
|
||||||
|
(openpgp-format-fingerprint expected-signer))))))))
|
||||||
|
|
||||||
|
(define* (authenticate-channel channel checkout commit
|
||||||
|
#:key (keyring-reference-prefix "origin/"))
|
||||||
|
"Authenticate the given COMMIT of CHANNEL, available at CHECKOUT, a
|
||||||
|
directory containing a CHANNEL checkout. Raise an error if authentication
|
||||||
|
fails."
|
||||||
|
;; XXX: Too bad we need to re-open CHECKOUT.
|
||||||
|
(with-repository checkout repository
|
||||||
|
(define start-commit
|
||||||
|
(commit-lookup repository
|
||||||
|
(string->oid
|
||||||
|
(channel-introduction-first-signed-commit
|
||||||
|
(channel-introduction channel)))))
|
||||||
|
|
||||||
|
(define end-commit
|
||||||
|
(commit-lookup repository (string->oid commit)))
|
||||||
|
|
||||||
|
(define cache-key
|
||||||
|
(string-append "channels/" (symbol->string (channel-name channel))))
|
||||||
|
|
||||||
|
(define keyring-reference
|
||||||
|
(channel-metadata-keyring-reference
|
||||||
|
(read-channel-metadata-from-source checkout)))
|
||||||
|
|
||||||
|
(define keyring
|
||||||
|
(load-keyring-from-reference repository
|
||||||
|
(string-append keyring-reference-prefix
|
||||||
|
keyring-reference)))
|
||||||
|
|
||||||
|
(define authenticated-commits
|
||||||
|
;; Previously-authenticated commits that don't need to be checked again.
|
||||||
|
(filter-map (lambda (id)
|
||||||
|
(false-if-exception
|
||||||
|
(commit-lookup repository (string->oid id))))
|
||||||
|
(previously-authenticated-commits cache-key)))
|
||||||
|
|
||||||
|
(define commits
|
||||||
|
;; Commits to authenticate, excluding the closure of
|
||||||
|
;; AUTHENTICATED-COMMITS.
|
||||||
|
(commit-difference end-commit start-commit
|
||||||
|
authenticated-commits))
|
||||||
|
|
||||||
|
(define reporter
|
||||||
|
(progress-reporter/bar (length commits)))
|
||||||
|
|
||||||
|
;; When COMMITS is empty, it's either because AUTHENTICATED-COMMITS
|
||||||
|
;; contains END-COMMIT or because END-COMMIT is not a descendant of
|
||||||
|
;; START-COMMIT. Check that.
|
||||||
|
(if (null? commits)
|
||||||
|
(match (commit-relation start-commit end-commit)
|
||||||
|
((or 'self 'ancestor 'descendant) #t) ;nothing to do!
|
||||||
|
('unrelated
|
||||||
|
(raise
|
||||||
|
(condition
|
||||||
|
(&message
|
||||||
|
(message
|
||||||
|
(format #f (G_ "'~a' is not related to introductory \
|
||||||
|
commit of channel '~a'~%")
|
||||||
|
(oid->string (commit-id end-commit))
|
||||||
|
(channel-name channel))))))))
|
||||||
|
(begin
|
||||||
|
(format (current-error-port)
|
||||||
|
(G_ "Authenticating channel '~a', \
|
||||||
|
commits ~a to ~a (~h new commits)...~%")
|
||||||
|
(channel-name channel)
|
||||||
|
(commit-short-id start-commit)
|
||||||
|
(commit-short-id end-commit)
|
||||||
|
(length commits))
|
||||||
|
|
||||||
|
;; If it's our first time, verify CHANNEL's introductory commit.
|
||||||
|
(when (null? authenticated-commits)
|
||||||
|
(verify-introductory-commit repository
|
||||||
|
(channel-introduction channel)
|
||||||
|
keyring))
|
||||||
|
|
||||||
|
(call-with-progress-reporter reporter
|
||||||
|
(lambda (report)
|
||||||
|
(authenticate-commits repository commits
|
||||||
|
#:keyring keyring
|
||||||
|
#:report-progress report)))
|
||||||
|
|
||||||
|
(cache-authenticated-commit cache-key
|
||||||
|
(oid->string
|
||||||
|
(commit-id end-commit)))))))
|
||||||
|
|
||||||
(define* (latest-channel-instance store channel
|
(define* (latest-channel-instance store channel
|
||||||
#:key (patches %patches)
|
#:key (patches %patches)
|
||||||
starting-commit)
|
starting-commit)
|
||||||
|
@ -225,6 +386,15 @@ (define (dot-git? file stat)
|
||||||
(update-cached-checkout (channel-url channel)
|
(update-cached-checkout (channel-url channel)
|
||||||
#:ref (channel-reference channel)
|
#:ref (channel-reference channel)
|
||||||
#:starting-commit starting-commit)))
|
#:starting-commit starting-commit)))
|
||||||
|
(if (channel-introduction channel)
|
||||||
|
(authenticate-channel channel checkout commit)
|
||||||
|
;; TODO: Warn for all the channels once the authentication interface
|
||||||
|
;; is public.
|
||||||
|
(when (guix-channel? channel)
|
||||||
|
(warning (G_ "channel '~a' lacks an introduction and \
|
||||||
|
cannot be authenticated~%")
|
||||||
|
(channel-name channel))))
|
||||||
|
|
||||||
(when (guix-channel? channel)
|
(when (guix-channel? channel)
|
||||||
;; Apply the relevant subset of PATCHES directly in CHECKOUT. This is
|
;; Apply the relevant subset of PATCHES directly in CHECKOUT. This is
|
||||||
;; safe to do because 'switch-to-ref' eventually does a hard reset.
|
;; safe to do because 'switch-to-ref' eventually does a hard reset.
|
||||||
|
|
|
@ -31,15 +31,28 @@ (define-module (test-channels)
|
||||||
#:use-module ((guix build utils) #:select (which))
|
#:use-module ((guix build utils) #:select (which))
|
||||||
#:use-module (git)
|
#:use-module (git)
|
||||||
#:use-module (guix git)
|
#:use-module (guix git)
|
||||||
|
#:use-module (guix git-authenticate)
|
||||||
|
#:use-module (guix openpgp)
|
||||||
#:use-module (guix tests git)
|
#:use-module (guix tests git)
|
||||||
|
#:use-module (guix tests gnupg)
|
||||||
#:use-module (srfi srfi-1)
|
#:use-module (srfi srfi-1)
|
||||||
#:use-module (srfi srfi-26)
|
#:use-module (srfi srfi-26)
|
||||||
#:use-module (srfi srfi-34)
|
#:use-module (srfi srfi-34)
|
||||||
#:use-module (srfi srfi-35)
|
#:use-module (srfi srfi-35)
|
||||||
#:use-module (srfi srfi-64)
|
#:use-module (srfi srfi-64)
|
||||||
|
#:use-module (rnrs bytevectors)
|
||||||
|
#:use-module (rnrs io ports)
|
||||||
#:use-module (ice-9 control)
|
#:use-module (ice-9 control)
|
||||||
#:use-module (ice-9 match))
|
#:use-module (ice-9 match))
|
||||||
|
|
||||||
|
(define (gpg+git-available?)
|
||||||
|
(and (which (git-command))
|
||||||
|
(which (gpg-command)) (which (gpgconf-command))))
|
||||||
|
|
||||||
|
(define commit-id-string
|
||||||
|
(compose oid->string commit-id))
|
||||||
|
|
||||||
|
|
||||||
(test-begin "channels")
|
(test-begin "channels")
|
||||||
|
|
||||||
(define* (make-instance #:key
|
(define* (make-instance #:key
|
||||||
|
@ -389,4 +402,113 @@ (define (find-commit* message)
|
||||||
(channel-news-for-commit channel commit5 commit1))
|
(channel-news-for-commit channel commit5 commit1))
|
||||||
'(#f "tag-for-first-news-entry")))))))
|
'(#f "tag-for-first-news-entry")))))))
|
||||||
|
|
||||||
|
(unless (gpg+git-available?) (test-skip 1))
|
||||||
|
(test-assert "authenticate-channel, wrong first commit signer"
|
||||||
|
(with-fresh-gnupg-setup (list %ed25519-public-key-file
|
||||||
|
%ed25519-secret-key-file
|
||||||
|
%ed25519bis-public-key-file
|
||||||
|
%ed25519bis-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))))
|
||||||
|
(with-repository directory repository
|
||||||
|
(let* ((commit1 (find-commit repository "first"))
|
||||||
|
(intro ((@@ (guix channels) make-channel-introduction)
|
||||||
|
(commit-id-string commit1)
|
||||||
|
(openpgp-public-key-fingerprint
|
||||||
|
(read-openpgp-packet
|
||||||
|
%ed25519bis-public-key-file)) ;different key
|
||||||
|
#f)) ;no signature
|
||||||
|
(channel (channel (name 'example)
|
||||||
|
(url (string-append "file://" directory))
|
||||||
|
(introduction intro))))
|
||||||
|
(guard (c ((message? c)
|
||||||
|
(->bool (string-contains (condition-message c)
|
||||||
|
"initial commit"))))
|
||||||
|
(authenticate-channel channel directory
|
||||||
|
(commit-id-string commit1)
|
||||||
|
#:keyring-reference-prefix "")
|
||||||
|
'failed))))))
|
||||||
|
|
||||||
|
(unless (gpg+git-available?) (test-skip 1))
|
||||||
|
(test-assert "authenticate-channel, .guix-authorizations"
|
||||||
|
(with-fresh-gnupg-setup (list %ed25519-public-key-file
|
||||||
|
%ed25519-secret-key-file
|
||||||
|
%ed25519bis-public-key-file
|
||||||
|
%ed25519bis-secret-key-file)
|
||||||
|
(with-temporary-git-repository directory
|
||||||
|
`((add ".guix-channel"
|
||||||
|
,(object->string
|
||||||
|
'(channel (version 0)
|
||||||
|
(keyring-reference "channel-keyring"))))
|
||||||
|
(add ".guix-authorizations"
|
||||||
|
,(object->string
|
||||||
|
`(authorizations (version 0)
|
||||||
|
((,(key-fingerprint
|
||||||
|
%ed25519-public-key-file)
|
||||||
|
(name "Charlie"))))))
|
||||||
|
(commit "zeroth commit")
|
||||||
|
(add "a.txt" "A")
|
||||||
|
(commit "first commit"
|
||||||
|
(signer ,(key-fingerprint %ed25519-public-key-file)))
|
||||||
|
(add "b.txt" "B")
|
||||||
|
(commit "second commit"
|
||||||
|
(signer ,(key-fingerprint %ed25519-public-key-file)))
|
||||||
|
(add "c.txt" "C")
|
||||||
|
(commit "third commit"
|
||||||
|
(signer ,(key-fingerprint %ed25519bis-public-key-file)))
|
||||||
|
(branch "channel-keyring")
|
||||||
|
(checkout "channel-keyring")
|
||||||
|
(add "signer.key" ,(call-with-input-file %ed25519-public-key-file
|
||||||
|
get-string-all))
|
||||||
|
(add "other.key" ,(call-with-input-file %ed25519bis-public-key-file
|
||||||
|
get-string-all))
|
||||||
|
(commit "keyring commit")
|
||||||
|
(checkout "master"))
|
||||||
|
(with-repository directory repository
|
||||||
|
(let* ((commit1 (find-commit repository "first"))
|
||||||
|
(commit2 (find-commit repository "second"))
|
||||||
|
(commit3 (find-commit repository "third"))
|
||||||
|
(intro ((@@ (guix channels) make-channel-introduction)
|
||||||
|
(commit-id-string commit1)
|
||||||
|
(openpgp-public-key-fingerprint
|
||||||
|
(read-openpgp-packet
|
||||||
|
%ed25519-public-key-file))
|
||||||
|
#f)) ;no signature
|
||||||
|
(channel (channel (name 'example)
|
||||||
|
(url (string-append "file://" directory))
|
||||||
|
(introduction intro))))
|
||||||
|
;; COMMIT1 and COMMIT2 are fine.
|
||||||
|
(and (authenticate-channel channel directory
|
||||||
|
(commit-id-string commit2)
|
||||||
|
#:keyring-reference-prefix "")
|
||||||
|
|
||||||
|
;; COMMIT3 is signed by an unauthorized key according to its
|
||||||
|
;; parent's '.guix-authorizations' file.
|
||||||
|
(guard (c ((unauthorized-commit-error? c)
|
||||||
|
(and (oid=? (git-authentication-error-commit c)
|
||||||
|
(commit-id commit3))
|
||||||
|
(bytevector=?
|
||||||
|
(openpgp-public-key-fingerprint
|
||||||
|
(unauthorized-commit-error-signing-key c))
|
||||||
|
(openpgp-public-key-fingerprint
|
||||||
|
(read-openpgp-packet
|
||||||
|
%ed25519bis-public-key-file))))))
|
||||||
|
(authenticate-channel channel directory
|
||||||
|
(commit-id-string commit3)
|
||||||
|
#:keyring-reference-prefix "")
|
||||||
|
'failed)))))))
|
||||||
|
|
||||||
(test-end "channels")
|
(test-end "channels")
|
||||||
|
|
Loading…
Reference in a new issue