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
53static void
54usage(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
62static char *
63checked_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
73FILE *
74checked_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
86void
87send_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
99void
100receive_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
122static char *
123bin2hex(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 */
153static void
154magic(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
243static void
244sign(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
324static int
325wait_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
336int
337main(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_init_crypto(OPENSSL_INIT_LOAD_CONFIG |
394	    OPENSSL_INIT_LOAD_CRYPTO_STRINGS |
395	    OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS, NULL);
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