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