;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2020, 2021, 2022 Mathieu Othacehe ;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen ;;; Copyright © 2022 Pavel Shlyak ;;; Copyright © 2022 Denis 'GNUtoo' Carikli ;;; Copyright © 2022 Alex Griffin ;;; Copyright © 2023 Efraim Flashner ;;; Copyright © 2023 Oleg Pykhalov ;;; ;;; 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
aboutsummaryrefslogtreecommitdiff
blob: 8171c7273414df7a4ed2814ec70a10f112147cf9 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2020, 2021 Marius Bakke <marius@gnu.org>
;;; Copyright © 2022 Nicolas Graves <ngraves@ngraves.fr>
;;;
;;; 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 (gnu build chromium-extension)
  #:use-module (guix gexp)
  #:use-module (guix packages)
  #:use-module (gnu packages gnupg)
  #:use-module (gnu packages tls)
  #:use-module (gnu packages node-xyz)
  #:use-module (guix build-system trivial)
  #:export (make-chromium-extension))

;;; Commentary:
;;;
;;; Tools to deal with Chromium extensions.
;;;
;;; Code:

(define (make-signing-key seed)
  "Return a derivation for a deterministic PKCS #8 private key using SEED."
  (computed-file
   (string-append seed "-signing-key.pem")
   (with-extensions (list guile-gcrypt)
     #~(begin
         (use-modules (gcrypt base16) (gcrypt hash) (ice-9 iconv))
         (let* ((sha256sum (bytevector->base16-string
                            (sha256 (string->bytevector #$seed "UTF-8"))))
                ;; certtool.c wants a 56 byte seed for a 2048 bit key.
                (key-size 2048)
                (normalized-seed (string-take sha256sum 56)))

           (system* #$(file-append gnutls "/bin/certtool")
                    "--generate-privkey"
                    "--key-type=rsa"
                    "--pkcs8"
                    ;; Use the provable FIPS-PUB186-4 algorithm for
                    ;; deterministic results.
                    "--provable"
                    "--password="
                    "--no-text"
                    (string-append "--bits=" (number->string key-size))
                    (string-append "--seed=" normalized-seed)
                    "--outfile" #$output))))
   #:local-build? #t))

(define* (make-crx signing-key package #:optional (package-output "out"))
  "Create a signed \".crx\" file from the unpacked Chromium extension residing
in PACKAGE-OUTPUT of PACKAGE.  The extension will be signed with SIGNING-KEY."
  (define name (package-name package))
  (define version (package-version package))

  (computed-file
   (string-append name "-" version ".crx")
   (with-imported-modules '((guix build utils))
     #~(begin
         (use-modules (guix build utils))
         (let ((crx3 #+(file-append node-crx3 "/bin/crx3"))
               (packdir (string-append (getcwd) "/extension")))
           (mkdir packdir)
           (copy-recursively (ungexp package package-output) packdir
                             ;; Ensure consistent file modification times.
                             #:keep-mtime? #t)
           (invoke crx3 "--keyPath" #$signing-key packdir)
           (copy-file (string-append packdir ".crx") #$output))))
   #:local-build? #t))

(define (crx->chromium-json crx version)
  "Return a derivation that creates a Chromium JSON settings file for the
extension given as CRX.  VERSION is used to signify the CRX version, and
must match the version listed in the extension manifest.json."
  ;; See chrome/browser/extensions/external_provider_impl.cc and
  ;; extensions/common/extension.h for documentation on the JSON format.
  (computed-file "extension.json"
                 #~(call-with-output-file #$output
                     (lambda (port)
                       (format port "{
  \"external_crx\": \"~a\",
  \"external_version\": \"~a\"
}
"
                               #$crx #$version)))
                 #:local-build? #t))


(define (signing-key->public-der key)
  "Return a derivation for a file containing the public key of KEY in DER
format."
  (computed-file "der"
                 #~(system* #$(file-append gnutls "/bin/certtool")
                            "--load-privkey" #$key
                            "--pubkey-info"
                            "--outfile" #$output
                            "--outder")
                 #:local-build? #t))

(define (file-sha256sum file)
  (with-extensions (list guile-gcrypt)
    #~(begin
        (use-modules (gcrypt base16) (gcrypt hash))
        (bytevector->base16-string (file-sha256 #$file)))))

(define* (make-chromium-extension pkg #:optional (pkg-output "out"))
  "Create a Chromium extension from package PKG and return a package that,
when installed, will make the extension contained in PKG available as a
Chromium browser extension.  PKG-OUTPUT specifies which output of PKG to use."
  (let* ((name (package-name pkg))
         (version (package-version pkg)))
    (package
      (inherit pkg)
      (name (string-append name "-chromium"))
      (source #f)
      (native-inputs '())
      (inputs '())
      (propagated-inputs '())
      (outputs '("out"))
      (build-system trivial-build-system)
      (arguments
       (list #:modules '((guix build utils))
             #:builder
             (let*
                 ((private-key (make-signing-key name))
                  (public-key (signing-key->public-der private-key))
                  (checksum (file-sha256sum public-key))
                  (crx (make-crx private-key pkg pkg-output))
                  (json (crx->chromium-json crx version)))
               #~(begin
                   (use-modules (guix build utils))
                   (define (base16-char->chromium-base16 char)
                     ;; Translate CHAR, a hexadecimal character, to a Chromium-style
                     ;; representation using the letters a-p (where a=0, p=15).
                     (string-ref "abcdefghijklmnop"
                                 (string-index "0123456789abcdef" char)))
                   (let ((file-name (string-map base16-char->chromium-base16
                                                (string-take #$checksum 32)))
                         (extension-directory
                          (string-append #$output
                                         "/share/chromium/extensions")))
                     (mkdir-p extension-directory)
                     (symlink #$json (string-append extension-directory "/"
                                                    file-name ".json"))))))))))
(string-prefix? "fat" type)) (list dosfstools fakeroot mtools)) (else '()))) (image-root "tmp-root")) (sql-schema #$schema) (set-path-environment-variable "PATH" '("bin" "sbin") inputs) ;; Allow non-ASCII file names--e.g., 'nss-certs'--to be ;; decoded. (setenv "GUIX_LOCPATH" #+(file-append (libc-utf8-locales-for-target (%current-system)) "/lib/locale")) (setlocale LC_ALL "en_US.utf8") (initializer image-root #:references-graphs '#$graph #:deduplicate? #f #:copy-closures? (not #$(image-shared-store? image)) #:system-directory #$os #:grub-efi #+grub-efi #:grub-efi32 #+grub-efi32 #:bootloader-package #+(bootloader-package bootloader) #:bootloader-installer #+(bootloader-installer bootloader) #:bootcfg #$bootcfg #:bootcfg-location #$(bootloader-configuration-file bootloader)) (make-partition-image #$(partition->gexp partition) #$output image-root))))) (computed-file "partition.img" image-builder ;; Allow offloading so that this I/O-intensive process ;; doesn't run on the build farm's head node. #:local-build? #f #:options `(#:references-graphs ,inputs)))) (define (gpt-image? image) (eq? 'gpt (image-partition-table-type image))) (define (partition-type-values image partition) (if (gpt-image? image) (values "partition-type-uuid" (partition->gpt-type partition)) (values "partition-type" (partition->dos-type partition)))) (define (partition->config image partition) ;; Return the genimage partition configuration for PARTITION. (let-values (((partition-type-attribute partition-type-value) (partition-type-values image partition))) (let ((label (partition-label partition)) (image (partition-image partition)) (offset (partition-offset partition)) (bootable (if (memq 'boot (partition-flags partition)) "true" "false" ))) #~(format #f "~/partition ~a { ~/~/~a = ~a ~/~/image = \"~a\" ~/~/offset = \"~a\" ~/~/bootable = \"~a\" ~/}" #$label #$partition-type-attribute #$partition-type-value #$image #$offset #$bootable)))) (define (genimage-type-options image-type image) (cond ((equal? image-type "hdimage") (format #f "~%~/~/partition-table-type = \"~a\"~%~/" (image-partition-table-type image))) (else ""))) (when (and (memq (bootloader-name bootloader) '(grub-efi grub-efi32 grub-efi-removable-bootloader)) (not (gpt-image? image))) (raise (formatted-message (G_ "EFI bootloader required with GPT partitioning")))) (let* ((format (image-format image)) (image-type (format->image-type format)) (image-type-options (genimage-type-options image-type image)) (partitions (image-partitions image)) (partitions-config (map (cut partition->config image <>) partitions)) (builder #~(begin (let ((format (@ (ice-9 format) format))) (call-with-output-file #$output (lambda (port) (format port "\ image ~a { ~/~a {~a} ~{~a~^~%~} }~%" #$genimage-name #$image-type #$image-type-options (list #$@partitions-config)))))))) (computed-file "genimage.cfg" builder))) (let* ((image-name (image-name image)) (name (if image-name (symbol->string image-name) name)) (format (image-format image)) (substitutable? (image-substitutable? image)) (builder (with-imported-modules* (let ((inputs '#+(list genimage coreutils findutils qemu-minimal)) (bootloader-installer #+(bootloader-disk-image-installer bootloader)) (out-image (string-append "images/" #$genimage-name))) (set-path-environment-variable "PATH" '("bin" "sbin") inputs) (genimage #$(image->genimage-cfg image)) ;; Install the bootloader directly on the disk-image. (when bootloader-installer (bootloader-installer #+(bootloader-package bootloader) #$(root-partition-index image) out-image)) (convert-disk-image out-image '#$format #$output))))) (computed-file name builder #:local-build? #f ;too I/O-intensive #:options `(#:substitutable? ,substitutable?)))) ;; ;; ISO9660 image. ;; (define (has-guix-service-type? os) "Return true if OS contains a service of the type GUIX-SERVICE-TYPE." (not (not (srfi-1:find (lambda (service) (eq? (service-kind service) guix-service-type)) (operating-system-services os))))) (define* (system-iso9660-image image #:key (name "image.iso") bootcfg bootloader register-closures? (inputs '()) (grub-mkrescue-environment '())) "Return as a file-like object a bootable, stand-alone iso9660 image. INPUTS is a list of inputs (as for packages). When REGISTER-CLOSURES? is true, register INPUTS in the store database of the image so that Guix can be used in the image. " (define root-label (match (image-partitions image) ((partition) (partition-label partition)))) (define root-uuid (match (image-partitions image) ((partition) (uuid-bytevector (partition-uuid partition))))) (let* ((os (image-operating-system image)) (bootloader (bootloader-package bootloader)) (compression? (image-compression? image)) (substitutable? (image-substitutable? image)) (schema (local-file (search-path %load-path "guix/store/schema.sql"))) (graph (match inputs (((names . _) ...) names))) (builder (with-imported-modules* (let* ((inputs '#$(list parted e2fsprogs dosfstools xorriso sed grep coreutils findutils gawk)) (image-root "tmp-root")) (sql-schema #$schema) ;; Allow non-ASCII file names--e.g., 'nss-certs'--to be decoded. (setenv "GUIX_LOCPATH" #+(file-append (libc-utf8-locales-for-target (%current-system)) "/lib/locale")) (setlocale LC_ALL "en_US.utf8") (set-path-environment-variable "PATH" '("bin" "sbin") inputs) (initialize-root-partition image-root #:references-graphs '#$graph #:deduplicate? #f #:system-directory #$os) (make-iso9660-image #$xorriso '#$grub-mkrescue-environment #$bootloader #$bootcfg #$os image-root #$output #:references-graphs '#$graph #:register-closures? #$register-closures? #:compression? #$compression? #:volume-id #$root-label #:volume-uuid #$root-uuid))))) (computed-file name builder ;; Allow offloading so that this I/O-intensive process ;; doesn't run on the build farm's head node. #:local-build? #f #:options `(#:references-graphs ,inputs #:substitutable? ,substitutable?)))) (define (image-with-label base-image label) "The volume ID of an ISO is the label of the first partition. This procedure returns an image record where the first partition's label is set to