Date: Thu, 26 Feb 2015 09:15:25 +0000 (UTC) From: Edward Tomasz Napierala <trasz@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r279315 - in head/usr.sbin: . uefisign Message-ID: <201502260915.t1Q9FPxD075675@svn.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: trasz Date: Thu Feb 26 09:15:24 2015 New Revision: 279315 URL: https://svnweb.freebsd.org/changeset/base/279315 Log: Add uefisign(8), UEFI Secure Boot signing utility. MFC after: 1 month Sponsored by: The FreeBSD Foundation Added: head/usr.sbin/uefisign/ head/usr.sbin/uefisign/Makefile (contents, props changed) head/usr.sbin/uefisign/child.c (contents, props changed) head/usr.sbin/uefisign/magic.h (contents, props changed) head/usr.sbin/uefisign/pe.c (contents, props changed) head/usr.sbin/uefisign/uefisign.8 (contents, props changed) head/usr.sbin/uefisign/uefisign.c (contents, props changed) head/usr.sbin/uefisign/uefisign.h (contents, props changed) Modified: head/usr.sbin/Makefile Modified: head/usr.sbin/Makefile ============================================================================== --- head/usr.sbin/Makefile Thu Feb 26 09:08:48 2015 (r279314) +++ head/usr.sbin/Makefile Thu Feb 26 09:15:24 2015 (r279315) @@ -86,6 +86,7 @@ SUBDIR= adduser \ traceroute \ trpt \ tzsetup \ + uefisign \ ugidfw \ vigr \ vipw \ Added: head/usr.sbin/uefisign/Makefile ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/usr.sbin/uefisign/Makefile Thu Feb 26 09:15:24 2015 (r279315) @@ -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> Added: head/usr.sbin/uefisign/child.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/usr.sbin/uefisign/child.c Thu Feb 26 09:15:24 2015 (r279315) @@ -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); +} Added: head/usr.sbin/uefisign/magic.h ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/usr.sbin/uefisign/magic.h Thu Feb 26 09:15:24 2015 (r279315) @@ -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"; Added: head/usr.sbin/uefisign/pe.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/usr.sbin/uefisign/pe.c Thu Feb 26 09:15:24 2015 (r279315) @@ -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 +} Added: head/usr.sbin/uefisign/uefisign.8 ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/usr.sbin/uefisign/uefisign.8 Thu Feb 26 09:15:24 2015 (r279315) @@ -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 *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201502260915.t1Q9FPxD075675>