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