Merge branch 'ungrafting' into staging
[jackhill/guix/guix.git] / gnu / build / chromium-extension.scm
CommitLineData
d7a295b2
MB
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2020 Marius Bakke <marius@gnu.org>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (gnu build chromium-extension)
20 #:use-module (gcrypt base16)
21 #:use-module ((gcrypt hash) #:prefix hash:)
22 #:use-module (ice-9 iconv)
23 #:use-module (guix gexp)
24 #:use-module (guix packages)
25 #:use-module (gnu packages base)
26 #:use-module (gnu packages check)
27 #:use-module (gnu packages chromium)
28 #:use-module (gnu packages gnupg)
29 #:use-module (gnu packages tls)
30 #:use-module (gnu packages xorg)
31 #:use-module (guix build-system trivial)
32 #:export (make-chromium-extension))
33
34;;; Commentary:
35;;;
36;;; Tools to deal with Chromium extensions.
37;;;
38;;; Code:
39
40(define (make-signing-key seed)
41 "Return a derivation for a deterministic PKCS #8 private key using SEED."
42
43 (define sha256sum
44 (bytevector->base16-string (hash:sha256 (string->bytevector seed "UTF-8"))))
45
46 ;; certtool.c wants a 56 byte seed for a 2048 bit key.
47 (define size 2048)
48 (define normalized-seed (string-take sha256sum 56))
49
50 (computed-file (string-append seed "-signing-key.pem")
51 #~(system* #$(file-append gnutls "/bin/certtool")
52 "--generate-privkey"
53 "--key-type=rsa"
54 "--pkcs8"
55 ;; Use the provable FIPS-PUB186-4 algorithm for
56 ;; deterministic results.
57 "--provable"
58 "--password="
59 "--no-text"
60 (string-append "--bits=" #$(number->string size))
61 (string-append "--seed=" #$normalized-seed)
62 "--outfile" #$output)
63 #:local-build? #t))
64
65(define* (make-crx signing-key package #:optional (package-output "out"))
66 "Create a signed \".crx\" file from the unpacked Chromium extension residing
67in PACKAGE-OUTPUT of PACKAGE. The extension will be signed with SIGNING-KEY."
68 (define name (package-name package))
69 (define version (package-version package))
70
71 (with-imported-modules '((guix build utils))
72 (computed-file
73 (string-append name "-" version ".crx")
74 #~(begin
75 ;; This is not great. We pull Xorg and Chromium just to Zip and
76 ;; sign an extension. This should be implemented with something
77 ;; lighter. (TODO: where is the CRXv3 documentation..?)
78 (use-modules (guix build utils))
79 (let ((chromium #$(file-append ungoogled-chromium "/bin/chromium"))
80 (xvfb #$(file-append xorg-server "/bin/Xvfb"))
81 (packdir "/tmp/extension"))
82 (mkdir-p (dirname packdir))
83 (copy-recursively (ungexp package package-output) packdir)
84 (system (string-append xvfb " :1 &"))
85 (setenv "DISPLAY" ":1")
86 (sleep 2) ;give Xorg some time to initialize...
87 ;; Chromium stores the current time in the .crx Zip archive.
88 ;; Use a fixed timestamp for deterministic behavior.
89 ;; FIXME (core-updates): faketime is missing an absolute reference
90 ;; to 'date', hence the need to set PATH.
91 (setenv "PATH" #$(file-append coreutils "/bin"))
92 (invoke #$(file-append libfaketime "/bin/faketime")
93 "2000-01-01 00:00:00"
94 chromium
95 "--user-data-dir=/tmp/signing-profile"
96 (string-append "--pack-extension=" packdir)
97 (string-append "--pack-extension-key=" #$signing-key))
98 (copy-file (string-append packdir ".crx") #$output)))
99 #:local-build? #t)))
100
101(define* (crx->chromium-json crx version)
102 "Return a derivation that creates a Chromium JSON settings file for the
103extension given as CRX. VERSION is used to signify the CRX version, and
104must match the version listed in the extension manifest.json."
105 ;; See chrome/browser/extensions/external_provider_impl.cc and
106 ;; extensions/common/extension.h for documentation on the JSON format.
107 (computed-file "extension.json"
108 #~(call-with-output-file #$output
109 (lambda (port)
110 (format port "{
111 \"external_crx\": \"~a\",
112 \"external_version\": \"~a\"
113}
114"
115 #$crx #$version)))
116 #:local-build? #t))
117
118
119(define (signing-key->public-der key)
120 "Return a derivation for a file containing the public key of KEY in DER
121format."
122 (computed-file "der"
123 #~(system* #$(file-append gnutls "/bin/certtool")
124 "--load-privkey" #$key
125 "--pubkey-info"
126 "--outfile" #$output
127 "--outder")
128 #:local-build? #t))
129
130(define (chromium-json->profile-object json signing-key)
131 "Return a derivation that installs JSON to the directory searched by
132Chromium, using a file name (aka extension ID) derived from SIGNING-KEY."
133 (define der (signing-key->public-der signing-key))
134
135 (with-extensions (list guile-gcrypt)
136 (with-imported-modules '((guix build utils))
137 (computed-file
138 "chromium-extension"
139 #~(begin
140 (use-modules (guix build utils)
141 (gcrypt base16)
142 (gcrypt hash))
143 (define (base16-string->chromium-base16 str)
144 ;; Translate STR, a hexadecimal string, to a Chromium-style
145 ;; representation using the letters a-p (where a=0, p=15).
146 (define s1 "0123456789abcdef")
147 (define s2 "abcdefghijklmnop")
148 (let loop ((chars (string->list str))
149 (converted '()))
150 (if (null? chars)
151 (list->string (reverse converted))
152 (loop (cdr chars)
153 (cons (string-ref s2 (string-index s1 (car chars)))
154 converted)))))
155
156 (let* ((checksum (bytevector->base16-string (file-sha256 #$der)))
157 (file-name (base16-string->chromium-base16
158 (string-take checksum 32)))
159 (extension-directory (string-append #$output
160 "/share/chromium/extensions")))
161 (mkdir-p extension-directory)
162 (symlink #$json (string-append extension-directory "/"
163 file-name ".json"))))
164 #:local-build? #t))))
165
166(define* (make-chromium-extension p #:optional (output "out"))
167 "Create a Chromium extension from package P and return a package that,
168when installed, will make the extension contained in P available as a
169Chromium browser extension. OUTPUT specifies which output of P to use."
170 (let* ((pname (package-name p))
171 (version (package-version p))
172 (signing-key (make-signing-key pname)))
173 (package
174 (inherit p)
175 (name (string-append pname "-chromium"))
176 (source #f)
177 (build-system trivial-build-system)
178 (native-inputs '())
179 (inputs
180 `(("extension" ,(chromium-json->profile-object
181 (crx->chromium-json (make-crx signing-key p output)
182 version)
183 signing-key))))
184 (propagated-inputs '())
185 (outputs '("out"))
186 (arguments
187 '(#:modules ((guix build utils))
188 #:builder
189 (begin
190 (use-modules (guix build utils))
191 (copy-recursively (assoc-ref %build-inputs "extension")
192 (assoc-ref %outputs "out"))))))))