xref: /freebsd/usr.sbin/uefisign/uefisign.c (revision e0c4386e7e71d93b0edc0c8fa156263fc4a8b0b6)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2014 The FreeBSD Foundation
5  *
6  * This software was developed by Edward Tomasz Napierala under sponsorship
7  * from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  *
30  */
31 
32 #include <sys/cdefs.h>
33 #include <sys/wait.h>
34 #include <assert.h>
35 #include <err.h>
36 #include <errno.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <unistd.h>
40 
41 #include <openssl/conf.h>
42 #include <openssl/evp.h>
43 #include <openssl/err.h>
44 #include <openssl/pem.h>
45 #include <openssl/pkcs7.h>
46 
47 #include "uefisign.h"
48 #include "magic.h"
49 
50 static void
51 usage(void)
52 {
53 
54 	fprintf(stderr, "usage: uefisign -c cert -k key -o outfile [-v] file\n"
55 			"       uefisign -V [-c cert] [-v] file\n");
56 	exit(1);
57 }
58 
59 static char *
60 checked_strdup(const char *s)
61 {
62 	char *c;
63 
64 	c = strdup(s);
65 	if (c == NULL)
66 		err(1, "strdup");
67 	return (c);
68 }
69 
70 FILE *
71 checked_fopen(const char *path, const char *mode)
72 {
73 	FILE *fp;
74 
75 	assert(path != NULL);
76 
77 	fp = fopen(path, mode);
78 	if (fp == NULL)
79 		err(1, "%s", path);
80 	return (fp);
81 }
82 
83 void
84 send_chunk(const void *buf, size_t len, int pipefd)
85 {
86 	ssize_t ret;
87 
88 	ret = write(pipefd, &len, sizeof(len));
89 	if (ret != sizeof(len))
90 		err(1, "write");
91 	ret = write(pipefd, buf, len);
92 	if (ret != (ssize_t)len)
93 		err(1, "write");
94 }
95 
96 void
97 receive_chunk(void **bufp, size_t *lenp, int pipefd)
98 {
99 	ssize_t ret;
100 	size_t len;
101 	void *buf;
102 
103 	ret = read(pipefd, &len, sizeof(len));
104 	if (ret != sizeof(len))
105 		err(1, "read");
106 
107 	buf = calloc(1, len);
108 	if (buf == NULL)
109 		err(1, "calloc");
110 
111 	ret = read(pipefd, buf, len);
112 	if (ret != (ssize_t)len)
113 		err(1, "read");
114 
115 	*bufp = buf;
116 	*lenp = len;
117 }
118 
119 static char *
120 bin2hex(const char *bin, size_t bin_len)
121 {
122 	unsigned char *hex, *tmp, ch;
123 	size_t hex_len;
124 	size_t i;
125 
126 	hex_len = bin_len * 2 + 1; /* +1 for '\0'. */
127 	hex = malloc(hex_len);
128 	if (hex == NULL)
129 		err(1, "malloc");
130 
131 	tmp = hex;
132 	for (i = 0; i < bin_len; i++) {
133 		ch = bin[i];
134 		tmp += sprintf(tmp, "%02x", ch);
135 	}
136 
137 	return (hex);
138 }
139 
140 /*
141  * We need to replace a standard chunk of PKCS7 signature with one mandated
142  * by Authenticode.  Problem is, replacing it just like that and then calling
143  * PKCS7_final() would make OpenSSL segfault somewhere in PKCS7_dataFinal().
144  * So, instead, we call PKCS7_dataInit(), then put our Authenticode-specific
145  * data into BIO it returned, then call PKCS7_dataFinal() - which now somehow
146  * does not panic - and _then_ we replace it in the signature.  This technique
147  * was used in sbsigntool by Jeremy Kerr, and might have originated in
148  * osslsigncode.
149  */
150 static void
151 magic(PKCS7 *pkcs7, const char *digest, size_t digest_len)
152 {
153 	BIO *bio, *t_bio;
154 	ASN1_TYPE *t;
155 	ASN1_STRING *s;
156 	CONF *cnf;
157 	unsigned char *buf, *tmp;
158 	char *digest_hex, *magic_conf, *str;
159 	int len, nid, ok;
160 
161 	digest_hex = bin2hex(digest, digest_len);
162 
163 	/*
164 	 * Construct the SpcIndirectDataContent chunk.
165 	 */
166 	nid = OBJ_create("1.3.6.1.4.1.311.2.1.4", NULL, NULL);
167 
168 	asprintf(&magic_conf, magic_fmt, digest_hex);
169 	if (magic_conf == NULL)
170 		err(1, "asprintf");
171 
172 	bio = BIO_new_mem_buf((void *)magic_conf, -1);
173 	if (bio == NULL) {
174 		ERR_print_errors_fp(stderr);
175 		errx(1, "BIO_new_mem_buf(3) failed");
176 	}
177 
178 	cnf = NCONF_new(NULL);
179 	if (cnf == NULL) {
180 		ERR_print_errors_fp(stderr);
181 		errx(1, "NCONF_new(3) failed");
182 	}
183 
184 	ok = NCONF_load_bio(cnf, bio, NULL);
185 	if (ok == 0) {
186 		ERR_print_errors_fp(stderr);
187 		errx(1, "NCONF_load_bio(3) failed");
188 	}
189 
190 	str = NCONF_get_string(cnf, "default", "asn1");
191 	if (str == NULL) {
192 		ERR_print_errors_fp(stderr);
193 		errx(1, "NCONF_get_string(3) failed");
194 	}
195 
196 	t = ASN1_generate_nconf(str, cnf);
197 	if (t == NULL) {
198 		ERR_print_errors_fp(stderr);
199 		errx(1, "ASN1_generate_nconf(3) failed");
200 	}
201 
202 	/*
203 	 * We now have our proprietary piece of ASN.1.  Let's do
204 	 * the actual signing.
205 	 */
206 	len = i2d_ASN1_TYPE(t, NULL);
207 	tmp = buf = calloc(1, len);
208 	if (tmp == NULL)
209 		err(1, "calloc");
210 	i2d_ASN1_TYPE(t, &tmp);
211 
212 	/*
213 	 * We now have contents of 't' stuffed into memory buffer 'buf'.
214 	 */
215 	tmp = NULL;
216 	t = NULL;
217 
218 	t_bio = PKCS7_dataInit(pkcs7, NULL);
219 	if (t_bio == NULL) {
220 		ERR_print_errors_fp(stderr);
221 		errx(1, "PKCS7_dataInit(3) failed");
222 	}
223 
224 	BIO_write(t_bio, buf + 2, len - 2);
225 
226 	ok = PKCS7_dataFinal(pkcs7, t_bio);
227 	if (ok == 0) {
228 		ERR_print_errors_fp(stderr);
229 		errx(1, "PKCS7_dataFinal(3) failed");
230 	}
231 
232 	t = ASN1_TYPE_new();
233 	s = ASN1_STRING_new();
234 	ASN1_STRING_set(s, buf, len);
235 	ASN1_TYPE_set(t, V_ASN1_SEQUENCE, s);
236 
237 	PKCS7_set0_type_other(pkcs7->d.sign->contents, nid, t);
238 }
239 
240 static void
241 sign(X509 *cert, EVP_PKEY *key, int pipefd)
242 {
243 	PKCS7 *pkcs7;
244 	BIO *bio, *out;
245 	const EVP_MD *md;
246 	PKCS7_SIGNER_INFO *info;
247 	void *digest, *signature;
248 	size_t digest_len, signature_len;
249 	int ok;
250 
251 	assert(cert != NULL);
252 	assert(key != NULL);
253 
254 	receive_chunk(&digest, &digest_len, pipefd);
255 
256 	bio = BIO_new_mem_buf(digest, digest_len);
257 	if (bio == NULL) {
258 		ERR_print_errors_fp(stderr);
259 		errx(1, "BIO_new_mem_buf(3) failed");
260 	}
261 
262 	pkcs7 = PKCS7_sign(NULL, NULL, NULL, bio, PKCS7_BINARY | PKCS7_PARTIAL);
263 	if (pkcs7 == NULL) {
264 		ERR_print_errors_fp(stderr);
265 		errx(1, "PKCS7_sign(3) failed");
266 	}
267 
268 	md = EVP_get_digestbyname(DIGEST);
269 	if (md == NULL) {
270 		ERR_print_errors_fp(stderr);
271 		errx(1, "EVP_get_digestbyname(\"%s\") failed", DIGEST);
272 	}
273 
274 	info = PKCS7_sign_add_signer(pkcs7, cert, key, md, 0);
275 	if (info == NULL) {
276 		ERR_print_errors_fp(stderr);
277 		errx(1, "PKCS7_sign_add_signer(3) failed");
278 	}
279 
280 	/*
281 	 * XXX: All the signed binaries seem to have this, but where is it
282 	 *      described in the spec?
283 	 */
284 	PKCS7_add_signed_attribute(info, NID_pkcs9_contentType,
285 	    V_ASN1_OBJECT, OBJ_txt2obj("1.3.6.1.4.1.311.2.1.4", 1));
286 
287 	magic(pkcs7, digest, digest_len);
288 
289 #if 0
290 	out = BIO_new(BIO_s_file());
291 	BIO_set_fp(out, stdout, BIO_NOCLOSE);
292 	PKCS7_print_ctx(out, pkcs7, 0, NULL);
293 
294 	i2d_PKCS7_bio(out, pkcs7);
295 #endif
296 
297 	out = BIO_new(BIO_s_mem());
298 	if (out == NULL) {
299 		ERR_print_errors_fp(stderr);
300 		errx(1, "BIO_new(3) failed");
301 	}
302 
303 	ok = i2d_PKCS7_bio(out, pkcs7);
304 	if (ok == 0) {
305 		ERR_print_errors_fp(stderr);
306 		errx(1, "i2d_PKCS7_bio(3) failed");
307 	}
308 
309 	signature_len = BIO_get_mem_data(out, &signature);
310 	if (signature_len <= 0) {
311 		ERR_print_errors_fp(stderr);
312 		errx(1, "BIO_get_mem_data(3) failed");
313 	}
314 
315 	(void)BIO_set_close(out, BIO_NOCLOSE);
316 	BIO_free(out);
317 
318 	send_chunk(signature, signature_len, pipefd);
319 }
320 
321 static int
322 wait_for_child(pid_t pid)
323 {
324 	int status;
325 
326 	pid = waitpid(pid, &status, 0);
327 	if (pid == -1)
328 		err(1, "waitpid");
329 
330 	return (WEXITSTATUS(status));
331 }
332 
333 int
334 main(int argc, char **argv)
335 {
336 	int ch, error;
337 	bool Vflag = false, vflag = false;
338 	const char *certpath = NULL, *keypath = NULL, *outpath = NULL, *inpath = NULL;
339 	FILE *certfp = NULL, *keyfp = NULL;
340 	X509 *cert = NULL;
341 	EVP_PKEY *key = NULL;
342 	pid_t pid;
343 	int pipefds[2];
344 
345 	while ((ch = getopt(argc, argv, "Vc:k:o:v")) != -1) {
346 		switch (ch) {
347 		case 'V':
348 			Vflag = true;
349 			break;
350 		case 'c':
351 			if (certpath == NULL)
352 				certpath = checked_strdup(optarg);
353 			else
354 				err(1, "-c can only be specified once");
355 			break;
356 		case 'k':
357 			if (keypath == NULL)
358 				keypath = checked_strdup(optarg);
359 			else
360 				err(1, "-k can only be specified once");
361 			break;
362 		case 'o':
363 			if (outpath == NULL)
364 				outpath = checked_strdup(optarg);
365 			else
366 				err(1, "-o can only be specified once");
367 			break;
368 		case 'v':
369 			vflag = true;
370 			break;
371 		default:
372 			usage();
373 		}
374 	}
375 
376 	argc -= optind;
377 	argv += optind;
378 	if (argc != 1)
379 		usage();
380 
381 	if (Vflag) {
382 		if (certpath != NULL)
383 			errx(1, "-V and -c are mutually exclusive");
384 		if (keypath != NULL)
385 			errx(1, "-V and -k are mutually exclusive");
386 		if (outpath != NULL)
387 			errx(1, "-V and -o are mutually exclusive");
388 	} else {
389 		if (certpath == NULL)
390 			errx(1, "-c option is mandatory");
391 		if (keypath == NULL)
392 			errx(1, "-k option is mandatory");
393 		if (outpath == NULL)
394 			errx(1, "-o option is mandatory");
395 	}
396 
397 	inpath = argv[0];
398 
399 	OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG |
400 	    OPENSSL_INIT_LOAD_CRYPTO_STRINGS |
401 	    OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS, NULL);
402 
403 	error = pipe(pipefds);
404 	if (error != 0)
405 		err(1, "pipe");
406 
407 	pid = fork();
408 	if (pid < 0)
409 		err(1, "fork");
410 
411 	if (pid == 0) {
412 		close(pipefds[0]);
413 		exit(child(inpath, outpath, pipefds[1], Vflag, vflag));
414 	}
415 
416 	close(pipefds[1]);
417 
418 	if (!Vflag) {
419 		certfp = checked_fopen(certpath, "r");
420 		cert = PEM_read_X509(certfp, NULL, NULL, NULL);
421 		if (cert == NULL) {
422 			ERR_print_errors_fp(stderr);
423 			errx(1, "failed to load certificate from %s", certpath);
424 		}
425 
426 		keyfp = checked_fopen(keypath, "r");
427 		key = PEM_read_PrivateKey(keyfp, NULL, NULL, NULL);
428 		if (key == NULL) {
429 			ERR_print_errors_fp(stderr);
430 			errx(1, "failed to load private key from %s", keypath);
431 		}
432 
433 		sign(cert, key, pipefds[0]);
434 	}
435 
436 	exit(wait_for_child(pid));
437 }
438