1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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
52static void
53usage(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
61static char *
62checked_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
72FILE *
73checked_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
85void
86send_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
98void
99receive_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
121static char *
122bin2hex(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 */
152static void
153magic(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
242static void
243sign(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
323static int
324wait_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
335int
336main(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		exit(child(inpath, outpath, pipefds[1], Vflag, vflag));
415
416	if (!Vflag) {
417		certfp = checked_fopen(certpath, "r");
418		cert = PEM_read_X509(certfp, NULL, NULL, NULL);
419		if (cert == NULL) {
420			ERR_print_errors_fp(stderr);
421			errx(1, "failed to load certificate from %s", certpath);
422		}
423
424		keyfp = checked_fopen(keypath, "r");
425		key = PEM_read_PrivateKey(keyfp, NULL, NULL, NULL);
426		if (key == NULL) {
427			ERR_print_errors_fp(stderr);
428			errx(1, "failed to load private key from %s", keypath);
429		}
430
431		sign(cert, key, pipefds[0]);
432	}
433
434	exit(wait_for_child(pid));
435}
436