Add (guix git-authenticate).
* build-aux/git-authenticate.scm (commit-signing-key) (read-authorizations, commit-authorized-keys, authenticate-commit) (load-keyring-from-blob, load-keyring-from-reference) (authenticate-commits, authenticated-commit-cache-file) (previously-authenticated-commits, cache-authenticated-commit): Remove. * build-aux/git-authenticate.scm (git-authenticate): Pass #:default-authorizations to 'authenticate-commits'. * guix/git-authenticate.scm: New file, with code taken from 'build-aux/git-authenticate.scm'. Remove references to '%historical-authorized-signing-keys' and add #:default-authorizations parameter instead. * Makefile.am (MODULES): Add it. (authenticate): Depend on guix/git-authenticate.go.master
parent
ecab53c320
commit
41f443c90a
|
@ -104,6 +104,7 @@ MODULES = \
|
||||||
guix/lint.scm \
|
guix/lint.scm \
|
||||||
guix/glob.scm \
|
guix/glob.scm \
|
||||||
guix/git.scm \
|
guix/git.scm \
|
||||||
|
guix/git-authenticate.scm \
|
||||||
guix/graph.scm \
|
guix/graph.scm \
|
||||||
guix/cache.scm \
|
guix/cache.scm \
|
||||||
guix/cve.scm \
|
guix/cve.scm \
|
||||||
|
@ -632,7 +633,7 @@ commit_v1_0_1 = d68de958b60426798ed62797ff7c96c327a672ac
|
||||||
|
|
||||||
# Authenticate the current Git checkout by checking signatures on every commit
|
# Authenticate the current Git checkout by checking signatures on every commit
|
||||||
# starting from $(commit_v1_0_1).
|
# starting from $(commit_v1_0_1).
|
||||||
authenticate: guix/openpgp.go guix/git.go
|
authenticate: guix/openpgp.go guix/git-authenticate.go guix/git.go
|
||||||
$(AM_V_at)echo "Authenticating Git checkout..." ; \
|
$(AM_V_at)echo "Authenticating Git checkout..." ; \
|
||||||
"$(top_builddir)/pre-inst-env" $(GUILE) \
|
"$(top_builddir)/pre-inst-env" $(GUILE) \
|
||||||
--no-auto-compile -e git-authenticate \
|
--no-auto-compile -e git-authenticate \
|
||||||
|
|
|
@ -22,21 +22,16 @@
|
||||||
;;;
|
;;;
|
||||||
|
|
||||||
(use-modules (git)
|
(use-modules (git)
|
||||||
(guix git)
|
|
||||||
(guix openpgp)
|
|
||||||
(guix base16)
|
(guix base16)
|
||||||
((guix utils)
|
(guix git)
|
||||||
#:select (cache-directory with-atomic-file-output))
|
(guix git-authenticate)
|
||||||
((guix build utils) #:select (mkdir-p))
|
|
||||||
(guix i18n)
|
(guix i18n)
|
||||||
|
((guix openpgp)
|
||||||
|
#:select (openpgp-public-key-fingerprint
|
||||||
|
openpgp-format-fingerprint))
|
||||||
(guix progress)
|
(guix progress)
|
||||||
(srfi srfi-1)
|
(srfi srfi-1)
|
||||||
(srfi srfi-11)
|
|
||||||
(srfi srfi-26)
|
(srfi srfi-26)
|
||||||
(srfi srfi-34)
|
|
||||||
(srfi srfi-35)
|
|
||||||
(rnrs bytevectors)
|
|
||||||
(rnrs io ports)
|
|
||||||
(ice-9 match)
|
(ice-9 match)
|
||||||
(ice-9 format)
|
(ice-9 format)
|
||||||
(ice-9 pretty-print))
|
(ice-9 pretty-print))
|
||||||
|
@ -231,195 +226,9 @@
|
||||||
;; Commits lacking a signature.
|
;; Commits lacking a signature.
|
||||||
'())
|
'())
|
||||||
|
|
||||||
(define (commit-signing-key repo commit-id keyring)
|
|
||||||
"Return the OpenPGP key that signed COMMIT-ID (an OID). Raise an exception
|
|
||||||
if the commit is unsigned, has an invalid signature, or if its signing key is
|
|
||||||
not in KEYRING."
|
|
||||||
(let-values (((signature signed-data)
|
|
||||||
(catch 'git-error
|
|
||||||
(lambda ()
|
|
||||||
(commit-extract-signature repo commit-id))
|
|
||||||
(lambda _
|
|
||||||
(values #f #f)))))
|
|
||||||
(unless signature
|
|
||||||
(raise (condition
|
|
||||||
(&message
|
|
||||||
(message (format #f (G_ "commit ~a lacks a signature")
|
|
||||||
commit-id))))))
|
|
||||||
|
|
||||||
(let ((signature (string->openpgp-packet signature)))
|
|
||||||
(with-fluids ((%default-port-encoding "UTF-8"))
|
|
||||||
(let-values (((status data)
|
|
||||||
(verify-openpgp-signature signature keyring
|
|
||||||
(open-input-string signed-data))))
|
|
||||||
(match status
|
|
||||||
('bad-signature
|
|
||||||
;; There's a signature but it's invalid.
|
|
||||||
(raise (condition
|
|
||||||
(&message
|
|
||||||
(message (format #f (G_ "signature verification failed \
|
|
||||||
for commit ~a")
|
|
||||||
(oid->string commit-id)))))))
|
|
||||||
('missing-key
|
|
||||||
(raise (condition
|
|
||||||
(&message
|
|
||||||
(message (format #f (G_ "could not authenticate \
|
|
||||||
commit ~a: key ~a is missing")
|
|
||||||
(oid->string commit-id)
|
|
||||||
data))))))
|
|
||||||
('good-signature data)))))))
|
|
||||||
|
|
||||||
(define (read-authorizations port)
|
|
||||||
"Read authorizations in the '.guix-authorizations' format from PORT, and
|
|
||||||
return a list of authorized fingerprints."
|
|
||||||
(match (read port)
|
|
||||||
(('authorizations ('version 0)
|
|
||||||
(((? string? fingerprints) _ ...) ...)
|
|
||||||
_ ...)
|
|
||||||
(map (lambda (fingerprint)
|
|
||||||
(base16-string->bytevector
|
|
||||||
(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 (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)
|
|
||||||
default-authorizations
|
|
||||||
(throw key error)))))
|
|
||||||
|
|
||||||
(apply lset-intersection bytevector=?
|
|
||||||
(map commit-authorizations (commit-parents commit))))
|
|
||||||
|
|
||||||
(define (authenticate-commit repository commit keyring)
|
|
||||||
"Authenticate COMMIT from REPOSITORY and return the signing key fingerprint.
|
|
||||||
Raise an error when authentication fails."
|
|
||||||
(define id
|
|
||||||
(commit-id commit))
|
|
||||||
|
|
||||||
(define signing-key
|
|
||||||
(commit-signing-key repository id keyring))
|
|
||||||
|
|
||||||
(unless (member (openpgp-public-key-fingerprint signing-key)
|
|
||||||
(commit-authorized-keys repository commit
|
|
||||||
%historical-authorized-signing-keys))
|
|
||||||
(raise (condition
|
|
||||||
(&message
|
|
||||||
(message (format #f (G_ "commit ~a not signed by an authorized \
|
|
||||||
key: ~a")
|
|
||||||
(oid->string id)
|
|
||||||
(openpgp-format-fingerprint
|
|
||||||
(openpgp-public-key-fingerprint
|
|
||||||
signing-key))))))))
|
|
||||||
|
|
||||||
signing-key)
|
|
||||||
|
|
||||||
(define (load-keyring-from-blob repository oid keyring)
|
|
||||||
"Augment KEYRING with the keyring available in the blob at OID, which may or
|
|
||||||
may not be ASCII-armored."
|
|
||||||
(let* ((blob (blob-lookup repository oid))
|
|
||||||
(port (open-bytevector-input-port (blob-content blob))))
|
|
||||||
(get-openpgp-keyring (if (port-ascii-armored? port)
|
|
||||||
(open-bytevector-input-port (read-radix-64 port))
|
|
||||||
port)
|
|
||||||
keyring)))
|
|
||||||
|
|
||||||
(define (load-keyring-from-reference repository reference)
|
|
||||||
"Load the '.key' files from the tree at REFERENCE in REPOSITORY and return
|
|
||||||
an OpenPGP keyring."
|
|
||||||
(let* ((reference (branch-lookup repository
|
|
||||||
(string-append "origin/" reference)
|
|
||||||
BRANCH-REMOTE))
|
|
||||||
(target (reference-target reference))
|
|
||||||
(commit (commit-lookup repository target))
|
|
||||||
(tree (commit-tree commit)))
|
|
||||||
(fold (lambda (name keyring)
|
|
||||||
(if (string-suffix? ".key" name)
|
|
||||||
(let ((entry (tree-entry-bypath tree name)))
|
|
||||||
(load-keyring-from-blob repository
|
|
||||||
(tree-entry-id entry)
|
|
||||||
keyring))
|
|
||||||
keyring))
|
|
||||||
%empty-keyring
|
|
||||||
(tree-list tree))))
|
|
||||||
|
|
||||||
(define* (authenticate-commits repository commits
|
|
||||||
#:key
|
|
||||||
(keyring-reference "keyring")
|
|
||||||
(report-progress (const #t)))
|
|
||||||
"Authenticate COMMITS, a list of commit objects, calling REPORT-PROGRESS for
|
|
||||||
each of them. Return an alist showing the number of occurrences of each key.
|
|
||||||
The OpenPGP keyring is loaded from KEYRING-REFERENCE in REPOSITORY."
|
|
||||||
(define keyring
|
|
||||||
(load-keyring-from-reference repository keyring-reference))
|
|
||||||
|
|
||||||
(fold (lambda (commit stats)
|
|
||||||
(report-progress)
|
|
||||||
(let ((signer (authenticate-commit repository commit keyring)))
|
|
||||||
(match (assq signer stats)
|
|
||||||
(#f (cons `(,signer . 1) stats))
|
|
||||||
((_ . count) (cons `(,signer . ,(+ count 1))
|
|
||||||
(alist-delete signer stats))))))
|
|
||||||
'()
|
|
||||||
commits))
|
|
||||||
|
|
||||||
(define commit-short-id
|
(define commit-short-id
|
||||||
(compose (cut string-take <> 7) oid->string commit-id))
|
(compose (cut string-take <> 7) oid->string commit-id))
|
||||||
|
|
||||||
|
|
||||||
;;;
|
|
||||||
;;; Caching.
|
|
||||||
;;;
|
|
||||||
|
|
||||||
(define (authenticated-commit-cache-file)
|
|
||||||
"Return the name of the file that contains the cache of
|
|
||||||
previously-authenticated commits."
|
|
||||||
(string-append (cache-directory) "/authentication/channels/guix"))
|
|
||||||
|
|
||||||
(define (previously-authenticated-commits)
|
|
||||||
"Return the previously-authenticated commits as a list of commit IDs (hex
|
|
||||||
strings)."
|
|
||||||
(catch 'system-error
|
|
||||||
(lambda ()
|
|
||||||
(call-with-input-file (authenticated-commit-cache-file)
|
|
||||||
read))
|
|
||||||
(lambda args
|
|
||||||
(if (= ENOENT (system-error-errno args))
|
|
||||||
'()
|
|
||||||
(apply throw args)))))
|
|
||||||
|
|
||||||
(define (cache-authenticated-commit commit-id)
|
|
||||||
"Record in ~/.cache COMMIT-ID and its closure as authenticated (only
|
|
||||||
COMMIT-ID is written to cache, though)."
|
|
||||||
(define %max-cache-length
|
|
||||||
;; Maximum number of commits in cache.
|
|
||||||
200)
|
|
||||||
|
|
||||||
(let ((lst (delete-duplicates
|
|
||||||
(cons commit-id (previously-authenticated-commits))))
|
|
||||||
(file (authenticated-commit-cache-file)))
|
|
||||||
(mkdir-p (dirname file))
|
|
||||||
(with-atomic-file-output file
|
|
||||||
(lambda (port)
|
|
||||||
(let ((lst (if (> (length lst) %max-cache-length)
|
|
||||||
(take lst %max-cache-length) ;truncate
|
|
||||||
lst)))
|
|
||||||
(chmod port #o600)
|
|
||||||
(display ";; List of previously-authenticated commits.\n\n"
|
|
||||||
port)
|
|
||||||
(pretty-print lst port))))))
|
|
||||||
|
|
||||||
|
|
||||||
;;;
|
;;;
|
||||||
;;; Entry point.
|
;;; Entry point.
|
||||||
|
@ -462,6 +271,8 @@ COMMIT-ID is written to cache, though)."
|
||||||
(let ((stats (call-with-progress-reporter reporter
|
(let ((stats (call-with-progress-reporter reporter
|
||||||
(lambda (report)
|
(lambda (report)
|
||||||
(authenticate-commits repository commits
|
(authenticate-commits repository commits
|
||||||
|
#:default-authorizations
|
||||||
|
%historical-authorized-signing-keys
|
||||||
#:report-progress report)))))
|
#:report-progress report)))))
|
||||||
(cache-authenticated-commit (oid->string (commit-id end-commit)))
|
(cache-authenticated-commit (oid->string (commit-id end-commit)))
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,244 @@
|
||||||
|
;;; GNU Guix --- Functional package management for GNU
|
||||||
|
;;; Copyright © 2019, 2020 Ludovic Courtès <ludo@gnu.org>
|
||||||
|
;;;
|
||||||
|
;;; This file is part of GNU Guix.
|
||||||
|
;;;
|
||||||
|
;;; GNU Guix is free software; you can redistribute it and/or modify it
|
||||||
|
;;; under the terms of the GNU General Public License as published by
|
||||||
|
;;; the Free Software Foundation; either version 3 of the License, or (at
|
||||||
|
;;; your option) any later version.
|
||||||
|
;;;
|
||||||
|
;;; GNU Guix is distributed in the hope that it will be useful, but
|
||||||
|
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
;;; GNU General Public License for more details.
|
||||||
|
;;;
|
||||||
|
;;; You should have received a copy of the GNU General Public License
|
||||||
|
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
(define-module (guix git-authenticate)
|
||||||
|
#:use-module (git)
|
||||||
|
#:use-module (guix base16)
|
||||||
|
#:use-module (guix i18n)
|
||||||
|
#:use-module (guix openpgp)
|
||||||
|
#:use-module ((guix utils)
|
||||||
|
#:select (cache-directory with-atomic-file-output))
|
||||||
|
#:use-module ((guix build utils)
|
||||||
|
#:select (mkdir-p))
|
||||||
|
#:use-module (srfi srfi-1)
|
||||||
|
#:use-module (srfi srfi-11)
|
||||||
|
#:use-module (srfi srfi-26)
|
||||||
|
#:use-module (srfi srfi-34)
|
||||||
|
#:use-module (srfi srfi-35)
|
||||||
|
#:use-module (rnrs bytevectors)
|
||||||
|
#:use-module (rnrs io ports)
|
||||||
|
#:use-module (ice-9 match)
|
||||||
|
#:autoload (ice-9 pretty-print) (pretty-print)
|
||||||
|
#:export (read-authorizations
|
||||||
|
commit-signing-key
|
||||||
|
commit-authorized-keys
|
||||||
|
authenticate-commit
|
||||||
|
authenticate-commits
|
||||||
|
load-keyring-from-reference
|
||||||
|
previously-authenticated-commits
|
||||||
|
cache-authenticated-commit))
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;;;
|
||||||
|
;;; This module provides tools to authenticate a range of Git commits. A
|
||||||
|
;;; commit is considered "authentic" if and only if it is signed by an
|
||||||
|
;;; authorized party. Parties authorized to sign a commit are listed in the
|
||||||
|
;;; '.guix-authorizations' file of the parent commit.
|
||||||
|
;;;
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(define (commit-signing-key repo commit-id keyring)
|
||||||
|
"Return the OpenPGP key that signed COMMIT-ID (an OID). Raise an exception
|
||||||
|
if the commit is unsigned, has an invalid signature, or if its signing key is
|
||||||
|
not in KEYRING."
|
||||||
|
(let-values (((signature signed-data)
|
||||||
|
(catch 'git-error
|
||||||
|
(lambda ()
|
||||||
|
(commit-extract-signature repo commit-id))
|
||||||
|
(lambda _
|
||||||
|
(values #f #f)))))
|
||||||
|
(unless signature
|
||||||
|
(raise (condition
|
||||||
|
(&message
|
||||||
|
(message (format #f (G_ "commit ~a lacks a signature")
|
||||||
|
commit-id))))))
|
||||||
|
|
||||||
|
(let ((signature (string->openpgp-packet signature)))
|
||||||
|
(with-fluids ((%default-port-encoding "UTF-8"))
|
||||||
|
(let-values (((status data)
|
||||||
|
(verify-openpgp-signature signature keyring
|
||||||
|
(open-input-string signed-data))))
|
||||||
|
(match status
|
||||||
|
('bad-signature
|
||||||
|
;; There's a signature but it's invalid.
|
||||||
|
(raise (condition
|
||||||
|
(&message
|
||||||
|
(message (format #f (G_ "signature verification failed \
|
||||||
|
for commit ~a")
|
||||||
|
(oid->string commit-id)))))))
|
||||||
|
('missing-key
|
||||||
|
(raise (condition
|
||||||
|
(&message
|
||||||
|
(message (format #f (G_ "could not authenticate \
|
||||||
|
commit ~a: key ~a is missing")
|
||||||
|
(oid->string commit-id)
|
||||||
|
data))))))
|
||||||
|
('good-signature data)))))))
|
||||||
|
|
||||||
|
(define (read-authorizations port)
|
||||||
|
"Read authorizations in the '.guix-authorizations' format from PORT, and
|
||||||
|
return a list of authorized fingerprints."
|
||||||
|
(match (read port)
|
||||||
|
(('authorizations ('version 0)
|
||||||
|
(((? string? fingerprints) _ ...) ...)
|
||||||
|
_ ...)
|
||||||
|
(map (lambda (fingerprint)
|
||||||
|
(base16-string->bytevector
|
||||||
|
(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 (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)
|
||||||
|
default-authorizations
|
||||||
|
(throw key error)))))
|
||||||
|
|
||||||
|
(apply lset-intersection bytevector=?
|
||||||
|
(map commit-authorizations (commit-parents commit))))
|
||||||
|
|
||||||
|
(define* (authenticate-commit repository commit keyring
|
||||||
|
#:key (default-authorizations '()))
|
||||||
|
"Authenticate COMMIT from REPOSITORY and return the signing key fingerprint.
|
||||||
|
Raise an error when authentication fails. If one of the parent commits does
|
||||||
|
not specify anything, fall back to DEFAULT-AUTHORIZATIONS."
|
||||||
|
(define id
|
||||||
|
(commit-id commit))
|
||||||
|
|
||||||
|
(define signing-key
|
||||||
|
(commit-signing-key repository id keyring))
|
||||||
|
|
||||||
|
(unless (member (openpgp-public-key-fingerprint signing-key)
|
||||||
|
(commit-authorized-keys repository commit
|
||||||
|
default-authorizations))
|
||||||
|
(raise (condition
|
||||||
|
(&message
|
||||||
|
(message (format #f (G_ "commit ~a not signed by an authorized \
|
||||||
|
key: ~a")
|
||||||
|
(oid->string id)
|
||||||
|
(openpgp-format-fingerprint
|
||||||
|
(openpgp-public-key-fingerprint
|
||||||
|
signing-key))))))))
|
||||||
|
|
||||||
|
signing-key)
|
||||||
|
|
||||||
|
(define (load-keyring-from-blob repository oid keyring)
|
||||||
|
"Augment KEYRING with the keyring available in the blob at OID, which may or
|
||||||
|
may not be ASCII-armored."
|
||||||
|
(let* ((blob (blob-lookup repository oid))
|
||||||
|
(port (open-bytevector-input-port (blob-content blob))))
|
||||||
|
(get-openpgp-keyring (if (port-ascii-armored? port)
|
||||||
|
(open-bytevector-input-port (read-radix-64 port))
|
||||||
|
port)
|
||||||
|
keyring)))
|
||||||
|
|
||||||
|
(define (load-keyring-from-reference repository reference)
|
||||||
|
"Load the '.key' files from the tree at REFERENCE in REPOSITORY and return
|
||||||
|
an OpenPGP keyring."
|
||||||
|
(let* ((reference (branch-lookup repository
|
||||||
|
(string-append "origin/" reference)
|
||||||
|
BRANCH-REMOTE))
|
||||||
|
(target (reference-target reference))
|
||||||
|
(commit (commit-lookup repository target))
|
||||||
|
(tree (commit-tree commit)))
|
||||||
|
(fold (lambda (name keyring)
|
||||||
|
(if (string-suffix? ".key" name)
|
||||||
|
(let ((entry (tree-entry-bypath tree name)))
|
||||||
|
(load-keyring-from-blob repository
|
||||||
|
(tree-entry-id entry)
|
||||||
|
keyring))
|
||||||
|
keyring))
|
||||||
|
%empty-keyring
|
||||||
|
(tree-list tree))))
|
||||||
|
|
||||||
|
(define* (authenticate-commits repository commits
|
||||||
|
#:key
|
||||||
|
(default-authorizations '())
|
||||||
|
(keyring-reference "keyring")
|
||||||
|
(report-progress (const #t)))
|
||||||
|
"Authenticate COMMITS, a list of commit objects, calling REPORT-PROGRESS for
|
||||||
|
each of them. Return an alist showing the number of occurrences of each key.
|
||||||
|
The OpenPGP keyring is loaded from KEYRING-REFERENCE in REPOSITORY."
|
||||||
|
(define keyring
|
||||||
|
(load-keyring-from-reference repository keyring-reference))
|
||||||
|
|
||||||
|
(fold (lambda (commit stats)
|
||||||
|
(report-progress)
|
||||||
|
(let ((signer (authenticate-commit repository commit keyring
|
||||||
|
#:default-authorizations
|
||||||
|
default-authorizations)))
|
||||||
|
(match (assq signer stats)
|
||||||
|
(#f (cons `(,signer . 1) stats))
|
||||||
|
((_ . count) (cons `(,signer . ,(+ count 1))
|
||||||
|
(alist-delete signer stats))))))
|
||||||
|
'()
|
||||||
|
commits))
|
||||||
|
|
||||||
|
|
||||||
|
;;;
|
||||||
|
;;; Caching.
|
||||||
|
;;;
|
||||||
|
|
||||||
|
(define (authenticated-commit-cache-file)
|
||||||
|
"Return the name of the file that contains the cache of
|
||||||
|
previously-authenticated commits."
|
||||||
|
(string-append (cache-directory) "/authentication/channels/guix"))
|
||||||
|
|
||||||
|
(define (previously-authenticated-commits)
|
||||||
|
"Return the previously-authenticated commits as a list of commit IDs (hex
|
||||||
|
strings)."
|
||||||
|
(catch 'system-error
|
||||||
|
(lambda ()
|
||||||
|
(call-with-input-file (authenticated-commit-cache-file)
|
||||||
|
read))
|
||||||
|
(lambda args
|
||||||
|
(if (= ENOENT (system-error-errno args))
|
||||||
|
'()
|
||||||
|
(apply throw args)))))
|
||||||
|
|
||||||
|
(define (cache-authenticated-commit commit-id)
|
||||||
|
"Record in ~/.cache COMMIT-ID and its closure as authenticated (only
|
||||||
|
COMMIT-ID is written to cache, though)."
|
||||||
|
(define %max-cache-length
|
||||||
|
;; Maximum number of commits in cache.
|
||||||
|
200)
|
||||||
|
|
||||||
|
(let ((lst (delete-duplicates
|
||||||
|
(cons commit-id (previously-authenticated-commits))))
|
||||||
|
(file (authenticated-commit-cache-file)))
|
||||||
|
(mkdir-p (dirname file))
|
||||||
|
(with-atomic-file-output file
|
||||||
|
(lambda (port)
|
||||||
|
(let ((lst (if (> (length lst) %max-cache-length)
|
||||||
|
(take lst %max-cache-length) ;truncate
|
||||||
|
lst)))
|
||||||
|
(chmod port #o600)
|
||||||
|
(display ";; List of previously-authenticated commits.\n\n"
|
||||||
|
port)
|
||||||
|
(pretty-print lst port))))))
|
Reference in New Issue