Index: contrib/elftoolchain/libelf/_libelf.h =================================================================== --- contrib/elftoolchain/libelf/_libelf.h +++ contrib/elftoolchain/libelf/_libelf.h @@ -29,6 +29,8 @@ #ifndef __LIBELF_H_ #define __LIBELF_H_ +#include + #include #include "_libelf_config.h" @@ -229,6 +231,8 @@ int _libelf_setshnum(Elf *_e, void *_eh, int _elfclass, size_t _shnum); int _libelf_setshstrndx(Elf *_e, void *_eh, int _elfclass, size_t _shstrndx); +Elf_Data * _libelf_getdata(Elf_Scn *s, Elf_Data *ed, bool updating); +Elf_Data * _libelf_rawdata(Elf_Scn *s, Elf_Data *ed, bool updating); Elf_Data *_libelf_xlate(Elf_Data *_d, const Elf_Data *_s, unsigned int _encoding, int _elfclass, int _direction); int _libelf_xlate_shtype(uint32_t _sht); Index: contrib/elftoolchain/libelf/elf_data.c =================================================================== --- contrib/elftoolchain/libelf/elf_data.c +++ contrib/elftoolchain/libelf/elf_data.c @@ -29,13 +29,14 @@ #include #include #include +#include #include "_libelf.h" ELFTC_VCSID("$Id: elf_data.c 3466 2016-05-11 18:35:44Z emaste $"); Elf_Data * -elf_getdata(Elf_Scn *s, Elf_Data *ed) +_libelf_getdata(Elf_Scn *s, Elf_Data *ed, bool updating) { Elf *e; unsigned int sh_type; @@ -94,7 +95,9 @@ if ((elftype = _libelf_xlate_shtype(sh_type)) < ELF_T_FIRST || elftype > ELF_T_LAST || (sh_type != SHT_NOBITS && - (sh_offset > e->e_rawsize || sh_size > e->e_rawsize - sh_offset))) { + (!updating && + (sh_offset > e->e_rawsize || + sh_size > e->e_rawsize - sh_offset)))) { LIBELF_SET_ERROR(SECTION, 0); return (NULL); } @@ -165,6 +168,12 @@ return (&d->d_data); } +Elf_Data * +elf_getdata(Elf_Scn *s, Elf_Data *ed) +{ + return (_libelf_getdata(s, ed, false)); +} + Elf_Data * elf_newdata(Elf_Scn *s) { @@ -209,7 +218,7 @@ */ Elf_Data * -elf_rawdata(Elf_Scn *s, Elf_Data *ed) +_libelf_rawdata(Elf_Scn *s, Elf_Data *ed, bool updating) { Elf *e; int elf_class; @@ -254,7 +263,9 @@ } if (sh_type != SHT_NOBITS && - (sh_offset > e->e_rawsize || sh_size > e->e_rawsize - sh_offset)) { + (!updating && + (sh_offset > e->e_rawsize || + sh_size > e->e_rawsize - sh_offset))) { LIBELF_SET_ERROR(SECTION, 0); return (NULL); } @@ -274,3 +285,9 @@ return (&d->d_data); } + +Elf_Data * +elf_rawdata(Elf_Scn *s, Elf_Data *ed) +{ + return (_libelf_rawdata(s, ed, false)); +} Index: contrib/elftoolchain/libelf/elf_update.c =================================================================== --- contrib/elftoolchain/libelf/elf_update.c +++ contrib/elftoolchain/libelf/elf_update.c @@ -182,7 +182,8 @@ * Otherwise, we need to bring in the section's data * from the underlying ELF object. */ - if (e->e_cmd != ELF_C_WRITE && elf_getdata(s, NULL) == NULL) + if (e->e_cmd != ELF_C_WRITE && + _libelf_getdata(s, NULL, true) == NULL) return (0); } Index: share/man/man5/Makefile =================================================================== --- share/man/man5/Makefile +++ share/man/man5/Makefile @@ -63,6 +63,7 @@ resolver.5 \ services.5 \ shells.5 \ + signed-elf.5 \ src.conf.5 \ stab.5 \ style.Makefile.5 \ Index: share/man/man5/signed-elf.5 =================================================================== --- /dev/null +++ share/man/man5/signed-elf.5 @@ -0,0 +1,307 @@ +.\" Copyright (c) 2017 Eric McCorkle +.\" All rights reserved. +.\" +.\" 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$ +.\" +.Dd April 24, 2017 +.Dt SIGNED-ELF 5 +.Os +.Sh NAME +.Nm signed-elf +.Nd "cryptographically signed ELF binaries" +.Sh DESCRIPTION +Signed ELF binaries are files in the Executable Linkable Format +.Pq Xr elf 5 +that contain extra metadata bearing a cryptographic signature that +allows the contents of the file to be verified against a public key. +.Ss Format Specification +The signed ELF format makes use of features intrinsic to the ELF file +format to store signatures. The signature data for a signed ELF is +stored in a section named +.Sy .sign . +The +.Sy .sign +section is similar in +nature to the +.Sy .comment +section and typically will not be included in +any loadable segment. The signature data itself is stored as a +DER-encoded CMS detached signature, typically without metadata such +as certificates and chains, MIME capabilities, attributes, or other +such extras. The signature is computed in binary mode using the +contents of the entire file, but with the entire +.Sy .sign +section +containing zero data. +.Ss Ciphers and Hashes +The CMS format is designed to allow for a variable selection of +ciphers. In order to maintain strong security properties, the signed +ELF specification imposes the following restrictions on ciphers and +hashes: +.Bl -bullet -offset indent +.It +The allowed hash algorithms are as follows: SHA256, SHA384, SHA512, +SHA3 (Keccak), Blake-2b, Skein, and Whirlpool. The default hash is +SHA256. +.It +RSA digital signatures are allowed with key lengths of 4096 bits or higher +.It +ECDSA/EdDSA signatures are allowed for curves satisfying all of the +safety properties described by the SafeCurves project (at the time of +writing, this list consists of the following curves: M-221, E-222, +Curve1174, Curve25519, E-382, M-383, Curve383187, Curve41417, +Goldilocks-448, M-511, and E-521). +.El +.Pp +Any signature which does not use an allowed hash and cipher will be +rejected. +.Pp +These restrictions may be changed at a later date, but will generally +progress in the direction of stronger security properties. +.Ss Creation and Verification +The procedure for generating a signature typically requires +modification of the ELF file. The procedure is as follows: +.Bl -enum -offset indent +.It +The size of the signature is calculated. (This requires that the +signature size be invariant with respect to the data being signed- a +property possessed by CMS detached signatures.) +.It +The +.Sy .sign +section is added and initialized to the proper size. (Often +times this necessitates the creation of string table and section +header entries and some file offsets may change as a result.) +.It +The +.Sy .sign +section is filled with zeros. +.It +The signature is computed using the entire file as input. +.It +The Signature is written into the +.Sy .sign +section in CMS detached +format using the DER enconding. +.El +.Pp +The verification procedure is as follows: +.Bl -enum -offset indent +.It +The +.Sy .sign +section is located, and the CMS detached signature is loaded +.It +The +.Sy .sign +section is overwritten with zeros. +.It +The signature is verified using the entire file as input. +.El +.Ss Usage +Signed ELF files are primarily intended to be verified by mechanisms such as +.Xr loader 8 , +.Xr kldload 8 , +.Xr execve 2 , +and similar mechanisms as part of a chain-of-trust security protocol +designed to prevent tampering with executable files, shared libraries, +kernel modules, and the like. As such, signed ELF files will almost +always be of the executable or shared object variety, as these are +typically not modified following their creation. While it is possible +to sign archives and linkable objects in the same manner, it typically +makes no sense to do so. The security properties of cryptographic +signatures prevent even the slightest modification of the data they +protect, and both linkable objects and archives are specifically +designed to be modified and composed into final products. +.Pp +It should be noted, however, that some applications (often programming +language runtime systems) are known to (ab)use linkable objects or +archives in ways that more closely resemble the intended use of +executable or shared objects: as a runtime-supported executable, as a +kind of loadable module, or for even more exotic uses. In such cases, +signing such files may well serve a useful purpose. +.Sh CERTIFICATES +Signed ELF files require public-key certificates for verification +(note that new signatures can only be produced using the corresponding +private key). The +.Xr openssl 1 +tool suite provides a number of utilities for creating and managing +private keys and public-key certificates. +.Pp +A FreeBSD installation maintains a set of trusted public key +certificates to serve as a trust root, and at least one signing key to +serve as a trust root. The default location for these certificates +and keys is +.Pa /etc/trust +with signing keys being stored in +.Pa /etc/trust/priv +and public key certificates in +.Pa /etc/trust/certs +(this allows differing permissions on the two directories), and with +trust root keys and certificates being stored in a similar fashion +under +.Pa /etc/trust/root/priv +and +.Pa /etc/trust/root/certs. +The default public and private keys use for signing locally-produced +binaries are located at +.Pa /etc/trust/priv/local.pem +and +.Pa /etc/trust/priv/local.pub.pem +respectively. Additional public key certificates may be stored in +.Pa /etc/trust/certs +that have no corresponding private key. This can be used to grant +trust to binaries produced by a third party. +.Sh UTILITIES +There are several ways of creating, modifying, and verifying signed +ELF binaries. +.Ss Preferred Method +The preferred method for creating and verifying signed ELF files is the +.Xr signelf 8 +utility. (See the man page for usage details.) +.Ss Signing with Binutils and OpenSSL +Signed ELF files can also be created and verified using the +.Xr objcopy 1 +and +.Xr openssl 1 +utilities. This method can be used in cases where +.Xr signelf 5 +is not available for some reason, and can also be used to support +signed ELF files on foreign operating systems. +.Pp +A signed ELF file can be created using +.Xr objcopy 1 +and +.Xr openssl 1 +with the following procedure: +.Pp +First, add the +.Sy .sign +section: +.Pp +.Dl "$ objcopy --add-section .sign=zeros myexe" +.Pp +Where +.Pa zeros +is a file exactly as large as a signature which contains zeros. Next, +sign the binary: +.Pp +.Dl "$ openssl cms -sign -outform DER -md sha256 -binary -nosmimecap \\" +.Dl " -nocerts -noattr -signer cert.pem \\" +.Dl " -inkey key.pem -in myexe -out signature" +.Pp +Last, update the +.Sy .sign +section to contain the signature data: +.Pp +.Dl "$ objcopy --update-section .sign=signature myexe" +.Pp +.Ss Verification with Binutils and OpenSSL +A signature can be verified with +.Xr objcopy 1 +and +.Xr openssl 1 +by the following procedure: +.Pp +First, extract the signature: +.Pp +.Dl "$ objcopy --update-section .sign=signature myexe" +.Pp +Now, zero out the signature: +.Pp +.Dl "$ objcopy --update-section .sign=zeros myexe myexe.tmp" +.Pp +Last, verify the signature: +.Pp +.Dl "$ openssl cms -verify -nointern -inform DER -binary \\" +.Dl " -in signature -content signelf -certfile cert.pem \\" +.Dl " -CApath /etc/trust/certs -out /dev/null" +.Pp +The check against the system trusted keys can be omitted as follows: +.Pp +.Dl "$ openssl cms -verify -nointern -noverify -inform DER -binary \\" +.Dl " -in signature -content signelf -certfile cert.pem \\" +.Dl " -out /dev/null" +.Pp +.Ss Deleting Signatures +Lastly, signatures can be deleted from a file using the +.Xr objcopy 3 +utility: +.Pp +.Dl "$ objcopy -R .sign myexe" +.Pp +.Sh WARNINGS +Any modification of a signed ELF binary- however slight -will cause +signature verification to fail (as intended). Furthermore, due to the +nature of the ELF format, signatures will likely be retained if a +signed ELF binary is used as input to a tool such as +.Xr objcopy 1 +or similar utilities, which will result in an ELF binary containing a +bad signature. +.Pp +It is therefore recommended to only sign executables and shared +objects after at the end of all compilation steps. +.Pp +As of the time of writing, it is reasonably likely that quantum +computing devices capable of attacking public-key ciphers will become +available in the foreseeable future. Should this become a reality, the +security of RSA- and ECC-based public key algorithms will be compromised. +This has major implications for any systems utilizing ELF signatures (or +public-key cryptography generally): namely that such systems should avoid +hardwired or burned-in keys, that they should assume keys will be +periodically changed, and that they should incorporate key expiration +and revocation into their designs. Additionally, users should assume that +the cipher suite will undergo significant changes to incorporate post-quantum +ciphers and remove quantum-vulnerable ones. +.Sh SEE ALSO +.Xr trust-config 7 , +.Xr elf 5 , +.Xr signelf 5 , +.Xr openssl 1 , +.Xr cms 1 , +.Xr objcopy 1 +.Rs +.%A Hewlett Packard +.%B Elf-64 Object File Format +.Re +.Rs +.%A Unix System Laboratories +.%T Object Files +.%B "Executable and Linking Format (ELF)" +.Re +.Rs +.%A Internet Engineering Task Force (IETF) +.%B RFC 2315 (CMS) +.Re +.Rs +.%A Daniel J. Bernstein and Tanja Lange. +.%T SafeCurves: choosing safe curves for elliptic-curve cryptography. +.%B https://safecurves.cr.yp.to/ +.Re +.Sh HISTORY +The signed ELF binary specification first appeared in +.Fx 12.0 . +.Sh AUTHORS +This manual page was written by +.An Eric L. McCorkle Aq Mt emc2@metricspace.net . Index: share/man/man7/Makefile =================================================================== --- share/man/man7/Makefile +++ share/man/man7/Makefile @@ -31,6 +31,7 @@ stdint.7 \ sticky.7 \ tests.7 \ + trust-config.7 \ tuning.7 MLINKS= intro.7 miscellaneous.7 Index: share/man/man7/trust-config.7 =================================================================== --- /dev/null +++ share/man/man7/trust-config.7 @@ -0,0 +1,243 @@ +.\" Copyright (c) 2017 Eric McCorkle +.\" All rights reserved. +.\" +.\" 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$ +.\" +.Dd April 25, 2017 +.Dt TRUST-CONFIG 7 +.Os +.Sh NAME +.Nm trust-config +.Nd "trust system configuration" +.Sh DESCRIPTION +The trust system configuration specifices the the trust root +certificates, intermediate certificates, and provides the trusted +signing keys, allowing users to create signed executables (see +.Xr signed-elf 5) +as well as other signed assets. It provides the default keys to +.Xr signelf 8 +and other utilities, and controls which keys are included in critical +system components as the builtin trust root set. +.Sh TERMINOLOGY +The trust configuration controls the behavior of a system consisting +of several parts with interlocking functions; thus, it is essential to +be clear about the terminology used to describe both the system and +its configuration. +.Ss Trust Root Certificates +The +.Sy trust root certificates +are a set of public (verification) certificates which are built +directly in to critical system components. The trust root +certificates are then used to verify intermediate certificates, +revocation lists, signed executables, and other signed assets. +.Pp +System components such as the kernel and +.Xr loader 8 +which play an essential role in the chain of custody from boot to user +must necessarily include the trust root set into their binaries (which +are presumably verified by earlier boot stages or stored in flash +memory). The build process must therefore collect a set of desired +keys and build them into these components. The +.Sy trust root configuration +is the set of certificates and additional information that controls +this aspect of the build process. +.Ss Intermediate Certificates +The +.Sy intermediate certificates +are a set of public (verification) certificates which have a valid +signature chain back to a trust root certificate. The combined set of +intermediate and root certificates are known as the +.Sy trust certificates . +.Ss Trusted Signing Keys +The +.Sy trusted signing keys +are a set of private keys that correspond to public keys in the trust +certificates. These are used to produce signatures for various assets +that can be verified by the trust certficates. +.Ss Trust System Configuration +The +.Sy trust system configuration +is the combination of the trust root configuration and the trusted +signing keys. +.Sh FILES +The trust system configuration consists of the following files and +directories: +.Bl -bullet indent +.It +.Pa /etc/trust/ : +The base directory for the trust system configuration +.It +.Pa /etc/trust/certs/ : +The directory containing the intermediate trust certificates +.It +.Pa /etc/trust/priv/ : +The directory containing the intermediate signing keys +.It +.Pa /etc/trust/root : +The base directory for the trust root configuration +.It +.Pa /etc/trust/root/certs/ : +The directory containing the trust root certificates +.It +.Pa /etc/trust/root/priv/ : +The directory containing the trust root signing keys +.El +.Pp +The trusted signing key directories +.Pa /etc/trust/priv/ +and +.Pa /etc/trust/root/priv/ +contain PEM-encoded private keys or PKCS#8 data structures. The +standard file naming convention for a key named +.Qq mykey +is +.Pa mykey.pem . +.Pp +The trusted certificate directories +.Pa /etc/trust/certs/ +and +.Pa /etc/trust/root/certs/ +contain PEM-encoded X509 certificates, and is generally compatible +with OpenSSL CA directory parameters or configuration options. +Certificates under +.Pa /etc/trust/root/certs/ +will typically be self-signed certificates, though nothing prevents +the inclusion of certificates signed by a third party. Note, however, +that signatures on trust root keys are ignored by all trust system +components. Certificates under +.Pa /etc/trust/certs/ +must have a valid chain of signatures back to a certificate under +.Pa /etc/trust/root/certs/ . +.Ss Trusted Signing Keys +Some certificates in the trust certificate directories correspond to +trusted signing keys in +.Pa /etc/trust/priv +or +.Pa /etc/trust/root/priv. +The naming convention for signing keys and their corresponding +certificates is as follows. Signing key filenames consist of the +key's name, followed by ".pem". The corresponding certificate +consists of the same name, followed by ".pub.pem". Certificates in +the +.Pa /etc/trust/certs/ +may be optionally preceeded by up to four numerals (0-9), followed by +a "." (this allows administrators to control the order in which +intermediate certificates will be encountered when listing a directory). +.Pp +For example, the filename for a private key named +.Qq mykey +would be +.Pa mykey.pem , +and its corresponding certificate would be +.Pa mykey.pub.pem +(or possibly +.Pa 00.mykey.pub.pem , +if it were stored in +.Pa /etc/trust/certs/ +). Additionally, keys in +.Pa /etc/trust/priv +may only correspond to keys in +.Pa /etc/trust/certs/ ; +similarly, keys in +.Pa /etc/trust/root/priv +may only correspond to keys in +.Pa /etc/trust/root/certs/ . +It is an illegal configuration to have a key in +.Pa /etc/trust/priv +and a corresponding cert in +.Pa /etc/trust/root/certs/ , +or vice versa. It is also an illegal configuration to have +certificates or keys with the same name in both +.Pa /etc/trust/root/ , +and +.Pa /etc/trust/ . + +.Ss Third-Party Trust Keys +It is not required that all public key certificates in the trust root +configuration have a corresponding trusted signing key (with one +exception; see below). There are many circumstances in which this may +be desirable, such as administration of large networks or distribution +of pre-built binaries from a trusted source. Public-key certificates +in the trust root configuration without a corresponding trusted +signing key are known as +.Qq third-party keys . +.Pp +It is important to consider the security implications of third-party +keys before accepting such a key into a trust root configuration. +Because of the severity of these implications, there is no requirement +that any configuration include any third-party key(s), nor will there +ever be such a requirement. +.Pp +Furthermore, the +.Xr signelf 5 +utility is perfectly capable of overwriting signatures from +third-party keys with a signature generated by a locally-controlled +keypair; thus, assets signed by a third party can easily be inspected +and re-signed locally at the behest of the system administrator. +.Ss Local Keypair +The key name +.Qq local +(which corresponds to the private key path +.Pa /etc/trust/root/priv/local.pem +and the public key path +.Pa /etc/trust/root/certs/local.pub.pem +by the file naming conventions) is used as the default keypair by +tools such as +.Xr signelf 8 . +This keypair is known as the +.Ar local keypair , +should be generated locally on each installation, and should generally +not be exported to other installations. Most user-oriented systems +and all systems that build the base system or packages locally for +their own use should have a local keypair. +.Pp +It is possible for some installations to lack a local keypair, +particularly on infrastructure-type systems that are set up using +standardized images or configuration management systems. Such systems +generally do not build anything locally and rely on pre-built packages +for installation and upgrades. +.Pp +However, it is an illegal configuration for there to be a public key +certificate named +.Pa /etc/trust/root/certs/local.pub.pem +without a corresponding private key. +.Sh DEFAULT +The default trust system configuration consists solely of a local +keypair which is generated during OS installation. Any additional +trusted keys (including third-party keys) are signed by the local +keypair and loaded as intermediate keys rather than being added +directly to the trust root configuration. This is the preferred +configuration, and alternatives should only be used if there is a +specific and compelling reason to do so. +.Sh SEE ALSO +.Xr trust 7 , +.Xr signed-elf 5 , +.Xr signelf 5 , +.Xr openssl 1 +.Sh HISTORY +The trust system first appeared in +.Fx 12.0 . +.Sh AUTHORS +This manual page was written by +.An Eric L. McCorkle Aq Mt emc2@metricspace.net . Index: usr.sbin/Makefile =================================================================== --- usr.sbin/Makefile +++ usr.sbin/Makefile @@ -82,6 +82,7 @@ setfib \ setfmac \ setpmac \ + signelf \ smbmsg \ snapinfo \ spi \ Index: usr.sbin/signelf/Makefile =================================================================== --- /dev/null +++ usr.sbin/signelf/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +.include + +PROG=signelf +MAN=signelf.8 +SRCS=signelf.c sign.c verify.c util.c + +LIBADD=crypto elf + +.include Index: usr.sbin/signelf/sign.c =================================================================== --- /dev/null +++ usr.sbin/signelf/sign.c @@ -0,0 +1,746 @@ +/*- + * Copyright (c) 2018 Eric McCorkle + * All rights reserved. + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "signelf.h" + +#define VPRINTF(...) (verbose ? fprintf(stderr, __VA_ARGS__) : 0) + +static char keypath[MAXPATHLEN + 1]; +static char pubpath[MAXPATHLEN + 1]; +static char **signpaths; +static size_t nsignpaths = 0; +static size_t max_signpaths = 16; +static bool verbose = false; +static bool ephemeral = false; +static char ephemeralpath[MAXPATHLEN + 1]; +static EVP_PKEY *sign_priv; +static X509 *sign_cert; +static size_t sigsize; +static size_t first_resizable; + +static void +add_signpath(char *signpath) +{ + if (max_signpaths <= nsignpaths) { + void *tmp; + + max_signpaths *= 2; + tmp = realloc(signpaths, max_signpaths * sizeof(signpaths[0])); + check_malloc_error(tmp); + signpaths = tmp; + } + + signpaths[nsignpaths] = strdup(signpath); + nsignpaths++; +} + +static void +set_keypath(const char *path) +{ + strncpy(keypath, path, MAXPATHLEN); +} + +static void +set_pubpath(const char *path) +{ + strncpy(pubpath, path, MAXPATHLEN); +} + +static void +set_ephemeralpath(const char *path) +{ + strncpy(ephemeralpath, path, MAXPATHLEN); +} + +static void +load_keys(void) +{ + FILE *f; + EVP_PKEY *priv; + X509 *cert; + + /* Load the private key */ + f = fopen(keypath, "r"); + check_file_error(f, "Error opening private key"); + priv = PEM_read_PrivateKey(f, NULL, NULL, NULL); + check_ssl_error("loading private key"); + fclose(f); + + /* Load the public key */ + f = fopen(pubpath, "r"); + check_file_error(f, "Error opening public key"); + cert = PEM_read_X509(f, NULL, NULL, NULL); + check_ssl_error("loading public key"); + fclose(f); + + if (ephemeral) { + ASN1_TIME *asntime; + X509_NAME *name; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(priv, NULL); + ASN1_INTEGER timestamp; + + VPRINTF("Generating ephemeral key..."); + + /* Generate the ephemeral private key */ + check_ssl_error("generating ephemeral private key"); + EVP_PKEY_keygen_init(ctx); + check_ssl_error("generating ephemeral private key"); + EVP_PKEY_keygen(ctx, &sign_priv); + check_ssl_error("generating ephemeral private key"); + EVP_PKEY_CTX_free(ctx); + + VPRINTF("done\n"); + + /* Create ephemeral public key cert */ + sign_cert = X509_new(); + check_ssl_error("creating ephemeral public key cert"); + X509_set_pubkey(sign_cert, sign_priv); + check_ssl_error("creating ephemeral public key cert"); + X509_set_version(sign_cert, 2); + check_ssl_error("creating ephemeral public key cert"); + ASN1_INTEGER_set(×tamp, time(NULL)); + check_ssl_error("creating ephemeral public key cert"); + asntime = X509_get_notBefore(cert); + check_ssl_error("creating ephemeral public key cert"); + X509_set_notBefore(sign_cert, asntime); + check_ssl_error("creating ephemeral public key cert"); + asntime = X509_get_notAfter(cert); + check_ssl_error("creating ephemeral public key cert"); + X509_set_notAfter(sign_cert, asntime); + check_ssl_error("creating ephemeral public key cert"); + name = X509_get_subject_name(cert); + check_ssl_error("creating ephemeral public key cert"); + X509_set_subject_name(sign_cert, name); + check_ssl_error("creating ephemeral public key cert"); + name = X509_get_issuer_name(cert); + check_ssl_error("creating ephemeral public key cert"); + X509_set_issuer_name(sign_cert, name); + check_ssl_error("creating ephemeral public key cert"); + + /* Sign ephemeral public key */ + X509_sign(sign_cert, priv, EVP_sha256()); + check_ssl_error("signing ephemeral public key"); + } else { + sign_priv = priv; + sign_cert = cert; + } +} + +static void +write_ephemeral(void) +{ + if (ephemeral) { + FILE *f; + + VPRINTF("Writing ephemeral key to %s\n", ephemeralpath); + + f = fopen(ephemeralpath, "w"); + check_file_error(f, "Error writing ephemeral key"); + PEM_write_X509(f, sign_cert); + check_ssl_error("writing out ephemeral key"); + fclose(f); + + } +} + +static CMS_ContentInfo * +make_sig(void *buf, size_t len) +{ + BIO *bio; + CMS_ContentInfo *cms; + + bio = BIO_new_mem_buf(buf, len); + cms = CMS_sign(sign_cert, sign_priv, NULL, bio, + CMS_DETACHED | CMS_BINARY | CMS_NOCERTS | + CMS_NOSMIMECAP | CMS_NOATTR); + + return cms; +} + +static void +compute_sigsize(void) +{ + char buf[0]; + int len; + CMS_ContentInfo *cms = make_sig(buf, sizeof(buf)); + + len = i2d_CMS_ContentInfo(cms, NULL); + check_ssl_error("computing signature size"); + sigsize = len; + CMS_ContentInfo_free(cms); +} + +static int parse_args(const int argc, char* argv[]) +{ + int ch; + int i; + + while ((ch = getopt(argc, argv, "e:k:p:v")) != -1) { + switch (ch) { + default: + usage(); + + return (1); + case 'e': + ephemeral = true; + set_ephemeralpath(optarg); + break; + case 'k': + set_keypath(optarg); + break; + case 'p': + set_pubpath(optarg); + break; + case 'v': + verbose = true; + break; + case '?': + usage(); + break; + } + } + + for(i = optind; i < argc; i++) { + add_signpath(argv[i]); + } + + return (0); +} + +static bool +prefer_type(GElf_Word old, GElf_Word new) +{ + /* Always prefer anything to NULL, and prefer a NOTE section + * over anything. + */ + if (new == SHT_NULL || old == SHT_NOTE) { + return (false); + } + if (old == SHT_NULL || new == SHT_NOTE) { + return (true); + } + + /* Next, prefer static information over anything else. */ + if (new == SHT_SYMTAB || new == SHT_STRTAB) { + return (true); + } + if (old == SHT_SYMTAB || old == SHT_STRTAB) { + return (false); + } + + /* Default: prefer newer over older */ + return (true); +} + +/* Figure out if the given section and everything after can be resized. */ +static void +find_first_resizable(Elf *elf) +{ + Elf_Scn *curr; + GElf_Ehdr ehdr; + size_t phnum; + size_t curridx; + + gelf_getehdr(elf, &ehdr); + check_elf_error(); + phnum = ehdr.e_phnum; + + /* Check the section and all sections that follow it. */ + for (curridx = ehdr.e_shnum - 1; curridx > 0; curridx--) { + GElf_Shdr shdr; + size_t idx; + size_t sbegin; + size_t send; + + curr = elf_getscn(elf, curridx); + check_elf_error(); + gelf_getshdr(curr, &shdr); + check_elf_error(); + sbegin = shdr.sh_offset; + send = sbegin + shdr.sh_size; + + /* Check if the current section's file offsets are + * used in the program header at all. If they are, + * then we can't move them. + */ + for (idx = 0; idx < phnum; idx++) { + GElf_Phdr phdr; + size_t pbegin; + size_t pend; + + gelf_getphdr(elf, idx, &phdr); + check_elf_error(); + pbegin = phdr.p_offset; + pend = pbegin + phdr.p_filesz; + + + if ((pbegin <= sbegin && sbegin < pend) || + (pbegin <= send && send <= pend) || + (sbegin <= pbegin && pbegin < send) || + (sbegin <= pend && pend <= send)) { + first_resizable = curridx + 1; + return; + } + } + } + + first_resizable = 0; +} + +static bool +section_resizable(Elf_Scn *scn) +{ + size_t idx; + + idx = elf_ndxscn(scn); + check_elf_error(); + + return (idx >= first_resizable); +} + +static size_t +align(size_t offset, size_t align) +{ + if (offset == 0) { + return (0); + } else { + size_t mask = align - 1; + + return (((offset - 1) & ~mask) + align); + } +} + +static void +fix_offsets(Elf *elf) +{ + GElf_Ehdr ehdr; + GElf_Shdr shdr; + Elf_Scn *curr; + size_t offset; + size_t shdr_start; + size_t shdr_size; + size_t shdr_end; + size_t aligned; + size_t idx; + + gelf_getehdr(elf, &ehdr); + check_elf_error(); + shdr_start = ehdr.e_shoff; + shdr_size = ehdr.e_shnum * ehdr.e_shentsize; + idx = first_resizable; + + /* This can happen if the very last section is not resizable, + * in which case we can't do anything at all here. + */ + if (idx >= ehdr.e_shnum) { + return; + } + + curr = elf_getscn(elf, idx); + check_elf_error(); + shdr_end = shdr_start + shdr_size; + gelf_getshdr(curr, &shdr); + check_elf_error(); + offset = shdr.sh_offset; + + /* Fix up the offsets of the section and all sections that follow it */ + while (curr != NULL) { + /* Grab all the data elements, so that they get + * written back to the file when we update. + * Otherwise, they'll get corrupted. + */ + Elf_Data *data; + + for(data = elf_getdata(curr, NULL); data != NULL; + data = elf_getdata(curr, data)); + + /* The section header might be between two offsets */ + if ((offset <= shdr_start && shdr_start <= shdr.sh_offset) || + (offset <= shdr_end && shdr_end <= shdr.sh_offset) || + (shdr_start <= offset && offset <= shdr_end) || + (shdr_start <= shdr.sh_offset && + shdr.sh_offset <= shdr_end)) { + aligned = align(offset, 8); + ehdr.e_shoff = aligned; + gelf_update_ehdr(elf, &ehdr); + check_elf_error(); + offset = ehdr.e_shoff + shdr_size; + } + + aligned = align(offset, shdr.sh_addralign); + shdr.sh_offset = aligned; + + offset = shdr.sh_offset + shdr.sh_size; + gelf_update_shdr(curr, &shdr); + check_elf_error(); + curr = elf_nextscn(elf, curr); + check_elf_error(); + + if (curr != NULL) { + gelf_getshdr(curr, &shdr); + check_elf_error(); + } + } + + /* The section header might have been at the very end */ + if ((shdr_start <= offset && offset <= shdr_end)) { + aligned = align(offset, 8); + ehdr.e_shoff = aligned; + gelf_update_ehdr(elf, &ehdr); + check_elf_error(); + offset += shdr_size; + } +} + +static size_t +strtab_insert_sign(Elf_Scn *scn) +{ + GElf_Shdr shdr; + Elf_Data *data; + char *strtab; + size_t idx; + + data = elf_getdata(scn, NULL); + check_elf_error(); + strtab = data->d_buf; + idx = data->d_size; + data->d_buf = realloc(data->d_buf, data->d_size + sizeof (SIGN_NAME)); + check_malloc_error(data->d_buf); + strncpy((char *)data->d_buf + data->d_size, SIGN_NAME, + sizeof (SIGN_NAME)); + data->d_size += sizeof (SIGN_NAME); + + /* Update the section header */ + gelf_getshdr(scn, &shdr); + check_elf_error(); + shdr.sh_size += sizeof (SIGN_NAME); + gelf_update_shdr(scn, &shdr); + check_elf_error(); + + return (idx); +} + +static size_t +strtab_find_sign(Elf_Scn *scn) +{ + Elf_Data *data; + char *strtab; + size_t offset; + char *str; + + data = elf_getdata(scn, NULL); + check_elf_error(); + strtab = data->d_buf; + + /* ELF conventions: offset 0 is the empty string, so we start at 1 */ + for(offset = 1; offset < data->d_size; + offset += strlen(strtab + offset) + 1) { + str = strstr(strtab + offset, SIGN_NAME); + + if (str != NULL) { + return (str - strtab); + } + } + + return (0); +} + +/* Try to find ".sign" in the strtab, or insert it if it's not there. */ +static size_t +get_sign_idx(Elf_Scn *strtab) +{ + size_t idx; + + if ((idx = strtab_find_sign(strtab)) == 0) { + /* If we can't resize the strtab, give up */ + if (!section_resizable(strtab)) { + fprintf(stderr, "Cannot resize strtab"); + exit(1); + } + + /* Insert ".sign" into the strtab and fixup all the + * subsequent offsets. + */ + idx = strtab_insert_sign(strtab); + } + + return (idx); +} + +/* Set the section name. To do this, we need to figure out what + * strtab to use. + */ +static void +set_sign_name(Elf *elf, Elf_Scn *newscn) +{ + GElf_Ehdr ehdr; + GElf_Shdr shdr; + Elf_Scn *curr = NULL; + GElf_Word type = SHT_NULL; + size_t sym, idx = 0; + Elf_Scn *strtab; + + /* First, figure out which strtab section to use. */ + gelf_getehdr(elf, &ehdr); + check_elf_error(); + + if (ehdr.e_shstrndx == SHN_UNDEF) { + /* This shouldn't happen, but check for it anyway. */ + fprintf(stderr, "File contains no section header names\n"); + exit(1); + } else if (ehdr.e_shstrndx != SHN_XINDEX) { + /* We're not using extended section numbering */ + idx = ehdr.e_shstrndx; + } else { + /* Scan through the sections, looking for the best + * strtab to use. + */ + for(curr = elf_nextscn(elf, curr); curr != NULL; + curr = elf_nextscn(elf, curr)) { + GElf_Shdr currshdr; + + check_elf_error(); + gelf_getshdr(curr, &currshdr); + check_elf_error(); + + /* If this section has a better type, update the + * preferred index + */ + if(prefer_type(currshdr.sh_type, type)) { + type = currshdr.sh_type; + idx = currshdr.sh_link; + } + } + + /* Set the link */ + gelf_getshdr(newscn, &shdr); + check_elf_error(); + shdr.sh_link = idx; + gelf_update_shdr(newscn, &shdr); + check_elf_error(); + } + + + /* Now find or create the index of the ".sign" string. */ + strtab = elf_getscn(elf, idx); + check_elf_error(); + sym = get_sign_idx(strtab); + + /* Set the name */ + gelf_getshdr(newscn, &shdr); + check_elf_error(); + shdr.sh_name = sym; + gelf_update_shdr(newscn, &shdr); + check_elf_error(); +} + +static void +sign_elf(Elf *elf) +{ + size_t idx; + Elf_Scn *scn; + Elf_Data *data; + unsigned char *buf; + size_t filesize; + void *filedata; + CMS_ContentInfo *cms; + size_t siglen; + + find_first_resizable(elf); + idx = find_sig(elf); + elf_flagelf(elf, ELF_C_SET, ELF_F_LAYOUT); + + if (idx != 0) { + /* Resize the section and fixup offsets */ + GElf_Shdr shdr; + + scn = elf_getscn(elf, idx); + check_elf_error(); + data = elf_getdata(scn, NULL); + check_elf_error(); + + if (data->d_size != sigsize) { + if (!section_resizable(scn)) { + fprintf(stderr, + "Cannot resize signature section\n"); + exit(1); + } + + /* Set the section size and fix up all the + * following sections + */ + gelf_getshdr(scn, &shdr); + check_elf_error(); + shdr.sh_size = sigsize; + gelf_update_shdr(scn, &shdr); + check_elf_error(); + fix_offsets(elf); + + data->d_size = sigsize; + data->d_buf = realloc(data->d_buf, sigsize); + check_malloc_error(data->d_buf); + } + } else { + /* Create the .sign section */ + GElf_Shdr shdr; + GElf_Ehdr ehdr; + + /* Create a new section */ + scn = elf_newscn(elf); + idx = elf_ndxscn(scn); + check_elf_error(); + gelf_getehdr(elf, &ehdr); + check_elf_error(); + ehdr.e_shnum += 1; + gelf_update_ehdr(elf, &ehdr); + check_elf_error(); + + /* Set up the data */ + data = elf_newdata(scn); + check_elf_error(); + data->d_align = 1; + data->d_off = 0; + data->d_size = sigsize; + data->d_buf = malloc(sigsize); + check_malloc_error(data->d_buf); + + /* Set the section name and type */ + gelf_getshdr(scn, &shdr); + check_elf_error(); + shdr.sh_type = SHT_PROGBITS; + shdr.sh_size = sigsize; + shdr.sh_addralign = 1; + gelf_update_shdr(scn, &shdr); + check_elf_error(); + + set_sign_name(elf, scn); + fix_offsets(elf); + } + + /* Set the signature section to all zeros */ + memset(data->d_buf, 0, sigsize); + data->d_size = sigsize; + + /* Update the file and get a pointer to the raw data */ + elf_update(elf, ELF_C_WRITE); + check_elf_error(); + filedata = elf_rawfile(elf, &filesize); + check_elf_error(); + + /* The section and data pointers aren't good after elf_update, + * so refresh them. + */ + elf_nextscn(elf, NULL); + scn = elf_getscn(elf, idx); + check_elf_error(); + data = elf_getdata(scn, NULL); + check_elf_error(); + + /* Actually compute the signature */ + cms = make_sig(filedata, filesize); + + siglen = i2d_CMS_ContentInfo(cms, NULL); + + if(siglen != sigsize) { + fprintf(stderr, "Signature size %zu is not expected %zu\n", + siglen, sigsize); + abort(); + } + + /* Write back all the data. */ + buf = data->d_buf; + siglen = i2d_CMS_ContentInfo(cms, &buf); + elf_update(elf, ELF_C_WRITE); +} + +int +sign_main(int argc, char *argv[]) +{ + int err; + unsigned int i; + + set_keypath(DEFAULT_KEYPATH); + signpaths = malloc(max_signpaths * sizeof(signpaths[0])); + err = parse_args(argc, argv); + + VPRINTF("Loading key from %s and cert from %s, signing", + keypath, pubpath); + + for(i = 0; i < nsignpaths; i++) { + VPRINTF(" %s", signpaths[i]); + } + + VPRINTF(" %s\n", ephemeral ? "with ephemeral key" : "directly\n"); + + load_keys(); + write_ephemeral(); + compute_sigsize(); + + if (elf_version(EV_CURRENT) == EV_NONE) { + fprintf(stderr, "ELF library cannot handle version %u\n", + EV_CURRENT); + } + + for(i = 0; i < nsignpaths; i++) { + int fd; + Elf *elf; + + VPRINTF("Signing %s\n", signpaths[i]); + fd = open(signpaths[i], O_RDWR); + check_fd_error(fd); + elf = elf_begin(fd, ELF_C_RDWR, NULL); + check_elf_error(); + sign_elf(elf); + elf_end(elf); + } + + return (err); +} Index: usr.sbin/signelf/signelf.h =================================================================== --- /dev/null +++ usr.sbin/signelf/signelf.h @@ -0,0 +1,59 @@ +/*- + * Copyright (c) 2018 Eric McCorkle + * All rights reserved. + * + * 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 +__FBSDID("$FreeBSD$"); + +#ifndef _SIGNELF_H_ +#define _SIGNELF_H_ + +#include +#include + +#include + +#define DEFAULT_TRUSTDIR "/etc/trust/root" +#define DEFAULT_PRIVDIR DEFAULT_TRUSTDIR "priv/" +#define DEFAULT_CERTDIR DEFAULT_TRUSTDIR "certs/" +#define DEFAULT_KEYPATH DEFAULT_PRIVDIR "local.pem" +#define DEFAULT_PUBKEYPATH DEFAULT_CERTDIR "local.pub.pem" + +#define SIGN_NAME ".sign" + +extern void usage(void); + +extern void check_elf_error(void); +extern void check_fd_error(int fd); +extern void check_file_error(const FILE *ptr, const char* str); +extern void check_malloc_error(const void *ptr); +extern void check_ssl_error(const char *op); + +extern size_t find_sig(Elf *elf); + +extern int sign_main(int argc, char *argv[]); +extern int verify_main(int argc, char *argv[]); +extern int unsign_main(int argc, char *argv[]); +#endif Index: usr.sbin/signelf/signelf.8 =================================================================== --- /dev/null +++ usr.sbin/signelf/signelf.8 @@ -0,0 +1,267 @@ +.\" Copyright (c) 2017 Eric McCorkle +.\" All rights reserved. +.\" +.\" 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$ +.\" +.Dd April 24, 2017 +.Dt SIGNELF 8 +.Os +.Sh NAME +.Nm signelf +.Nd "sign or verify ELF binaries" +.Sh SYNOPSIS +.Nm +.Cm sign +.Op Fl e Ar ephemeral cert +.Op Fl k Ar private key +.Op Fl p Ar public key cert +.Op Fl v +.Ar files +.Nm +.Cm verify +.Op Fl p Ar public key cert +.Ar files +.Sh DESCRIPTION +The +.Nm +utility signs binary executables and libraries in the Executable +Linkable Format (ELF) and verifies signatures on signed binaries. +Signed ELF binaries carry an additional section containing a +cryptograpic signature, which can be used to verify the contents of +the executable. For details on the format of signed ELF binaries, see the +.Xr signed-elf 5 +man page. +.Ss Ephemeral Keys +The +.Nm +utility can generate an ephemeral key-pair for signing a batch of ELF +binaries. After signing is complete, the ephemeral private key will +be discarded (at which point it will be impossible to sign any more +binaries with so that the ephemeral public key can verify them), while +the public key will be written out as a PEM-encoded X509 certificate +and signed with the user-supplied key-pair. +.Sh SUBCOMMANDS +The +.Nm +utility provides two subcommands. The following is a description of +their functioning: +.Pp +.Bl -tag -width 2n +.It Xo +.Nm +.Cm sign +.Op Fl e Ar ecert +.Op Fl k Ar pkey +.Op Fl p Ar pcert +.Op Fl v +.Ar files +.Xc +.Pp +Sign each ELF binary in +.Ar files . +Any existing signatures in +.Ar files +will be overwritten. +.Bl -tag -width indent +.It Fl e +Generate an ephemeral key-pair which will be used to sign the +.Ar files . +Once signing is complete, the ephemeral private key will be destroyed, +while the public key will be output as a PEM-encoded X509 certificate +to +.Ar ecert +and will be signed with the user-supplied key-pair. +.It Fl k +Use the private key in +.Ar pkey +(which must be a PEM-encoded private key or PKCS#8 store) as the +private key for signing binaries or the ephemeral key. If this option +is not provided, the default private key +.Pa /etc/trust/root/priv/local.pem +will be used. +.It Fl p +Use the PEM-encoded X509 certificate +.Ar pcert +as the public key for signing binaries or the ephemeral key. If this +option is not provided, the default public key +.Pa /etc/trust/root/certs/local.pub.pem +will be used. +.It Fl v +Generate verbose output. +.El +.It Xo +.Nm +.Cm verify +.Op Fl p Ar pcert +.Ar files +.Xc +.Pp +Verify signatures on each ELF binary in +.Ar files . +(Note that by default, no output is generated if verification succeeds.) +.Bl -tag -width indent +.It Fl p +Use the PEM-encoded X509 certificate in +.Ar pcert +as the public key for verifying the binaries. If this option is not +provided, the default public key +.Pa /etc/trust/priv/local.pem +will be used. +.It Fl v +Generate verbose output. +.El +.El +.Sh FILES +The default location for signing keys and certificates is +.Pa /etc/trust +with private keys being stored in +.Pa /etc/trust/priv +and corresponding public key certificates in +.Pa /etc/trust/certs +(this allows differing permissions on the two directories), and with +trust root keys and certificates being stored in a similar fashion +in +.Pa /etc/trust/root/priv +and +.Pa /etc/trust/root/certs. +The default private and public keys are located at +.Pa /etc/trust/priv/local.pem +and +.Pa /etc/trust/certs/local.pub.pem +respectively. See the +.Xr trust-config 5 +man page for more details. +.Sh WARNINGS +The +.Nm +utility relies on the predictable behaviors of +.Xr binutils 7 , +.Xr ld 1 , +and other system utilities used to produce ELF files in the normal +course of compiling and linking programs. The +.Nm +utility should operate reliably on executables and shared objects +produced in such a fashion. However, the ELF format is quite +general, and +.Nm +cannot account for all possible uses of the format. It is +not recommended to attempt to use +.Nm +on any ELF file not produced by the standard means of compiling and +linking programs. +.Pp +Also, once an ELF binary has been signed, +.Sy any +modification of the file- however slight -will cause signature +verification to fail (as intended). Furthermore, due to the nature of +the ELF format, signatures will likely be retained if a signed binary +is used as input to a tool such as +.Xr objcopy 1 +or similar utilities, which will result in an ELF binary containing a +bad signature. +.Pp +It is therefore recommended to only use +.Nm +on executables and shared objects after at the end of all compilation steps. +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +The following are examples of typical usage of the +.Nm +utility: +.Ss Signing +.Dl "$ signelf sign myexe" +.Pp +Sign the executable +.Ar myexe +directly using the default key and cert (located at +.Pa /etc/trust/priv/local.pem +and +.Pa /etc/trust/certs/local.pub.pem +respectively). +.Pp +.Dl "$ signelf sign -e ephemeral myexe" +.Pp +Generate an ephemeral signing key and sign the executable +.Ar myexe +using it. The ephemeral key will be signed using the default key and +cert (located at +.Pa /etc/trust/priv/local.pem +and +.Pa /etc/trust/certs/local.pub.pem +respectively) and saved as +.Pa ephemeral +in PEM format. Note that the private key is +.Sy not +retained, meaning the +certificate is only good for verifying signatures. +.Ss Verification +.Dl "$ signelf verify myexe" +.Pp +Verify the signature in the executable +.Ar myexe +using the default cert (located at +.Pa /etc/trust/certs/local.pub.pem +). +.Pp +.Dl "$ signelf verify -p cert.pem myexe" +.Pp +Verify the signature in the executable +.Ar myexe +using the certificate located at +.Pa cert.pem +(this can be used to verify signatures using an ephemeral key's +certificate). +.Ss Removing Signatures +Lastly, signatures can be deleted from a file using the +.Xr objcopy 3 +utility: +.Pp +.Dl "$ objcopy -R .sign myexe" +.Ss Using Binutils and OpenSSL +The basic functioning of +.Nm +can be replicated using the +.Xr openssl 1 +and +.Xr objcopy 1 +utilities. See the +.Qq UTILITIES +section of the +.Xr signed-elf 5 +man page for details. +.Sh SEE ALSO +.Xr elf 5 , +.Xr signed-elf 5 , +.Xr trust-config 5 +.Xr openssl 1 , +.Xr cms 1 +.Sh HISTORY +The +.Nm +utility first appeared in +.Fx 12.0 . +.Sh AUTHORS +This manual page was written by +.An Eric L. McCorkle Aq Mt emc2@metricspace.net . Index: usr.sbin/signelf/signelf.c =================================================================== --- /dev/null +++ usr.sbin/signelf/signelf.c @@ -0,0 +1,91 @@ +/*- + * Copyright (c) 2018 Eric McCorkle + * All rights reserved. + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include "signelf.h" + +#include +#include + +static const char* cmd; + +static const char * const usage_detail = + " Commands:\n" + "\n" + " sign: add signatures to one or more executables or shared objects\n" + "\n" + " Options:\n" + "\n" + " -e Sign with ephemeral keys, save public key to file\n" + " -k Path to private (signing) key\n" + " -p Path to public (verification) key certificate\n" + " -v Verbose mode\n" + "\n" + " verify: check signatures on one or more executables or shared objects\n" + "\n" + " Options:\n" + "\n" + " -p Path to public (verification) key certificate\n" + " -v Verbose mode\n" + "\n"; + +void +usage(void) +{ + fprintf(stderr, "Usage: %s [option]* [filename]+\n\n%s\n", + cmd, usage_detail); +} + +int +main(const int argc, char* argv[]) +{ + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); + + cmd = argv[0]; + + if (argc > 1) { + if (!strcmp(argv[1], "sign")) { + return (sign_main(argc - 1, argv + 1)); + } else if (!strcmp(argv[1], "verify")) { + return (verify_main(argc - 1, argv + 1)); + } else { + fprintf(stderr, "Invalid command %s\n", argv[1]); + } + } else { + fprintf(stderr, "Need a command\n"); + } + + usage(); + + return (1); +} Index: usr.sbin/signelf/util.c =================================================================== --- /dev/null +++ usr.sbin/signelf/util.c @@ -0,0 +1,142 @@ +/*- + * Copyright (c) 2018 Eric McCorkle + * All rights reserved. + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include +#include + +#include "signelf.h" + +#include + +void +check_elf_error(void) +{ + int err; + + if ((err = elf_errno()) != 0) { + fprintf(stderr, "Error handling ELF file: %s\n", + elf_errmsg(err)); + exit(errno); + } +} + +void +check_fd_error(int fd) { + if (fd < 0) { + perror("Error opening file"); + exit(errno); + } +} + +void +check_file_error(const FILE *ptr, const char* str) { + if (ptr == NULL) { + perror(str); + exit(errno); + } +} + +void +check_malloc_error(const void *ptr) { + if (ptr == NULL) { + perror("Could not allocate memory"); + abort(); + } +} + +void +check_ssl_error(const char *op) { + unsigned long err = ERR_get_error(); + + if (err != 0) { + fprintf(stderr, "Error in %s (%s) while %s: %s\n", + ERR_lib_error_string(err), ERR_func_error_string(err), op, + ERR_reason_error_string(err)); + exit(err); + } +} + +size_t +find_sig(Elf *elf) +{ + Elf_Scn *curr = NULL; + GElf_Ehdr ehdr; + bool link_strtab = false; + size_t strtabidx; + size_t out = 0; + + /* Try to get the strtab index */ + gelf_getehdr(elf, &ehdr); + check_elf_error(); + + /* See elf(5) man page for meaning of this. */ + if (ehdr.e_shstrndx == SHN_UNDEF) { + fprintf(stderr, "File contains no section header names\n"); + exit(1); + } else if (ehdr.e_shstrndx == SHN_XINDEX) { + link_strtab = true; + } else { + strtabidx = ehdr.e_shstrndx; + } + + for(curr = elf_nextscn(elf, curr); curr != NULL; + curr = elf_nextscn(elf, curr)) { + GElf_Shdr shdr; + char *str; + + check_elf_error(); + gelf_getshdr(curr, &shdr); + check_elf_error(); + + /* See elf(5) man page for meaning of this. */ + if (link_strtab) { + strtabidx = shdr.sh_link; + } + + str = elf_strptr(elf, strtabidx, shdr.sh_name); + check_elf_error(); + + if(!strcmp(str, ".sign")) { + break; + } + } + + if (curr != NULL) { + out = elf_ndxscn(curr); + check_elf_error(); + } + + return (out); +} Index: usr.sbin/signelf/verify.c =================================================================== --- /dev/null +++ usr.sbin/signelf/verify.c @@ -0,0 +1,252 @@ +/*- + * Copyright (c) 2018 Eric McCorkle + * All rights reserved. + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "signelf.h" + +#define VPRINTF(...) (verbose ? fprintf(stderr, __VA_ARGS__) : 0) + +static char pubpath[MAXPATHLEN + 1]; +static char **signpaths; +static size_t nsignpaths = 0; +static size_t max_signpaths = 16; +static bool verbose = false; +static STACK_OF(X509) *verifycerts; + +static void check_malloc(const void *ptr) { + if (ptr == NULL) { + perror("Could not allocate memory"); + abort(); + } +} + +static void add_signpath(char *signpath) +{ + if (max_signpaths <= nsignpaths) { + void *tmp; + + max_signpaths *= 2; + tmp = realloc(signpaths, max_signpaths * sizeof(signpaths[0])); + check_malloc(tmp); + signpaths = tmp; + } + + signpaths[nsignpaths] = strdup(signpath); + nsignpaths++; +} + +static void +load_cert(void) +{ + FILE *f; + X509 *cert; + + /* Load the public key */ + f = fopen(pubpath, "r"); + check_file_error(f, "Error opening public key"); + cert = PEM_read_X509(f, NULL, NULL, NULL); + check_ssl_error("loading public key"); + fclose(f); + + verifycerts = sk_X509_new_null(); + check_ssl_error("setting up public key"); + sk_X509_push(verifycerts, cert); + check_ssl_error("setting up public key"); +} + +static void set_pubpath(const char *path) +{ + strncpy(pubpath, path, MAXPATHLEN); +} + +static int parse_args(const int argc, char* argv[]) +{ + int ch; + int i; + + while ((ch = getopt(argc, argv, "p:v")) != -1) { + switch (ch) { + default: + usage(); + + return (1); + case 'p': + set_pubpath(optarg); + break; + case 'v': + verbose = true; + break; + case '?': + usage(); + break; + } + } + + if (!strcmp(pubpath, "")) { + set_pubpath(DEFAULT_PUBKEYPATH); + } + + for(i = optind; i < argc; i++) { + add_signpath(argv[i]); + } + + return (0); +} + +static bool +verify_elf(Elf *elf, const char path[]) +{ + size_t idx; + Elf_Scn *scn; + Elf_Data *data; + CMS_ContentInfo *cms; + const unsigned char *buf; + void *ptr; + size_t filesize; + int out; + unsigned long err; + BIO *bio; + + /* Find the signature section */ + idx = find_sig(elf); + + if (idx == 0) { + fprintf(stderr, "No signature in %s\n", path); + return (false); + } + + scn = elf_getscn(elf, idx); + check_elf_error(); + data = elf_rawdata(scn, NULL); + check_elf_error(); + + /* Load the signature */ + buf = data->d_buf; + + cms = NULL; + d2i_CMS_ContentInfo(&cms, &buf, data->d_size); + check_ssl_error("parsing signature"); + + /* Prepare the file data for verification */ + memset(data->d_buf, 0, data->d_size); + ptr = elf_rawfile(elf, &filesize); + bio = BIO_new_mem_buf(ptr, filesize); + check_ssl_error("preparing data"); + + /* Perform verification */ + out = CMS_verify(cms, verifycerts, NULL, bio, NULL, CMS_NOINTERN); + err = ERR_get_error(); + + if (err != 0) { + fprintf(stderr, "Signature verification for %s failed: %s\n", + path, ERR_reason_error_string(err)); + exit(err); + } else if (out) { + VPRINTF("Verification successful for %s\n", path); + } else { + fprintf(stderr, "Verification failed for %s\n", path); + } + + return (out); +} + +int +verify_main(int argc, char *argv[]) +{ + int err; + unsigned int i; + bool ok = true; + + signpaths = malloc(max_signpaths * sizeof(signpaths[0])); + err = parse_args(argc, argv); + + VPRINTF("Loading verification key from %s, verifying", pubpath); + + for(i = 0; i < nsignpaths; i++) { + VPRINTF(" %s", signpaths[i]); + } + + if (elf_version(EV_CURRENT) == EV_NONE) { + fprintf(stderr, "ELF library cannot handle version %u\n", + EV_CURRENT); + } + + load_cert(); + + if (elf_version(EV_CURRENT) == EV_NONE) { + fprintf(stderr, "ELF library cannot handle version %u\n", + EV_CURRENT); + } + + for(i = 0; i < nsignpaths; i++) { + int fd; + Elf *elf; + void *ptr; + struct stat st; + + VPRINTF("Signing %s\n", signpaths[i]); + fd = open(signpaths[i], O_RDONLY); + check_fd_error(fd); + + if (fstat(fd, &st) != 0) { + perror("Error getting file size"); + exit(errno); + } + + ptr = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE, fd, 0); + + if (ptr == MAP_FAILED) { + perror("Error mapping file contents"); + exit(errno); + } + + elf = elf_memory(ptr, st.st_size); + check_elf_error(); + ok &= verify_elf(elf, signpaths[i]); + elf_end(elf); + } + + return (ok ? 0 : 1); +}