xref: /dragonfly/usr.sbin/uefisign/uefisign.c (revision 8e6c217bed2417c878d70765a454bf178418d761)
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  * $FreeBSD: head/usr.sbin/uefisign/uefisign.c 339270 2018-10-09 21:28:26Z gjb $
32  */
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 
51 static void
usage(void)52 usage(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 
60 static char *
checked_strdup(const char * s)61 checked_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 
71 FILE *
checked_fopen(const char * path,const char * mode)72 checked_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 
84 void
send_chunk(const void * buf,size_t len,int pipefd)85 send_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 
97 void
receive_chunk(void ** bufp,size_t * lenp,int pipefd)98 receive_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 
120 static char *
bin2hex(const char * bin,size_t bin_len)121 bin2hex(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  */
151 static void
magic(PKCS7 * pkcs7,const char * digest,size_t digest_len)152 magic(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 
241 static void
sign(X509 * cert,EVP_PKEY * key,int pipefd)242 sign(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 
322 static int
wait_for_child(pid_t pid)323 wait_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 
334 int
main(int argc,char ** argv)335 main(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_init_crypto(OPENSSL_INIT_LOAD_CONFIG |
392               OPENSSL_INIT_LOAD_CRYPTO_STRINGS |
393               OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS, NULL);
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