Commit | Line | Data |
---|---|---|
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 | |
67 | in 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 | |
103 | extension given as CRX. VERSION is used to signify the CRX version, and | |
104 | must 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 | |
121 | format." | |
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 | |
132 | Chromium, 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, | |
168 | when installed, will make the extension contained in P available as a | |
169 | Chromium 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")))))))) |