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
50static void
51usage(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
59static char *
60checked_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
70FILE *
71checked_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
83void
84send_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
96void
97receive_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
119static char *
120bin2hex(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 */
150static void
151magic(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
240static void
241sign(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
321static int
322wait_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
333int
334main(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