Add uefisign(8), UEFI Secure Boot signing utility.
MFC after: 1 month Sponsored by: The FreeBSD Foundation
This commit is contained in:
parent
f584f51d96
commit
e595e65b8a
@ -86,6 +86,7 @@ SUBDIR= adduser \
|
||||
traceroute \
|
||||
trpt \
|
||||
tzsetup \
|
||||
uefisign \
|
||||
ugidfw \
|
||||
vigr \
|
||||
vipw \
|
||||
|
11
usr.sbin/uefisign/Makefile
Normal file
11
usr.sbin/uefisign/Makefile
Normal file
@ -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>
|
277
usr.sbin/uefisign/child.c
Normal file
277
usr.sbin/uefisign/child.c
Normal file
@ -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);
|
||||
}
|
66
usr.sbin/uefisign/magic.h
Normal file
66
usr.sbin/uefisign/magic.h
Normal file
@ -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";
|
560
usr.sbin/uefisign/pe.c
Normal file
560
usr.sbin/uefisign/pe.c
Normal file
@ -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
|
||||
}
|
93
usr.sbin/uefisign/uefisign.8
Normal file
93
usr.sbin/uefisign/uefisign.8
Normal file
@ -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.
|
425
usr.sbin/uefisign/uefisign.c
Normal file
425
usr.sbin/uefisign/uefisign.c
Normal file
@ -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));
|
||||
}
|
91
usr.sbin/uefisign/uefisign.h
Normal file
91
usr.sbin/uefisign/uefisign.h
Normal file
@ -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 */
|
Loading…
x
Reference in New Issue
Block a user