1279315Strasz/*-
2332615Strasz * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3332615Strasz *
4279315Strasz * Copyright (c) 2014 The FreeBSD Foundation
5279315Strasz * All rights reserved.
6279315Strasz *
7279315Strasz * This software was developed by Edward Tomasz Napierala under sponsorship
8279315Strasz * from the FreeBSD Foundation.
9279315Strasz *
10279315Strasz * Redistribution and use in source and binary forms, with or without
11279315Strasz * modification, are permitted provided that the following conditions
12279315Strasz * are met:
13279315Strasz * 1. Redistributions of source code must retain the above copyright
14279315Strasz *    notice, this list of conditions and the following disclaimer.
15279315Strasz * 2. Redistributions in binary form must reproduce the above copyright
16279315Strasz *    notice, this list of conditions and the following disclaimer in the
17279315Strasz *    documentation and/or other materials provided with the distribution.
18279315Strasz *
19279315Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20279315Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21279315Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22279315Strasz * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23279315Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24279315Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25279315Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26279315Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27279315Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28279315Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29279315Strasz * SUCH DAMAGE.
30279315Strasz *
31279315Strasz */
32279315Strasz
33279315Strasz#include <sys/cdefs.h>
34279315Strasz__FBSDID("$FreeBSD: stable/11/usr.sbin/uefisign/uefisign.c 332615 2018-04-16 17:13:54Z trasz $");
35279315Strasz
36279315Strasz#include <sys/wait.h>
37279315Strasz#include <assert.h>
38279315Strasz#include <err.h>
39279315Strasz#include <errno.h>
40279315Strasz#include <stdio.h>
41279315Strasz#include <string.h>
42279315Strasz#include <unistd.h>
43279315Strasz
44279315Strasz#include <openssl/conf.h>
45279315Strasz#include <openssl/evp.h>
46279315Strasz#include <openssl/err.h>
47279315Strasz#include <openssl/pem.h>
48279315Strasz#include <openssl/pkcs7.h>
49279315Strasz
50279315Strasz#include "uefisign.h"
51279315Strasz#include "magic.h"
52279315Strasz
53279315Straszstatic void
54279315Straszusage(void)
55279315Strasz{
56279315Strasz
57279315Strasz	fprintf(stderr, "usage: uefisign -c cert -k key -o outfile [-v] file\n"
58279315Strasz			"       uefisign -V [-c cert] [-v] file\n");
59279315Strasz	exit(1);
60279315Strasz}
61279315Strasz
62279315Straszstatic char *
63279315Straszchecked_strdup(const char *s)
64279315Strasz{
65279315Strasz	char *c;
66279315Strasz
67279315Strasz	c = strdup(s);
68279315Strasz	if (c == NULL)
69279315Strasz		err(1, "strdup");
70279315Strasz	return (c);
71279315Strasz}
72279315Strasz
73279315StraszFILE *
74279315Straszchecked_fopen(const char *path, const char *mode)
75279315Strasz{
76279315Strasz	FILE *fp;
77279315Strasz
78279315Strasz	assert(path != NULL);
79279315Strasz
80279315Strasz	fp = fopen(path, mode);
81279315Strasz	if (fp == NULL)
82279315Strasz		err(1, "%s", path);
83279315Strasz	return (fp);
84279315Strasz}
85279315Strasz
86279315Straszvoid
87279315Straszsend_chunk(const void *buf, size_t len, int pipefd)
88279315Strasz{
89279315Strasz	ssize_t ret;
90279315Strasz
91279315Strasz	ret = write(pipefd, &len, sizeof(len));
92279315Strasz	if (ret != sizeof(len))
93279315Strasz		err(1, "write");
94279315Strasz	ret = write(pipefd, buf, len);
95279315Strasz	if (ret != (ssize_t)len)
96279315Strasz		err(1, "write");
97279315Strasz}
98279315Strasz
99279315Straszvoid
100279315Straszreceive_chunk(void **bufp, size_t *lenp, int pipefd)
101279315Strasz{
102279315Strasz	ssize_t ret;
103279315Strasz	size_t len;
104279315Strasz	void *buf;
105279315Strasz
106279315Strasz	ret = read(pipefd, &len, sizeof(len));
107279315Strasz	if (ret != sizeof(len))
108279315Strasz		err(1, "read");
109279315Strasz
110279315Strasz	buf = calloc(1, len);
111279315Strasz	if (buf == NULL)
112279315Strasz		err(1, "calloc");
113279315Strasz
114279315Strasz	ret = read(pipefd, buf, len);
115279315Strasz	if (ret != (ssize_t)len)
116279315Strasz		err(1, "read");
117279315Strasz
118279315Strasz	*bufp = buf;
119279315Strasz	*lenp = len;
120279315Strasz}
121279315Strasz
122279315Straszstatic char *
123279315Straszbin2hex(const char *bin, size_t bin_len)
124279315Strasz{
125279315Strasz	unsigned char *hex, *tmp, ch;
126279315Strasz	size_t hex_len;
127279315Strasz	size_t i;
128279315Strasz
129279315Strasz	hex_len = bin_len * 2 + 1; /* +1 for '\0'. */
130279315Strasz	hex = malloc(hex_len);
131279315Strasz	if (hex == NULL)
132279315Strasz		err(1, "malloc");
133279315Strasz
134279315Strasz	tmp = hex;
135279315Strasz	for (i = 0; i < bin_len; i++) {
136279315Strasz		ch = bin[i];
137279315Strasz		tmp += sprintf(tmp, "%02x", ch);
138279315Strasz	}
139279315Strasz
140279315Strasz	return (hex);
141279315Strasz}
142279315Strasz
143279315Strasz/*
144279315Strasz * We need to replace a standard chunk of PKCS7 signature with one mandated
145279315Strasz * by Authenticode.  Problem is, replacing it just like that and then calling
146279315Strasz * PKCS7_final() would make OpenSSL segfault somewhere in PKCS7_dataFinal().
147279315Strasz * So, instead, we call PKCS7_dataInit(), then put our Authenticode-specific
148279315Strasz * data into BIO it returned, then call PKCS7_dataFinal() - which now somehow
149279315Strasz * does not panic - and _then_ we replace it in the signature.  This technique
150279315Strasz * was used in sbsigntool by Jeremy Kerr, and might have originated in
151279315Strasz * osslsigncode.
152279315Strasz */
153279315Straszstatic void
154279315Straszmagic(PKCS7 *pkcs7, const char *digest, size_t digest_len)
155279315Strasz{
156279315Strasz	BIO *bio, *t_bio;
157279315Strasz	ASN1_TYPE *t;
158279315Strasz	ASN1_STRING *s;
159279315Strasz	CONF *cnf;
160279315Strasz	unsigned char *buf, *tmp;
161279315Strasz	char *digest_hex, *magic_conf, *str;
162279315Strasz	int len, nid, ok;
163279315Strasz
164279315Strasz	digest_hex = bin2hex(digest, digest_len);
165279315Strasz
166279315Strasz	/*
167279315Strasz	 * Construct the SpcIndirectDataContent chunk.
168279315Strasz	 */
169279315Strasz	nid = OBJ_create("1.3.6.1.4.1.311.2.1.4", NULL, NULL);
170279315Strasz
171279315Strasz	asprintf(&magic_conf, magic_fmt, digest_hex);
172279315Strasz	if (magic_conf == NULL)
173279315Strasz		err(1, "asprintf");
174279315Strasz
175279315Strasz	bio = BIO_new_mem_buf((void *)magic_conf, -1);
176279315Strasz	if (bio == NULL) {
177279315Strasz		ERR_print_errors_fp(stderr);
178279315Strasz		errx(1, "BIO_new_mem_buf(3) failed");
179279315Strasz	}
180279315Strasz
181279315Strasz	cnf = NCONF_new(NULL);
182279315Strasz	if (cnf == NULL) {
183279315Strasz		ERR_print_errors_fp(stderr);
184279315Strasz		errx(1, "NCONF_new(3) failed");
185279315Strasz	}
186279315Strasz
187279315Strasz	ok = NCONF_load_bio(cnf, bio, NULL);
188279315Strasz	if (ok == 0) {
189279315Strasz		ERR_print_errors_fp(stderr);
190279315Strasz		errx(1, "NCONF_load_bio(3) failed");
191279315Strasz	}
192279315Strasz
193279315Strasz	str = NCONF_get_string(cnf, "default", "asn1");
194279315Strasz	if (str == NULL) {
195279315Strasz		ERR_print_errors_fp(stderr);
196279315Strasz		errx(1, "NCONF_get_string(3) failed");
197279315Strasz	}
198279315Strasz
199279315Strasz	t = ASN1_generate_nconf(str, cnf);
200279315Strasz	if (t == NULL) {
201279315Strasz		ERR_print_errors_fp(stderr);
202279315Strasz		errx(1, "ASN1_generate_nconf(3) failed");
203279315Strasz	}
204279315Strasz
205279315Strasz	/*
206279315Strasz	 * We now have our proprietary piece of ASN.1.  Let's do
207279315Strasz	 * the actual signing.
208279315Strasz	 */
209279315Strasz	len = i2d_ASN1_TYPE(t, NULL);
210279315Strasz	tmp = buf = calloc(1, len);
211279315Strasz	if (tmp == NULL)
212279315Strasz		err(1, "calloc");
213279315Strasz	i2d_ASN1_TYPE(t, &tmp);
214279315Strasz
215279315Strasz	/*
216279315Strasz	 * We now have contents of 't' stuffed into memory buffer 'buf'.
217279315Strasz	 */
218279315Strasz	tmp = NULL;
219279315Strasz	t = NULL;
220279315Strasz
221279315Strasz	t_bio = PKCS7_dataInit(pkcs7, NULL);
222279315Strasz	if (t_bio == NULL) {
223279315Strasz		ERR_print_errors_fp(stderr);
224279315Strasz		errx(1, "PKCS7_dataInit(3) failed");
225279315Strasz	}
226279315Strasz
227279315Strasz	BIO_write(t_bio, buf + 2, len - 2);
228279315Strasz
229279315Strasz	ok = PKCS7_dataFinal(pkcs7, t_bio);
230279315Strasz	if (ok == 0) {
231279315Strasz		ERR_print_errors_fp(stderr);
232279315Strasz		errx(1, "PKCS7_dataFinal(3) failed");
233279315Strasz	}
234279315Strasz
235279315Strasz	t = ASN1_TYPE_new();
236279315Strasz	s = ASN1_STRING_new();
237279315Strasz	ASN1_STRING_set(s, buf, len);
238279315Strasz	ASN1_TYPE_set(t, V_ASN1_SEQUENCE, s);
239279315Strasz
240279315Strasz	PKCS7_set0_type_other(pkcs7->d.sign->contents, nid, t);
241279315Strasz}
242279315Strasz
243279315Straszstatic void
244279315Straszsign(X509 *cert, EVP_PKEY *key, int pipefd)
245279315Strasz{
246279315Strasz	PKCS7 *pkcs7;
247279315Strasz	BIO *bio, *out;
248279315Strasz	const EVP_MD *md;
249279315Strasz	PKCS7_SIGNER_INFO *info;
250279315Strasz	void *digest, *signature;
251279315Strasz	size_t digest_len, signature_len;
252279315Strasz	int ok;
253279315Strasz
254279315Strasz	assert(cert != NULL);
255279315Strasz	assert(key != NULL);
256279315Strasz
257279315Strasz	receive_chunk(&digest, &digest_len, pipefd);
258279315Strasz
259279315Strasz	bio = BIO_new_mem_buf(digest, digest_len);
260279315Strasz	if (bio == NULL) {
261279315Strasz		ERR_print_errors_fp(stderr);
262279315Strasz		errx(1, "BIO_new_mem_buf(3) failed");
263279315Strasz	}
264279315Strasz
265279315Strasz	pkcs7 = PKCS7_sign(NULL, NULL, NULL, bio, PKCS7_BINARY | PKCS7_PARTIAL);
266279315Strasz	if (pkcs7 == NULL) {
267279315Strasz		ERR_print_errors_fp(stderr);
268279315Strasz		errx(1, "PKCS7_sign(3) failed");
269279315Strasz	}
270279315Strasz
271279315Strasz	md = EVP_get_digestbyname(DIGEST);
272279315Strasz	if (md == NULL) {
273279315Strasz		ERR_print_errors_fp(stderr);
274279315Strasz		errx(1, "EVP_get_digestbyname(\"%s\") failed", DIGEST);
275279315Strasz	}
276279315Strasz
277279315Strasz	info = PKCS7_sign_add_signer(pkcs7, cert, key, md, 0);
278279315Strasz	if (info == NULL) {
279279315Strasz		ERR_print_errors_fp(stderr);
280279315Strasz		errx(1, "PKCS7_sign_add_signer(3) failed");
281279315Strasz	}
282279315Strasz
283279315Strasz	/*
284279315Strasz	 * XXX: All the signed binaries seem to have this, but where is it
285279315Strasz	 *      described in the spec?
286279315Strasz	 */
287279315Strasz	PKCS7_add_signed_attribute(info, NID_pkcs9_contentType,
288279315Strasz	    V_ASN1_OBJECT, OBJ_txt2obj("1.3.6.1.4.1.311.2.1.4", 1));
289279315Strasz
290279315Strasz	magic(pkcs7, digest, digest_len);
291279315Strasz
292279315Strasz#if 0
293279315Strasz	out = BIO_new(BIO_s_file());
294279315Strasz	BIO_set_fp(out, stdout, BIO_NOCLOSE);
295279315Strasz	PKCS7_print_ctx(out, pkcs7, 0, NULL);
296279315Strasz
297279315Strasz	i2d_PKCS7_bio(out, pkcs7);
298279315Strasz#endif
299279315Strasz
300279315Strasz	out = BIO_new(BIO_s_mem());
301279315Strasz	if (out == NULL) {
302279315Strasz		ERR_print_errors_fp(stderr);
303279315Strasz		errx(1, "BIO_new(3) failed");
304279315Strasz	}
305279315Strasz
306279315Strasz	ok = i2d_PKCS7_bio(out, pkcs7);
307279315Strasz	if (ok == 0) {
308279315Strasz		ERR_print_errors_fp(stderr);
309279315Strasz		errx(1, "i2d_PKCS7_bio(3) failed");
310279315Strasz	}
311279315Strasz
312279315Strasz	signature_len = BIO_get_mem_data(out, &signature);
313279315Strasz	if (signature_len <= 0) {
314279315Strasz		ERR_print_errors_fp(stderr);
315279315Strasz		errx(1, "BIO_get_mem_data(3) failed");
316279315Strasz	}
317279315Strasz
318279315Strasz	(void)BIO_set_close(out, BIO_NOCLOSE);
319279315Strasz	BIO_free(out);
320279315Strasz
321279315Strasz	send_chunk(signature, signature_len, pipefd);
322279315Strasz}
323279315Strasz
324279315Straszstatic int
325279315Straszwait_for_child(pid_t pid)
326279315Strasz{
327279315Strasz	int status;
328279315Strasz
329279315Strasz	pid = waitpid(pid, &status, 0);
330279315Strasz	if (pid == -1)
331279315Strasz		err(1, "waitpid");
332279315Strasz
333279315Strasz	return (WEXITSTATUS(status));
334279315Strasz}
335279315Strasz
336279315Straszint
337279315Straszmain(int argc, char **argv)
338279315Strasz{
339279315Strasz	int ch, error;
340279315Strasz	bool Vflag = false, vflag = false;
341279315Strasz	const char *certpath = NULL, *keypath = NULL, *outpath = NULL, *inpath = NULL;
342279315Strasz	FILE *certfp = NULL, *keyfp = NULL;
343279315Strasz	X509 *cert = NULL;
344279315Strasz	EVP_PKEY *key = NULL;
345279315Strasz	pid_t pid;
346279315Strasz	int pipefds[2];
347279315Strasz
348279315Strasz	while ((ch = getopt(argc, argv, "Vc:k:o:v")) != -1) {
349279315Strasz		switch (ch) {
350279315Strasz		case 'V':
351279315Strasz			Vflag = true;
352279315Strasz			break;
353279315Strasz		case 'c':
354279315Strasz			certpath = checked_strdup(optarg);
355279315Strasz			break;
356279315Strasz		case 'k':
357279315Strasz			keypath = checked_strdup(optarg);
358279315Strasz			break;
359279315Strasz		case 'o':
360279315Strasz			outpath = checked_strdup(optarg);
361279315Strasz			break;
362279315Strasz		case 'v':
363279315Strasz			vflag = true;
364279315Strasz			break;
365279315Strasz		default:
366279315Strasz			usage();
367279315Strasz		}
368279315Strasz	}
369279315Strasz
370279315Strasz	argc -= optind;
371279315Strasz	argv += optind;
372279315Strasz	if (argc != 1)
373279315Strasz		usage();
374279315Strasz
375279315Strasz	if (Vflag) {
376279315Strasz		if (certpath != NULL)
377279315Strasz			errx(1, "-V and -c are mutually exclusive");
378279315Strasz		if (keypath != NULL)
379279315Strasz			errx(1, "-V and -k are mutually exclusive");
380279315Strasz		if (outpath != NULL)
381279315Strasz			errx(1, "-V and -o are mutually exclusive");
382279315Strasz	} else {
383279315Strasz		if (certpath == NULL)
384279315Strasz			errx(1, "-c option is mandatory");
385279315Strasz		if (keypath == NULL)
386279315Strasz			errx(1, "-k option is mandatory");
387279315Strasz		if (outpath == NULL)
388279315Strasz			errx(1, "-o option is mandatory");
389279315Strasz	}
390279315Strasz
391279315Strasz	inpath = argv[0];
392279315Strasz
393279315Strasz	OPENSSL_config(NULL);
394279315Strasz	ERR_load_crypto_strings();
395279315Strasz	OpenSSL_add_all_algorithms();
396279315Strasz
397279315Strasz	error = pipe(pipefds);
398279315Strasz	if (error != 0)
399279315Strasz		err(1, "pipe");
400279315Strasz
401279315Strasz	pid = fork();
402279315Strasz	if (pid < 0)
403279315Strasz		err(1, "fork");
404279315Strasz
405279315Strasz	if (pid == 0)
406279315Strasz		return (child(inpath, outpath, pipefds[1], Vflag, vflag));
407279315Strasz
408279315Strasz	if (!Vflag) {
409279315Strasz		certfp = checked_fopen(certpath, "r");
410279315Strasz		cert = PEM_read_X509(certfp, NULL, NULL, NULL);
411279315Strasz		if (cert == NULL) {
412279315Strasz			ERR_print_errors_fp(stderr);
413279315Strasz			errx(1, "failed to load certificate from %s", certpath);
414279315Strasz		}
415279315Strasz
416279315Strasz		keyfp = checked_fopen(keypath, "r");
417279315Strasz		key = PEM_read_PrivateKey(keyfp, NULL, NULL, NULL);
418279315Strasz		if (key == NULL) {
419279315Strasz			ERR_print_errors_fp(stderr);
420279315Strasz			errx(1, "failed to load private key from %s", keypath);
421279315Strasz		}
422279315Strasz
423279315Strasz		sign(cert, key, pipefds[0]);
424279315Strasz	}
425279315Strasz
426279315Strasz	return (wait_for_child(pid));
427279315Strasz}
428