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 @@ | ||||||
|   #: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 @@ | ||||||
|   #: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 @@ | ||||||
|             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 @@ | ||||||
|   (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 @@ | ||||||
|   (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 @@ if valid metadata could not be read from 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 @@ if valid metadata could not be read from 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 @@ doesn't exist." | ||||||
|         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 @@ result is unspecified." | ||||||
|          (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 @@ relation to STARTING-COMMIT when provided." | ||||||
|                 (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 @@ | ||||||
|   #: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 @@ | ||||||
|                          (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") | ||||||
|  |  | ||||||
		Reference in a new issue