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