diff options
author | trasz <trasz@FreeBSD.org> | 2015-05-15 15:08:37 +0000 |
---|---|---|
committer | trasz <trasz@FreeBSD.org> | 2015-05-15 15:08:37 +0000 |
commit | 0ba19b5b1c5d32c9b8ddf7913ce3c60ca062db93 (patch) | |
tree | 2244bae77b56e7c7ee9fd98ecc2198b6dc4420f1 | |
parent | 47cc02d0ccd815d336fb20171d05ef470775200a (diff) | |
download | FreeBSD-src-0ba19b5b1c5d32c9b8ddf7913ce3c60ca062db93.zip FreeBSD-src-0ba19b5b1c5d32c9b8ddf7913ce3c60ca062db93.tar.gz |
MFC r279315:
Add uefisign(8), UEFI Secure Boot signing utility.
Relnotes: yes
Sponsored by: The FreeBSD Foundation
-rw-r--r-- | usr.sbin/Makefile | 1 | ||||
-rw-r--r-- | usr.sbin/uefisign/Makefile | 11 | ||||
-rw-r--r-- | usr.sbin/uefisign/child.c | 277 | ||||
-rw-r--r-- | usr.sbin/uefisign/magic.h | 66 | ||||
-rw-r--r-- | usr.sbin/uefisign/pe.c | 560 | ||||
-rw-r--r-- | usr.sbin/uefisign/uefisign.8 | 93 | ||||
-rw-r--r-- | usr.sbin/uefisign/uefisign.c | 425 | ||||
-rw-r--r-- | usr.sbin/uefisign/uefisign.h | 91 |
8 files changed, 1524 insertions, 0 deletions
diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile index b501d1c..b705669 100644 --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -86,6 +86,7 @@ SUBDIR= adduser \ traceroute \ trpt \ tzsetup \ + uefisign \ ugidfw \ vipw \ wake \ diff --git a/usr.sbin/uefisign/Makefile b/usr.sbin/uefisign/Makefile new file mode 100644 index 0000000..559791c --- /dev/null +++ b/usr.sbin/uefisign/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +PROG= uefisign +SRCS= uefisign.c child.c pe.c +MAN= uefisign.8 + +LDFLAGS= -lcrypto + +WARNS= 6 + +.include <bsd.prog.mk> diff --git a/usr.sbin/uefisign/child.c b/usr.sbin/uefisign/child.c new file mode 100644 index 0000000..7dfc211 --- /dev/null +++ b/usr.sbin/uefisign/child.c @@ -0,0 +1,277 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#if __FreeBSD_version >= 1100000 +#include <sys/capsicum.h> +#else +#include <sys/capability.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/evp.h> +#include <openssl/err.h> +#include <openssl/pem.h> + +#include "uefisign.h" + +static void +load(struct executable *x) +{ + int error, fd; + struct stat sb; + char *buf; + size_t nread, len; + + fd = fileno(x->x_fp); + + error = fstat(fd, &sb); + if (error != 0) + err(1, "%s: fstat", x->x_path); + + len = sb.st_size; + if (len <= 0) + errx(1, "%s: file is empty", x->x_path); + + buf = malloc(len); + if (buf == NULL) + err(1, "%s: cannot malloc %zd bytes", x->x_path, len); + + nread = fread(buf, len, 1, x->x_fp); + if (nread != 1) + err(1, "%s: fread", x->x_path); + + x->x_buf = buf; + x->x_len = len; +} + +static void +digest_range(struct executable *x, EVP_MD_CTX *mdctx, off_t off, size_t len) +{ + int ok; + + range_check(x, off, len, "chunk"); + + ok = EVP_DigestUpdate(mdctx, x->x_buf + off, len); + if (ok == 0) { + ERR_print_errors_fp(stderr); + errx(1, "EVP_DigestUpdate(3) failed"); + } +} + +static void +digest(struct executable *x) +{ + EVP_MD_CTX *mdctx; + const EVP_MD *md; + size_t sum_of_bytes_hashed; + int i, ok; + + /* + * Windows Authenticode Portable Executable Signature Format + * spec version 1.0 specifies MD5 and SHA1. However, pesign + * and sbsign both use SHA256, so do the same. + */ + md = EVP_get_digestbyname(DIGEST); + if (md == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "EVP_get_digestbyname(\"%s\") failed", DIGEST); + } + + mdctx = EVP_MD_CTX_create(); + if (mdctx == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "EVP_MD_CTX_create(3) failed"); + } + + ok = EVP_DigestInit_ex(mdctx, md, NULL); + if (ok == 0) { + ERR_print_errors_fp(stderr); + errx(1, "EVP_DigestInit_ex(3) failed"); + } + + /* + * According to the Authenticode spec, we need to compute + * the digest in a rather... specific manner; see "Calculating + * the PE Image Hash" part of the spec for details. + * + * First, everything from 0 to before the PE checksum. + */ + digest_range(x, mdctx, 0, x->x_checksum_off); + + /* + * Second, from after the PE checksum to before the Certificate + * entry in Data Directory. + */ + digest_range(x, mdctx, x->x_checksum_off + x->x_checksum_len, + x->x_certificate_entry_off - + (x->x_checksum_off + x->x_checksum_len)); + + /* + * Then, from after the Certificate entry to the end of headers. + */ + digest_range(x, mdctx, + x->x_certificate_entry_off + x->x_certificate_entry_len, + x->x_headers_len - + (x->x_certificate_entry_off + x->x_certificate_entry_len)); + + /* + * Then, each section in turn, as specified in the PE Section Table. + * + * XXX: Sorting. + */ + sum_of_bytes_hashed = x->x_headers_len; + for (i = 0; i < x->x_nsections; i++) { + digest_range(x, mdctx, + x->x_section_off[i], x->x_section_len[i]); + sum_of_bytes_hashed += x->x_section_len[i]; + } + + /* + * I believe this can happen with overlapping sections. + */ + if (sum_of_bytes_hashed > x->x_len) + errx(1, "number of bytes hashed is larger than file size"); + + /* + * I can't really explain this one; just do what the spec says. + */ + if (sum_of_bytes_hashed < x->x_len) { + digest_range(x, mdctx, sum_of_bytes_hashed, + x->x_len - (signature_size(x) + sum_of_bytes_hashed)); + } + + ok = EVP_DigestFinal_ex(mdctx, x->x_digest, &x->x_digest_len); + if (ok == 0) { + ERR_print_errors_fp(stderr); + errx(1, "EVP_DigestFinal_ex(3) failed"); + } + + EVP_MD_CTX_destroy(mdctx); +} + +static void +show_digest(const struct executable *x) +{ + int i; + + printf("computed %s digest ", DIGEST); + for (i = 0; i < (int)x->x_digest_len; i++) + printf("%02x", (unsigned char)x->x_digest[i]); + printf("; digest len %u\n", x->x_digest_len); +} + +static void +send_digest(const struct executable *x, int pipefd) +{ + + send_chunk(x->x_digest, x->x_digest_len, pipefd); +} + +static void +receive_signature(struct executable *x, int pipefd) +{ + + receive_chunk(&x->x_signature, &x->x_signature_len, pipefd); +} + +static void +save(struct executable *x, FILE *fp, const char *path) +{ + size_t nwritten; + + assert(fp != NULL); + assert(path != NULL); + + nwritten = fwrite(x->x_buf, x->x_len, 1, fp); + if (nwritten != 1) + err(1, "%s: fwrite", path); +} + +int +child(const char *inpath, const char *outpath, int pipefd, + bool Vflag, bool vflag) +{ + int error; + FILE *outfp = NULL, *infp = NULL; + struct executable *x; + + infp = checked_fopen(inpath, "r"); + if (outpath != NULL) + outfp = checked_fopen(outpath, "w"); + + error = cap_enter(); + if (error != 0 && errno != ENOSYS) + err(1, "cap_enter"); + + x = calloc(1, sizeof(*x)); + if (x == NULL) + err(1, "calloc"); + x->x_path = inpath; + x->x_fp = infp; + + load(x); + parse(x); + if (Vflag) { + if (signature_size(x) == 0) + errx(1, "file not signed"); + + printf("file contains signature\n"); + if (vflag) { + digest(x); + show_digest(x); + show_certificate(x); + } + } else { + if (signature_size(x) != 0) + errx(1, "file already signed"); + + digest(x); + if (vflag) + show_digest(x); + send_digest(x, pipefd); + receive_signature(x, pipefd); + update(x); + save(x, outfp, outpath); + } + + return (0); +} diff --git a/usr.sbin/uefisign/magic.h b/usr.sbin/uefisign/magic.h new file mode 100644 index 0000000..4c3ad4e --- /dev/null +++ b/usr.sbin/uefisign/magic.h @@ -0,0 +1,66 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +/* + * This file contains Authenticode-specific ASN.1 "configuration", used, + * after being processed by asprintf(3), as an input to ASN1_generate_nconf(3). + */ +static const char *magic_fmt = +"asn1 = SEQUENCE:SpcIndirectDataContent\n" +"\n" +"[SpcIndirectDataContent]\n" +"a = SEQUENCE:SpcAttributeTypeAndOptionalValue\n" +"b = SEQUENCE:DigestInfo\n" +"\n" +"[SpcAttributeTypeAndOptionalValue]\n" +"# SPC_PE_IMAGE_DATAOBJ\n" +"a = OID:1.3.6.1.4.1.311.2.1.15\n" +"b = SEQUENCE:SpcPeImageData\n" +"\n" +"[SpcPeImageData]\n" +"a = FORMAT:HEX,BITSTRING:00\n" +/* + * Well, there should be some other struct here, "SPCLink", but it doesn't + * appear to be neccessary for UEFI, and I have no idea how to synthesize it, + * as it uses the CHOICE type. + */ +"\n" +"[DigestInfo]\n" +"a = SEQUENCE:AlgorithmIdentifier\n" +/* + * Here goes the digest computed from PE headers and sections. + */ +"b = FORMAT:HEX,OCTETSTRING:%s\n" +"\n" +"[AlgorithmIdentifier]\n" +"a = OBJECT:sha256\n" +"b = NULL\n"; diff --git a/usr.sbin/uefisign/pe.c b/usr.sbin/uefisign/pe.c new file mode 100644 index 0000000..26bb6d5 --- /dev/null +++ b/usr.sbin/uefisign/pe.c @@ -0,0 +1,560 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/* + * PE format reference: + * http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "uefisign.h" + +#ifndef CTASSERT +#define CTASSERT(x) _CTASSERT(x, __LINE__) +#define _CTASSERT(x, y) __CTASSERT(x, y) +#define __CTASSERT(x, y) typedef char __assert_ ## y [(x) ? 1 : -1] +#endif + +struct mz_header { + uint8_t mz_signature[2]; + uint8_t mz_dont_care[58]; + uint16_t mz_lfanew; +} __attribute__((packed)); + +struct coff_header { + uint8_t coff_dont_care[2]; + uint16_t coff_number_of_sections; + uint8_t coff_dont_care_either[16]; +} __attribute__((packed)); + +#define PE_SIGNATURE 0x00004550 + +struct pe_header { + uint32_t pe_signature; + struct coff_header pe_coff; +} __attribute__((packed)); + +#define PE_OPTIONAL_MAGIC_32 0x010B +#define PE_OPTIONAL_MAGIC_32_PLUS 0x020B + +#define PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION 10 +#define PE_OPTIONAL_SUBSYSTEM_EFI_BOOT 11 +#define PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME 12 + +struct pe_optional_header_32 { + uint16_t po_magic; + uint8_t po_dont_care[58]; + uint32_t po_size_of_headers; + uint32_t po_checksum; + uint16_t po_subsystem; + uint8_t po_dont_care_either[22]; + uint32_t po_number_of_rva_and_sizes; +} __attribute__((packed)); + +CTASSERT(offsetof(struct pe_optional_header_32, po_size_of_headers) == 60); +CTASSERT(offsetof(struct pe_optional_header_32, po_checksum) == 64); +CTASSERT(offsetof(struct pe_optional_header_32, po_subsystem) == 68); +CTASSERT(offsetof(struct pe_optional_header_32, po_number_of_rva_and_sizes) == 92); + +struct pe_optional_header_32_plus { + uint16_t po_magic; + uint8_t po_dont_care[58]; + uint32_t po_size_of_headers; + uint32_t po_checksum; + uint16_t po_subsystem; + uint8_t po_dont_care_either[38]; + uint32_t po_number_of_rva_and_sizes; +} __attribute__((packed)); + +CTASSERT(offsetof(struct pe_optional_header_32_plus, po_size_of_headers) == 60); +CTASSERT(offsetof(struct pe_optional_header_32_plus, po_checksum) == 64); +CTASSERT(offsetof(struct pe_optional_header_32_plus, po_subsystem) == 68); +CTASSERT(offsetof(struct pe_optional_header_32_plus, po_number_of_rva_and_sizes) == 108); + +#define PE_DIRECTORY_ENTRY_CERTIFICATE 4 + +struct pe_directory_entry { + uint32_t pde_rva; + uint32_t pde_size; +} __attribute__((packed)); + +struct pe_section_header { + uint8_t psh_dont_care[16]; + uint32_t psh_size_of_raw_data; + uint32_t psh_pointer_to_raw_data; + uint8_t psh_dont_care_either[16]; +} __attribute__((packed)); + +CTASSERT(offsetof(struct pe_section_header, psh_size_of_raw_data) == 16); +CTASSERT(offsetof(struct pe_section_header, psh_pointer_to_raw_data) == 20); + +#define PE_CERTIFICATE_REVISION 0x0200 +#define PE_CERTIFICATE_TYPE 0x0002 + +struct pe_certificate { + uint32_t pc_len; + uint16_t pc_revision; + uint16_t pc_type; + char pc_signature[0]; +} __attribute__((packed)); + +void +range_check(const struct executable *x, off_t off, size_t len, + const char *name) +{ + + if (off < 0) { + errx(1, "%s starts at negative offset %jd", + name, (intmax_t)off); + } + if (off >= (off_t)x->x_len) { + errx(1, "%s starts at %jd, past the end of executable at %zd", + name, (intmax_t)off, x->x_len); + } + if (len >= x->x_len) { + errx(1, "%s size %zd is larger than the executable size %zd", + name, len, x->x_len); + } + if (off + len > x->x_len) { + errx(1, "%s extends to %jd, past the end of executable at %zd", + name, (intmax_t)(off + len), x->x_len); + } +} + +size_t +signature_size(const struct executable *x) +{ + const struct pe_directory_entry *pde; + + range_check(x, x->x_certificate_entry_off, + x->x_certificate_entry_len, "Certificate Directory"); + + pde = (struct pe_directory_entry *) + (x->x_buf + x->x_certificate_entry_off); + + if (pde->pde_rva != 0 && pde->pde_size == 0) + warnx("signature size is 0, but its RVA is %d", pde->pde_rva); + if (pde->pde_rva == 0 && pde->pde_size != 0) + warnx("signature RVA is 0, but its size is %d", pde->pde_size); + + return (pde->pde_size); +} + +void +show_certificate(const struct executable *x) +{ + struct pe_certificate *pc; + const struct pe_directory_entry *pde; + + range_check(x, x->x_certificate_entry_off, + x->x_certificate_entry_len, "Certificate Directory"); + + pde = (struct pe_directory_entry *) + (x->x_buf + x->x_certificate_entry_off); + + if (signature_size(x) == 0) { + printf("file not signed\n"); + return; + } + +#if 0 + printf("certificate chunk at offset %zd, size %zd\n", + pde->pde_rva, pde->pde_size); +#endif + + range_check(x, pde->pde_rva, pde->pde_size, "Certificate chunk"); + + pc = (struct pe_certificate *)(x->x_buf + pde->pde_rva); + if (pc->pc_revision != PE_CERTIFICATE_REVISION) { + errx(1, "wrong certificate chunk revision, is %d, should be %d", + pc->pc_revision, PE_CERTIFICATE_REVISION); + } + if (pc->pc_type != PE_CERTIFICATE_TYPE) { + errx(1, "wrong certificate chunk type, is %d, should be %d", + pc->pc_type, PE_CERTIFICATE_TYPE); + } + printf("to dump PKCS7:\n " + "dd if='%s' bs=1 skip=%zd | openssl pkcs7 -inform DER -print\n", + x->x_path, pde->pde_rva + offsetof(struct pe_certificate, pc_signature)); + printf("to dump raw ASN.1:\n " + "openssl asn1parse -i -inform DER -offset %zd -in '%s'\n", + pde->pde_rva + offsetof(struct pe_certificate, pc_signature), x->x_path); +} + +static void +parse_section_table(struct executable *x, off_t off, int number_of_sections) +{ + const struct pe_section_header *psh; + int i; + + range_check(x, off, sizeof(*psh) * number_of_sections, + "section table"); + + if (x->x_headers_len <= off + sizeof(*psh) * number_of_sections) + errx(1, "section table outside of headers"); + + psh = (const struct pe_section_header *)(x->x_buf + off); + + if (number_of_sections >= MAX_SECTIONS) { + errx(1, "too many sections: got %d, should be %d", + number_of_sections, MAX_SECTIONS); + } + x->x_nsections = number_of_sections; + + for (i = 0; i < number_of_sections; i++) { + if (psh->psh_pointer_to_raw_data < x->x_headers_len) + errx(1, "section points inside the headers"); + + range_check(x, psh->psh_pointer_to_raw_data, + psh->psh_size_of_raw_data, "section"); +#if 0 + printf("section %d: start %d, size %d\n", + i, psh->psh_pointer_to_raw_data, psh->psh_size_of_raw_data); +#endif + x->x_section_off[i] = psh->psh_pointer_to_raw_data; + x->x_section_len[i] = psh->psh_size_of_raw_data; + psh++; + } +} + +static void +parse_directory(struct executable *x, off_t off, + int number_of_rva_and_sizes, int number_of_sections) +{ + //int i; + const struct pe_directory_entry *pde; + + //printf("Data Directory at offset %zd\n", off); + + if (number_of_rva_and_sizes <= PE_DIRECTORY_ENTRY_CERTIFICATE) { + errx(1, "wrong NumberOfRvaAndSizes %d; should be at least %d", + number_of_rva_and_sizes, PE_DIRECTORY_ENTRY_CERTIFICATE); + } + + range_check(x, off, sizeof(*pde) * number_of_rva_and_sizes, + "PE Data Directory"); + if (x->x_headers_len <= off + sizeof(*pde) * number_of_rva_and_sizes) + errx(1, "PE Data Directory outside of headers"); + + x->x_certificate_entry_off = + off + sizeof(*pde) * PE_DIRECTORY_ENTRY_CERTIFICATE; + x->x_certificate_entry_len = sizeof(*pde); +#if 0 + printf("certificate directory entry at offset %zd, len %zd\n", + x->x_certificate_entry_off, x->x_certificate_entry_len); + + pde = (struct pe_directory_entry *)(x->x_buf + off); + for (i = 0; i < number_of_rva_and_sizes; i++) { + printf("rva %zd, size %zd\n", pde->pde_rva, pde->pde_size); + pde++; + } +#endif + + return (parse_section_table(x, + off + sizeof(*pde) * number_of_rva_and_sizes, number_of_sections)); +} + +/* + * The PE checksum algorithm is undocumented; this code is mostly based on + * http://forum.sysinternals.com/optional-header-checksum-calculation_topic24214.html + * + * "Sum the entire image file, excluding the CheckSum field in the optional + * header, as an array of USHORTs, allowing any carry above 16 bits to be added + * back onto the low 16 bits. Then add the file size to get a 32-bit value." + * + * Note that most software does not care about the checksum at all; perhaps + * we could just set it to 0 instead. + * + * XXX: Endianess? + */ +static uint32_t +compute_checksum(const struct executable *x) +{ + uint32_t cksum = 0; + uint16_t tmp; + int i; + + range_check(x, x->x_checksum_off, x->x_checksum_len, "PE checksum"); + + assert(x->x_checksum_off % 2 == 0); + + for (i = 0; i + sizeof(tmp) < x->x_len; i += 2) { + /* + * Don't checksum the checksum. The +2 is because the checksum + * is 4 bytes, and here we're iterating over 2 byte chunks. + */ + if (i == x->x_checksum_off || i == x->x_checksum_off + 2) { + tmp = 0; + } else { + assert(i + sizeof(tmp) <= x->x_len); + memcpy(&tmp, x->x_buf + i, sizeof(tmp)); + } + + cksum += tmp; + cksum += cksum >> 16; + cksum &= 0xffff; + } + + cksum += cksum >> 16; + cksum &= 0xffff; + + cksum += x->x_len; + + return (cksum); +} + +static void +parse_optional_32_plus(struct executable *x, off_t off, + int number_of_sections) +{ + uint32_t computed_checksum; + const struct pe_optional_header_32_plus *po; + + range_check(x, off, sizeof(*po), "PE Optional Header"); + + po = (struct pe_optional_header_32_plus *)(x->x_buf + off); + switch (po->po_subsystem) { + case PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION: + case PE_OPTIONAL_SUBSYSTEM_EFI_BOOT: + case PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME: + break; + default: + errx(1, "wrong PE Optional Header subsystem 0x%x", + po->po_subsystem); + } + +#if 0 + printf("subsystem %d, checksum 0x%x, %d data directories\n", + po->po_subsystem, po->po_checksum, po->po_number_of_rva_and_sizes); +#endif + + x->x_checksum_off = off + + offsetof(struct pe_optional_header_32_plus, po_checksum); + x->x_checksum_len = sizeof(po->po_checksum); +#if 0 + printf("checksum 0x%x at offset %zd, len %zd\n", + po->po_checksum, x->x_checksum_off, x->x_checksum_len); +#endif + + computed_checksum = compute_checksum(x); + if (computed_checksum != po->po_checksum) { + warnx("invalid PE+ checksum; is 0x%x, should be 0x%x", + po->po_checksum, computed_checksum); + } + + if (x->x_len < x->x_headers_len) + errx(1, "invalid SizeOfHeaders %d", po->po_size_of_headers); + x->x_headers_len = po->po_size_of_headers; + //printf("Size of Headers: %d\n", po->po_size_of_headers); + + return (parse_directory(x, off + sizeof(*po), + po->po_number_of_rva_and_sizes, number_of_sections)); +} + +static void +parse_optional_32(struct executable *x, off_t off, int number_of_sections) +{ + uint32_t computed_checksum; + const struct pe_optional_header_32 *po; + + range_check(x, off, sizeof(*po), "PE Optional Header"); + + po = (struct pe_optional_header_32 *)(x->x_buf + off); + switch (po->po_subsystem) { + case PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION: + case PE_OPTIONAL_SUBSYSTEM_EFI_BOOT: + case PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME: + break; + default: + errx(1, "wrong PE Optional Header subsystem 0x%x", + po->po_subsystem); + } + +#if 0 + printf("subsystem %d, checksum 0x%x, %d data directories\n", + po->po_subsystem, po->po_checksum, po->po_number_of_rva_and_sizes); +#endif + + x->x_checksum_off = off + + offsetof(struct pe_optional_header_32, po_checksum); + x->x_checksum_len = sizeof(po->po_checksum); +#if 0 + printf("checksum at offset %zd, len %zd\n", + x->x_checksum_off, x->x_checksum_len); +#endif + + computed_checksum = compute_checksum(x); + if (computed_checksum != po->po_checksum) { + warnx("invalid PE checksum; is 0x%x, should be 0x%x", + po->po_checksum, computed_checksum); + } + + if (x->x_len < x->x_headers_len) + errx(1, "invalid SizeOfHeaders %d", po->po_size_of_headers); + x->x_headers_len = po->po_size_of_headers; + //printf("Size of Headers: %d\n", po->po_size_of_headers); + + return (parse_directory(x, off + sizeof(*po), + po->po_number_of_rva_and_sizes, number_of_sections)); +} + +static void +parse_optional(struct executable *x, off_t off, int number_of_sections) +{ + const struct pe_optional_header_32 *po; + + //printf("Optional header offset %zd\n", off); + + range_check(x, off, sizeof(*po), "PE Optional Header"); + + po = (struct pe_optional_header_32 *)(x->x_buf + off); + + switch (po->po_magic) { + case PE_OPTIONAL_MAGIC_32: + return (parse_optional_32(x, off, number_of_sections)); + case PE_OPTIONAL_MAGIC_32_PLUS: + return (parse_optional_32_plus(x, off, number_of_sections)); + default: + errx(1, "wrong PE Optional Header magic 0x%x", po->po_magic); + } +} + +static void +parse_pe(struct executable *x, off_t off) +{ + const struct pe_header *pe; + + //printf("PE offset %zd, PE size %zd\n", off, sizeof(*pe)); + + range_check(x, off, sizeof(*pe), "PE header"); + + pe = (struct pe_header *)(x->x_buf + off); + if (pe->pe_signature != PE_SIGNATURE) + errx(1, "wrong PE signature 0x%x", pe->pe_signature); + + //printf("Number of sections: %d\n", pe->pe_coff.coff_number_of_sections); + + parse_optional(x, off + sizeof(*pe), + pe->pe_coff.coff_number_of_sections); +} + +void +parse(struct executable *x) +{ + const struct mz_header *mz; + + range_check(x, 0, sizeof(*mz), "MZ header"); + + mz = (struct mz_header *)x->x_buf; + if (mz->mz_signature[0] != 'M' || mz->mz_signature[1] != 'Z') + errx(1, "MZ header not found"); + + return (parse_pe(x, mz->mz_lfanew)); +} + +static off_t +append(struct executable *x, void *ptr, size_t len) +{ + off_t off; + + /* + * XXX: Alignment. + */ + off = x->x_len; + x->x_buf = realloc(x->x_buf, x->x_len + len); + if (x->x_buf == NULL) + err(1, "realloc"); + memcpy(x->x_buf + x->x_len, ptr, len); + x->x_len += len; + + return (off); +} + +void +update(struct executable *x) +{ + uint32_t checksum; + struct pe_certificate *pc; + struct pe_directory_entry pde; + size_t pc_len; + off_t pc_off; + + pc_len = sizeof(*pc) + x->x_signature_len; + pc = calloc(1, pc_len); + if (pc == NULL) + err(1, "calloc"); + +#if 0 + /* + * Note that pc_len is the length of pc_certificate, + * not the whole structure. + * + * XXX: That's what the spec says - but it breaks at least + * sbverify and "pesign -S", so the spec is probably wrong. + */ + pc->pc_len = x->x_signature_len; +#else + pc->pc_len = pc_len; +#endif + pc->pc_revision = PE_CERTIFICATE_REVISION; + pc->pc_type = PE_CERTIFICATE_TYPE; + memcpy(&pc->pc_signature, x->x_signature, x->x_signature_len); + + pc_off = append(x, pc, pc_len); +#if 0 + printf("added signature chunk at offset %zd, len %zd\n", + pc_off, pc_len); +#endif + + free(pc); + + pde.pde_rva = pc_off; + pde.pde_size = pc_len; + memcpy(x->x_buf + x->x_certificate_entry_off, &pde, sizeof(pde)); + + checksum = compute_checksum(x); + assert(sizeof(checksum) == x->x_checksum_len); + memcpy(x->x_buf + x->x_checksum_off, &checksum, sizeof(checksum)); +#if 0 + printf("new checksum 0x%x\n", checksum); +#endif +} diff --git a/usr.sbin/uefisign/uefisign.8 b/usr.sbin/uefisign/uefisign.8 new file mode 100644 index 0000000..0bda8c9 --- /dev/null +++ b/usr.sbin/uefisign/uefisign.8 @@ -0,0 +1,93 @@ +.\" Copyright (c) 2014 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" This software was developed by Edward Tomasz Napierala under sponsorship +.\" from the FreeBSD Foundation. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd December 10, 2014 +.Dt UEFISIGN 8 +.Os +.Sh NAME +.Nm uefisign +.Nd UEFI Secure Boot signing utility +.Sh SYNOPSIS +.Nm +.Fl k Ar key +.Fl c Ar certificate +.Fl o Ar output +.Op Fl v +.Ar file +.Nm +.Fl V +.Op Fl v +.Ar file +.Sh DESCRIPTION +The +.Nm +utility signs PE binary files using Authenticode scheme, as required by +UEFI Secure Boot specification. +Alternatively, it can be used to view and verify existing signatures. +These options are available: +.Bl -tag -width ".Fl l" +.It Fl V +Determine whether the file is signed. +Note that this does not verify the correctness of the signature; +only that the file contains a signature. +.It Fl k +Name of file containing the private key used to sign the binary. +.It Fl c +Name of file containing the certificate used to sign the binary. +.It Fl o +Name of file to write the signed binary to. +.It Fl v +Be verbose. +.El +.Sh EXIT STATUS +The +.Nm +utility exits 0 on success, and >0 if an error occurs. +.Sh EXAMPLES +Generate self-signed certificate and use it to sign a binary: +.Dl /usr/share/examples/uefisign/uefikeys testcert +.Dl uefisign -c testcert.pem -k testcert.key -o signed-binary binary +.Pp +View signature: +.Dl uefisign -Vv binary +.Sh SEE ALSO +.Xr openssl 1 , +.Xr loader 8 , +.Xr uefi 8 +.Sh HISTORY +The +.Nm +command appeared in +.Fx 11.0 . +.Sh AUTHORS +The +.Nm +utility was developed by +.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. diff --git a/usr.sbin/uefisign/uefisign.c b/usr.sbin/uefisign/uefisign.c new file mode 100644 index 0000000..927f02b --- /dev/null +++ b/usr.sbin/uefisign/uefisign.c @@ -0,0 +1,425 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/wait.h> +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/conf.h> +#include <openssl/evp.h> +#include <openssl/err.h> +#include <openssl/pem.h> +#include <openssl/pkcs7.h> + +#include "uefisign.h" +#include "magic.h" + +static void +usage(void) +{ + + fprintf(stderr, "usage: uefisign -c cert -k key -o outfile [-v] file\n" + " uefisign -V [-c cert] [-v] file\n"); + exit(1); +} + +static char * +checked_strdup(const char *s) +{ + char *c; + + c = strdup(s); + if (c == NULL) + err(1, "strdup"); + return (c); +} + +FILE * +checked_fopen(const char *path, const char *mode) +{ + FILE *fp; + + assert(path != NULL); + + fp = fopen(path, mode); + if (fp == NULL) + err(1, "%s", path); + return (fp); +} + +void +send_chunk(const void *buf, size_t len, int pipefd) +{ + ssize_t ret; + + ret = write(pipefd, &len, sizeof(len)); + if (ret != sizeof(len)) + err(1, "write"); + ret = write(pipefd, buf, len); + if (ret != (ssize_t)len) + err(1, "write"); +} + +void +receive_chunk(void **bufp, size_t *lenp, int pipefd) +{ + ssize_t ret; + size_t len; + void *buf; + + ret = read(pipefd, &len, sizeof(len)); + if (ret != sizeof(len)) + err(1, "read"); + + buf = calloc(1, len); + if (buf == NULL) + err(1, "calloc"); + + ret = read(pipefd, buf, len); + if (ret != (ssize_t)len) + err(1, "read"); + + *bufp = buf; + *lenp = len; +} + +static char * +bin2hex(const char *bin, size_t bin_len) +{ + unsigned char *hex, *tmp, ch; + size_t hex_len; + size_t i; + + hex_len = bin_len * 2 + 1; /* +1 for '\0'. */ + hex = malloc(hex_len); + if (hex == NULL) + err(1, "malloc"); + + tmp = hex; + for (i = 0; i < bin_len; i++) { + ch = bin[i]; + tmp += sprintf(tmp, "%02x", ch); + } + + return (hex); +} + +/* + * We need to replace a standard chunk of PKCS7 signature with one mandated + * by Authenticode. Problem is, replacing it just like that and then calling + * PKCS7_final() would make OpenSSL segfault somewhere in PKCS7_dataFinal(). + * So, instead, we call PKCS7_dataInit(), then put our Authenticode-specific + * data into BIO it returned, then call PKCS7_dataFinal() - which now somehow + * does not panic - and _then_ we replace it in the signature. This technique + * was used in sbsigntool by Jeremy Kerr, and might have originated in + * osslsigncode. + */ +static void +magic(PKCS7 *pkcs7, const char *digest, size_t digest_len) +{ + BIO *bio, *t_bio; + ASN1_TYPE *t; + ASN1_STRING *s; + CONF *cnf; + unsigned char *buf, *tmp; + char *digest_hex, *magic_conf, *str; + int len, nid, ok; + + digest_hex = bin2hex(digest, digest_len); + + /* + * Construct the SpcIndirectDataContent chunk. + */ + nid = OBJ_create("1.3.6.1.4.1.311.2.1.4", NULL, NULL); + + asprintf(&magic_conf, magic_fmt, digest_hex); + if (magic_conf == NULL) + err(1, "asprintf"); + + bio = BIO_new_mem_buf((void *)magic_conf, -1); + if (bio == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "BIO_new_mem_buf(3) failed"); + } + + cnf = NCONF_new(NULL); + if (cnf == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "NCONF_new(3) failed"); + } + + ok = NCONF_load_bio(cnf, bio, NULL); + if (ok == 0) { + ERR_print_errors_fp(stderr); + errx(1, "NCONF_load_bio(3) failed"); + } + + str = NCONF_get_string(cnf, "default", "asn1"); + if (str == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "NCONF_get_string(3) failed"); + } + + t = ASN1_generate_nconf(str, cnf); + if (t == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "ASN1_generate_nconf(3) failed"); + } + + /* + * We now have our proprietary piece of ASN.1. Let's do + * the actual signing. + */ + len = i2d_ASN1_TYPE(t, NULL); + tmp = buf = calloc(1, len); + if (tmp == NULL) + err(1, "calloc"); + i2d_ASN1_TYPE(t, &tmp); + + /* + * We now have contents of 't' stuffed into memory buffer 'buf'. + */ + tmp = NULL; + t = NULL; + + t_bio = PKCS7_dataInit(pkcs7, NULL); + if (t_bio == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "PKCS7_dataInit(3) failed"); + } + + BIO_write(t_bio, buf + 2, len - 2); + + ok = PKCS7_dataFinal(pkcs7, t_bio); + if (ok == 0) { + ERR_print_errors_fp(stderr); + errx(1, "PKCS7_dataFinal(3) failed"); + } + + t = ASN1_TYPE_new(); + s = ASN1_STRING_new(); + ASN1_STRING_set(s, buf, len); + ASN1_TYPE_set(t, V_ASN1_SEQUENCE, s); + + PKCS7_set0_type_other(pkcs7->d.sign->contents, nid, t); +} + +static void +sign(X509 *cert, EVP_PKEY *key, int pipefd) +{ + PKCS7 *pkcs7; + BIO *bio, *out; + const EVP_MD *md; + PKCS7_SIGNER_INFO *info; + void *digest, *signature; + size_t digest_len, signature_len; + int ok; + + assert(cert != NULL); + assert(key != NULL); + + receive_chunk(&digest, &digest_len, pipefd); + + bio = BIO_new_mem_buf(digest, digest_len); + if (bio == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "BIO_new_mem_buf(3) failed"); + } + + pkcs7 = PKCS7_sign(NULL, NULL, NULL, bio, PKCS7_BINARY | PKCS7_PARTIAL); + if (pkcs7 == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "PKCS7_sign(3) failed"); + } + + md = EVP_get_digestbyname(DIGEST); + if (md == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "EVP_get_digestbyname(\"%s\") failed", DIGEST); + } + + info = PKCS7_sign_add_signer(pkcs7, cert, key, md, 0); + if (info == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "PKCS7_sign_add_signer(3) failed"); + } + + /* + * XXX: All the signed binaries seem to have this, but where is it + * described in the spec? + */ + PKCS7_add_signed_attribute(info, NID_pkcs9_contentType, + V_ASN1_OBJECT, OBJ_txt2obj("1.3.6.1.4.1.311.2.1.4", 1)); + + magic(pkcs7, digest, digest_len); + +#if 0 + out = BIO_new(BIO_s_file()); + BIO_set_fp(out, stdout, BIO_NOCLOSE); + PKCS7_print_ctx(out, pkcs7, 0, NULL); + + i2d_PKCS7_bio(out, pkcs7); +#endif + + out = BIO_new(BIO_s_mem()); + if (out == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "BIO_new(3) failed"); + } + + ok = i2d_PKCS7_bio(out, pkcs7); + if (ok == 0) { + ERR_print_errors_fp(stderr); + errx(1, "i2d_PKCS7_bio(3) failed"); + } + + signature_len = BIO_get_mem_data(out, &signature); + if (signature_len <= 0) { + ERR_print_errors_fp(stderr); + errx(1, "BIO_get_mem_data(3) failed"); + } + + (void)BIO_set_close(out, BIO_NOCLOSE); + BIO_free(out); + + send_chunk(signature, signature_len, pipefd); +} + +static int +wait_for_child(pid_t pid) +{ + int status; + + pid = waitpid(pid, &status, 0); + if (pid == -1) + err(1, "waitpid"); + + return (WEXITSTATUS(status)); +} + +int +main(int argc, char **argv) +{ + int ch, error; + bool Vflag = false, vflag = false; + const char *certpath = NULL, *keypath = NULL, *outpath = NULL, *inpath = NULL; + FILE *certfp = NULL, *keyfp = NULL; + X509 *cert = NULL; + EVP_PKEY *key = NULL; + pid_t pid; + int pipefds[2]; + + while ((ch = getopt(argc, argv, "Vc:k:o:v")) != -1) { + switch (ch) { + case 'V': + Vflag = true; + break; + case 'c': + certpath = checked_strdup(optarg); + break; + case 'k': + keypath = checked_strdup(optarg); + break; + case 'o': + outpath = checked_strdup(optarg); + break; + case 'v': + vflag = true; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + if (argc != 1) + usage(); + + if (Vflag) { + if (certpath != NULL) + errx(1, "-V and -c are mutually exclusive"); + if (keypath != NULL) + errx(1, "-V and -k are mutually exclusive"); + if (outpath != NULL) + errx(1, "-V and -o are mutually exclusive"); + } else { + if (certpath == NULL) + errx(1, "-c option is mandatory"); + if (keypath == NULL) + errx(1, "-k option is mandatory"); + if (outpath == NULL) + errx(1, "-o option is mandatory"); + } + + inpath = argv[0]; + + OPENSSL_config(NULL); + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); + + error = pipe(pipefds); + if (error != 0) + err(1, "pipe"); + + pid = fork(); + if (pid < 0) + err(1, "fork"); + + if (pid == 0) + return (child(inpath, outpath, pipefds[1], Vflag, vflag)); + + if (!Vflag) { + certfp = checked_fopen(certpath, "r"); + cert = PEM_read_X509(certfp, NULL, NULL, NULL); + if (cert == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "failed to load certificate from %s", certpath); + } + + keyfp = checked_fopen(keypath, "r"); + key = PEM_read_PrivateKey(keyfp, NULL, NULL, NULL); + if (key == NULL) { + ERR_print_errors_fp(stderr); + errx(1, "failed to load private key from %s", keypath); + } + + sign(cert, key, pipefds[0]); + } + + return (wait_for_child(pid)); +} diff --git a/usr.sbin/uefisign/uefisign.h b/usr.sbin/uefisign/uefisign.h new file mode 100644 index 0000000..d0920b8 --- /dev/null +++ b/usr.sbin/uefisign/uefisign.h @@ -0,0 +1,91 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef EFISIGN_H +#define EFISIGN_H + +#include <stdbool.h> +#include <openssl/evp.h> + +#define DIGEST "SHA256" +#define MAX_SECTIONS 128 + +struct executable { + const char *x_path; + FILE *x_fp; + + char *x_buf; + size_t x_len; + + /* + * Set by pe_parse(), used by digest(). + */ + size_t x_headers_len; + + off_t x_checksum_off; + size_t x_checksum_len; + + off_t x_certificate_entry_off; + size_t x_certificate_entry_len; + + int x_nsections; + off_t x_section_off[MAX_SECTIONS]; + size_t x_section_len[MAX_SECTIONS]; + + /* + * Computed by digest(). + */ + unsigned char x_digest[EVP_MAX_MD_SIZE]; + unsigned int x_digest_len; + + /* + * Received from the parent process, which computes it in sign(). + */ + void *x_signature; + size_t x_signature_len; +}; + + +FILE *checked_fopen(const char *path, const char *mode); +void send_chunk(const void *buf, size_t len, int pipefd); +void receive_chunk(void **bufp, size_t *lenp, int pipefd); + +int child(const char *inpath, const char *outpath, int pipefd, + bool Vflag, bool vflag); + +void parse(struct executable *x); +void update(struct executable *x); +size_t signature_size(const struct executable *x); +void show_certificate(const struct executable *x); +void range_check(const struct executable *x, + off_t off, size_t len, const char *name); + +#endif /* !EFISIGN_H */ |