diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab8a13c --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +*.swp +*~ + +# Generated by autotools +.deps +aclocal.m4 +autom4te.cache +config.guess +config.log +config.status +config.sub +configure +depcomp +install-sh +Makefile.in +Makefile +!tests/data/Makefile +missing +compile +libtool +ltmain.sh + +# Compiled executables +*.o +*.a +src/evmctl +tests/openclose +config.h +config.h.in +stamp-h1 +*.spec + +# But don't ignore the symlinks with the same names in this directory +!tests/valgrind/* + +# cscope/tags +tags +TAGS +cscope.* +ncscope.* + +# Generated documentation +*.8 +*.5 +manpage.links +manpage.refs + +# quilt's files +patches +series + +# test output + diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..471b05a --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Dmitry Kasatkin + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..e69de29 diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..169e054 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,5 @@ +2011-08-24 Dmitry Kasatkin + + version 0.1 + * Initial public version. + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..7d1c323 --- /dev/null +++ b/INSTALL @@ -0,0 +1,365 @@ +Installation Instructions +************************* + +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, +2006, 2007, 2008, 2009 Free Software Foundation, Inc. + + Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without warranty of any kind. + +Basic Installation +================== + + Briefly, the shell commands `./configure; make; make install' should +configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. Some packages provide this +`INSTALL' file but do not implement all of the features documented +below. The lack of an optional feature in a given package is not +necessarily a bug. More recommendations for GNU packages can be found +in *note Makefile Conventions: (standards)Makefile Conventions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + + The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package, generally using the just-built uninstalled binaries. + + 4. Type `make install' to install the programs and any data files and + documentation. When installing into a prefix owned by root, it is + recommended that the package be configured and built as a regular + user, and only the `make install' phase executed with root + privileges. + + 5. Optionally, type `make installcheck' to repeat any self-tests, but + this time using the binaries in their final installed location. + This target does not install anything. Running this target as a + regular user, particularly if the prior `make install' required + root privileges, verifies that the installation completed + correctly. + + 6. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 7. Often, you can also type `make uninstall' to remove the installed + files again. In practice, not all packages have tested that + uninstallation works correctly, even though it is required by the + GNU Coding Standards. + + 8. Some packages, particularly those that use Automake, provide `make + distcheck', which can by used by developers to test that all other + targets like `make install' and `make uninstall' work correctly. + This target is generally not run by end users. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. This +is known as a "VPATH" build. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple `-arch' options to the +compiler but only a single `-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the `lipo' tool if you have problems. + +Installation Names +================== + + By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX', where PREFIX must be an +absolute file name. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. In general, the +default for these options is expressed in terms of `${prefix}', so that +specifying just `--prefix' will affect all of the other directory +specifications that were not explicitly provided. + + The most portable way to affect installation locations is to pass the +correct locations to `configure'; however, many packages provide one or +both of the following shortcuts of passing variable assignments to the +`make install' command line to change installation locations without +having to reconfigure or recompile. + + The first method involves providing an override variable for each +affected directory. For example, `make install +prefix=/alternate/directory' will choose an alternate location for all +directory configuration variables that were expressed in terms of +`${prefix}'. Any directories that were specified during `configure', +but not in terms of `${prefix}', must each be overridden at install +time for the entire installation to be relocated. The approach of +makefile variable overrides for each directory variable is required by +the GNU Coding Standards, and ideally causes no recompilation. +However, some platforms have known limitations with the semantics of +shared libraries that end up requiring recompilation when using this +method, particularly noticeable in packages that use GNU Libtool. + + The second method involves providing the `DESTDIR' variable. For +example, `make install DESTDIR=/alternate/directory' will prepend +`/alternate/directory' before all installation names. The approach of +`DESTDIR' overrides is not required by the GNU Coding Standards, and +does not work on platforms that have drive letters. On the other hand, +it does better at avoiding recompilation issues, and works well even +when some directory options were not specified in terms of `${prefix}' +at `configure' time. + +Optional Features +================= + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + + Some packages offer the ability to configure how verbose the +execution of `make' will be. For these packages, running `./configure +--enable-silent-rules' sets the default to minimal output, which can be +overridden with `make V=1'; while running `./configure +--disable-silent-rules' sets the default to verbose, which can be +overridden with `make V=0'. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU +CC is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its `' header file. The option `-nodtk' can be used as +a workaround. If GNU CC is not installed, it is therefore recommended +to try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + + On Solaris, don't put `/usr/ucb' early in your `PATH'. This +directory contains several dysfunctional programs; working variants of +these programs are available in `/usr/bin'. So, if you need `/usr/ucb' +in your `PATH', put it _after_ `/usr/bin'. + + On Haiku, software installed for all users goes in `/boot/common', +not `/usr/local'. It is recommended to use the following options: + + ./configure --prefix=/boot/common + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf bug. Until the bug is fixed you can use this workaround: + + CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of all of the options to `configure', and exit. + +`--help=short' +`--help=recursive' + Print a summary of the options unique to this package's + `configure', and exit. The `short' variant lists options used + only in the top level, while the `recursive' variant lists options + also present in any nested packages. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--prefix=DIR' + Use DIR as the installation prefix. *note Installation Names:: + for more details, including other options available for fine-tuning + the installation locations. + +`--no-create' +`-n' + Run the configure checks, but stop before creating any output + files. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..3cb36a9 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,6 @@ +SUBDIRS = src tests + +#EXTRA_DIST = LEGAL acinclude.m4 include + +ACLOCAL_AMFLAGS = -I m4 + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README index e69de29..a8faa7f 100644 --- a/README +++ b/README @@ -0,0 +1,40 @@ + +1. Generate private key + +openssl genrsa -out privkey_evm.pem 1024 + +2. Generate public key + +openssl rsa -pubout -in privkey_evm.pem -out pubkey_evm.pem + +3. Copy public (+private if to sign on device) key to the device/qemu /etc/keys + +scp pubkey_evm.pem mad:/etc/keys + +4. Load keys and enable EVM + +evm_enable.sh + +This should be done at early phase, before mounting root filesystem. + +5. Sign EVM and use hash value for IMA - common case + +evmctl sign --imahash test.txt + +6. Sign IMA and EVM - for immutable files and modules + +evmctl sign --imasig test.txt + +7. Sign whole filesystem + +evm_sign_all.sh +or +find / \( -fstype rootfs -o -fstype ext3 -o -fstype ext4 \) ! -path "/lib/modules/*" -type f -uid 0 -exec evmctl sign --imahash '{}' \; +find /lib/modules ! -name "*.ko" -type f -uid 0 -exec evmctl sign --imahash '{}' \; +# security.ima needs to have signature for modules +find /lib/modules -name "*.ko" -type f -uid 0 -exec evmctl sign --imasig '{}' \; + +8. Label filesystem in fix mode... + +ima_fix_dir.sh + diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 0000000..dd430d4 --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,14 @@ + +AC_DEFUN([PKG_ARG_ENABLE], + [ + AC_MSG_CHECKING(whether to enable $1) + AC_ARG_ENABLE([$1], AC_HELP_STRING([--enable-$1], [enable $1 (default is $2)]), + [pkg_cv_enable_$1=$enableval], + [AC_CACHE_VAL([pkg_cv_enable_$1], [pkg_cv_enable_$1=$2])]) + if test $pkg_cv_enable_$1 = yes; then + AC_DEFINE([$3],, [$4]) + fi + AC_MSG_RESULT([$pkg_cv_enable_$1]) + AM_CONDITIONAL($3, test $pkg_cv_enable_$1 = yes) +]) + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..d01bb43 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,16 @@ +#! /bin/sh + +set -e + +# new way +# strange, but need this for Makefile.am, because it has -I m4 +test -d m4 || mkdir m4 +autoreconf -f -i + +# old way +#libtoolize --automake --copy --force +#aclocal +#autoconf --force +#autoheader --force +#automake --add-missing --copy --force-missing --gnu + diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..51ba559 --- /dev/null +++ b/configure.ac @@ -0,0 +1,60 @@ +# autoconf script + +AC_PREREQ([2.65]) +AC_INIT(evm-utils, 0.1, dmitry.kasatkin@intel.com) +AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) + +AC_CANONICAL_HOST + +# Checks for programs. +AC_PROG_CC +AM_PROG_CC_C_O +#AC_PROG_CXX +#AC_PROG_CPP +AC_PROG_INSTALL +AC_PROG_LIBTOOL +#AC_PROG_LN_S +LT_INIT + +# FIXME: Replace `main' with a function in `-lpthread': +#AC_CHECK_LIB([pthread], [main]) + +# Checks for header files. +AC_HEADER_STDC + +PKG_CHECK_MODULES(OPENSSL, [ openssl >= 0.9.8 ]) +AC_SUBST(OPENSSL_CFLAGS) +AC_SUBST(OPENSSL_LIBS) +AC_CHECK_HEADER(unistd.h) +AC_CHECK_HEADERS(openssl/conf.h) + +#debug support - yes for a while +PKG_ARG_ENABLE(debug, "yes", DEBUG, [Enable Debug support]) +if test $pkg_cv_enable_debug = yes; then + CFLAGS="-g -O1 -Wall -Wstrict-prototypes -pipe" +else + CFLAGS="$CFLAGS -Wall -Wstrict-prototypes -pipe -fomit-frame-pointer" +fi + +# for gcov +#CFLAGS="$CFLAGS -Wall -fprofile-arcs -ftest-coverage" +#CXXFLAGS="$CXXFLAGS -Wall -fprofile-arcs -ftest-coverage" +#LDFLAGS="$LDFLAGS -fprofile-arcs" +#DISTCLEANFILES="*.gcno *.gcda" + +AC_CONFIG_FILES([Makefile + src/Makefile + tests/Makefile + evm-utils.spec + ]) +AC_OUTPUT + +# Give some feedback +echo +echo +echo "Configuration:" +echo " debug: $pkg_cv_enable_debug" +echo + diff --git a/evm-utils.spec.in b/evm-utils.spec.in new file mode 100644 index 0000000..343ba9f --- /dev/null +++ b/evm-utils.spec.in @@ -0,0 +1,53 @@ +Name: @PACKAGE_NAME@ +Version: @PACKAGE_VERSION@ +Release: 1%{?dist} +Summary: evm-utils - IMA/EVM support utilities +Group: System/Libraries +License: LGPLv2 +#URL: +Source0: %{name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root + +BuildRequires: autoconf +BuildRequires: automake +BuildRequires: openssl-devel +BuildRequires: libattr-devel +BuildRequires: readline-devel +BuildRequires: keyutils-libs-devel + +%description +This library provides EVM support utilities. + +%prep +%setup -q + +%build +./autogen.sh +%configure --prefix=/usr +make + +%install +rm -rf %{buildroot} +make DESTDIR=%{buildroot} install + +%clean +rm -rf %{buildroot} + +%post +/sbin/ldconfig +exit 0 + +%preun -p /sbin/ldconfig + +%postun +/sbin/ldconfig + +%files +%defattr(-,root,root,-) +%{_bindir}/* +%{_libdir}/* + +%changelog +* Wed Jul 20 2011 Dmitry Kasatkin +- Initial package for MeeGo + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..6779baf --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,12 @@ + +bin_PROGRAMS = evmctl + +evmctl_SOURCES = evmctl.c +evmctl_CPPFLAGS = $(OPENSSL_CFLAGS) +evmctl_LDFLAGS = $(LDFLAGS_READLINE) +evmctl_LDADD = $(OPENSSL_LIBS) -lkeyutils + +INCLUDES = -I$(top_srcdir) -include config.h + +DISTCLEANFILES = @DISTCLEANFILES@ + diff --git a/src/evmctl.c b/src/evmctl.c new file mode 100644 index 0000000..61f4493 --- /dev/null +++ b/src/evmctl.c @@ -0,0 +1,974 @@ +/* + * evm-utils - IMA/EVM support utilities + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Intel Corporation + * + * Authors: + * Dmitry Kasatkin + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * File: evmctl.c + * IMA/EVM control program + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define USE_FPRINTF + +#ifdef USE_FPRINTF +#define do_log(level, fmt, args...) if (level <= verbose) fprintf(stderr, fmt, ##args) +#define do_log_dump(level, p, len) if (level <= verbose) do_dump(stderr, p, len) +#else +#define do_log(level, fmt, args...) syslog(level, fmt, ##args) +#define do_log_dump(p, len) +#endif + +#ifdef DEBUG +#define log_debug(fmt, args...) do_log(LOG_DEBUG, "%s:%d " fmt, __func__ , __LINE__ , ##args) +#define log_debug_dump(p, len) do_log_dump(LOG_DEBUG, p, len) +#else +#define log_debug(fmt, args...) +#define log_debug_dump(p, len) +#endif + +#define log_dump(p, len) do_log_dump(LOG_INFO, p, len) +#define log_info(fmt, args...) do_log(LOG_INFO, fmt, ##args) +#define log_err(fmt, args...) do_log(LOG_ERR, fmt, ##args) +#define log_errno(fmt, args...) do_log(LOG_ERR, fmt ": %s (%d)\n", ##args, strerror(errno), errno) + +#define DATA_SIZE 4096 +#define SHA1_HASH_LEN 20 + +#define EXT2_IOC_GETVERSION _IOR('v', 1, long) +#define EXT34_IOC_GETVERSION _IOR('f', 3, long) + +#define FS_IOC_GETFLAGS _IOR('f', 1, long) +#define FS_IOC_SETFLAGS _IOW('f', 2, long) +#define FS_IOC32_GETFLAGS _IOR('f', 1, int) +#define FS_IOC32_SETFLAGS _IOW('f', 2, int) + +struct h_misc { + unsigned long ino; + uint32_t generation; + uid_t uid; + gid_t gid; + unsigned short mode; +} hmac_misc; + +enum pubkey_algo { + PUBKEY_ALGO_RSA, + PUBKEY_ALGO_MAX, +}; + +enum digest_algo { + DIGEST_ALGO_SHA1, + DIGEST_ALGO_SHA256, + DIGEST_ALGO_MAX +}; + +struct pubkey_hdr { + uint8_t version; /* key format version */ + time_t timestamp; /* key made, always 0 for now */ + uint8_t algo; + uint8_t nmpi; + char mpi[0]; +} __attribute__ ((packed)); + + +struct signature_hdr { + uint8_t version; /* signature format version */ + time_t timestamp; /* signature made */ + uint8_t algo; + uint8_t hash; + uint8_t keyid[8]; + uint8_t nmpi; + char mpi[0]; +} __attribute__ ((packed)); + + +static char *evm_config_xattrnames[] = { + "security.selinux", + "security.SMACK64", + "security.ima", + "security.capability", + NULL +}; + +struct command { + char *name; + int (*func)(struct command *cmd); + int cmd; + char *arg; + char *msg; /* extra info message */ +}; + +static int verbose = LOG_INFO - 1; +static int g_argc; +static char **g_argv; +static int set_xattr = 1; +static int digest = 0; +static int digsig = 0; +static char *hash_algo = "sha1"; +static int binkey = 0; + +extern struct command cmds[]; +static void print_usage(struct command *cmd); + +static void do_dump(FILE *fp, const void *ptr, int len) +{ + int i; + uint8_t *data = (uint8_t *)ptr; + + for (i = 0; i < len; i++) { + fprintf(fp, "%02x", data[i]); + } + fprintf(fp, "\n"); +} + +static void dump(const void *ptr, int len) +{ + do_dump(stdout, ptr, len); +} + +static inline int get_filesize(const char *filename) +{ + struct stat stats; + /* Need to know the file length */ + stat(filename, &stats); + return (int) stats.st_size; +} + +static inline int get_fdsize(int fd) +{ + struct stat stats; + /* Need to know the file length */ + fstat(fd, &stats); + return (int) stats.st_size; +} + +static int bin2file(const char *file, const char *ext, const unsigned char *data, int len) +{ + FILE *fp; + char name[strlen(file) + (ext ? strlen(ext) : 0) + 2]; + int err; + + if (ext) + sprintf(name, "%s.%s", file, ext); + else + sprintf(name, "%s", file); + + log_info("Writing to %s\n", name); + + fp = fopen(name, "w"); + if (!fp) { + log_errno("Unable to open %s for writing", name); + return -1; + } + err = fwrite(data, len, 1, fp); + fclose(fp); + return err; +} + +static char *file2bin(const char *file, int *size) +{ + FILE *fp; + int len; + char *data; + + len = get_filesize(file); + fp = fopen(file, "r"); + if (!fp) { + log_errno("Unable to open %s", file); + return NULL; + } + data = malloc(len); + if (!fread(data, len, 1, fp)) + len = 0; + fclose(fp); + + *size = len; + return data; +} + +/* + * Create binary key representation suitable for kernel + */ +static int key2bin(RSA *key, unsigned char *pub) +{ + int len, b, offset = 0; + struct pubkey_hdr *pkh = (struct pubkey_hdr *)pub; + + /* add key header */ + pkh->version = 1; + pkh->timestamp = 0; /* PEM has no timestamp?? */ + pkh->algo = PUBKEY_ALGO_RSA; + pkh->nmpi = 2; + + offset += sizeof(*pkh); + + // MPIs + len = BN_num_bytes(key->n); + b = BN_num_bits(key->n); + pub[offset++] = b >> 8; + pub[offset++] = b & 0xff; + BN_bn2bin(key->n, &pub[offset]); + offset += len; + + len = BN_num_bytes(key->e); + b = BN_num_bits(key->e); + pub[offset++] = b >> 8; + pub[offset++] = b & 0xff; + BN_bn2bin(key->e, &pub[offset]); + offset += len; + + return offset; +} + +static int read_key(const char *inkey, unsigned char *pub) +{ + FILE *fp; + RSA *key = NULL, *key1; + int len; + + fp = fopen(inkey, "r"); + if (!fp) { + log_errno("read key failed from file %s", inkey); + return -1; + } + + key1 = PEM_read_RSA_PUBKEY(fp, &key, NULL, NULL); + fclose(fp); + if (!key1) { + log_errno("PEM_read_RSA_PUBKEY() failed"); + return -1; + } + + len = key2bin(key, pub); + + RSA_free(key); + + return len; +} + + +static void calc_keyid(uint8_t *keyid, char *str, const unsigned char *pkey, int len) +{ + uint8_t sha1[SHA_DIGEST_LENGTH]; + uint64_t id; + + log_debug("pkey:\n"); + log_debug_dump(pkey, len); + SHA1(pkey, len, sha1); + + //sha1[12 - 19] is exactly keyid from gpg file + memcpy(keyid, sha1 + 12, 8); + log_debug("keyid:\n"); + log_debug_dump(keyid, 8); + + id = __be64_to_cpup((__be64 *)keyid); + sprintf(str, "%llX", (unsigned long long)id); + log_info("keyid: %s\n", str); +} + +static int sign_hash(const unsigned char *hash, int size, const char *keyfile, unsigned char *sig) +{ + int err, len; + SHA_CTX ctx; + unsigned char pub[1024]; + RSA *key = NULL, *key1; + FILE *fp; + char name[20]; + unsigned char sighash[20]; + struct signature_hdr *hdr = (struct signature_hdr *)sig; + uint16_t *blen; + + log_info("hash: "); + log_dump(hash, size); + + fp = fopen(keyfile, "r"); + if (!fp) { + log_errno("Unable to open keyfile %s", keyfile); + return -1; + } + key1 = PEM_read_RSAPrivateKey(fp, &key, NULL, NULL); + fclose(fp); + if (!key1) { + log_errno("RSAPrivateKey() failed"); + return -1; + } + + /* now create a new hash */ + hdr->version = 1; + time(&hdr->timestamp); + hdr->algo = PUBKEY_ALGO_RSA; + hdr->hash = DIGEST_ALGO_SHA1; + + len = key2bin(key, pub); + calc_keyid(hdr->keyid, name, pub, len); + + hdr->nmpi = 1; + + SHA1_Init(&ctx); + SHA1_Update(&ctx, hash, size); + SHA1_Update(&ctx, hdr, sizeof(*hdr)); + SHA1_Final(sighash, &ctx); + log_info("sighash: "); + log_dump(sighash, sizeof(sighash)); + + err = RSA_private_encrypt(sizeof(sighash), sighash, sig + sizeof(*hdr) + 2, key, RSA_PKCS1_PADDING); + RSA_free(key); + if (err < 0) { + log_errno("RSA_private_encrypt() failed: %d", err); + return -1; + } + + len = err; + + /* we add bit length of the signature to make it gnupg compatible */ + blen = (uint16_t *)(sig + sizeof(*hdr)); + *blen = __cpu_to_be16(len << 3); + len += sizeof(*hdr) + 2; + log_info("evm/ima signature: %d bytes\n", len); + if (!set_xattr || verbose >= LOG_INFO) + dump(sig, len); + + return len; +} + +static int calc_evm_hash(const char *file, const char *keyfile, unsigned char *hash) +{ + struct stat st; + int fd, err; + uint32_t generation; + SHA_CTX ctx; + char **xattrname; + char xattr_value[1024]; + + fd = open(file, 0); + if (fd < 0) { + log_errno("Unable to open %s", file); + return -1; + } + + if (fstat(fd, &st)) { + log_errno("fstat() failed"); + return -1; + } + + if (ioctl(fd, EXT34_IOC_GETVERSION, &generation)) { + log_errno("ioctl() failed"); + return -1; + } + + close(fd); + + log_info("generation: %u\n", generation); + + SHA1_Init(&ctx); + + for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) { + err = getxattr(file, *xattrname, xattr_value, sizeof(xattr_value)); + if (err < 0) { + log_info("no attr: %s\n", *xattrname); + continue; + } + //log_debug("name: %s, value: %s, size: %d\n", *xattrname, xattr_value, err); + log_info("name: %s, size: %d\n", *xattrname, err); + log_debug_dump(xattr_value, err); + SHA1_Update(&ctx, xattr_value, err); + } + + memset(&hmac_misc, 0, sizeof(hmac_misc)); + hmac_misc.ino = st.st_ino; + hmac_misc.generation = generation; + hmac_misc.uid = st.st_uid; + hmac_misc.gid = st.st_gid; + hmac_misc.mode = st.st_mode; + + SHA1_Update(&ctx, (const unsigned char*)&hmac_misc, sizeof(hmac_misc)); + SHA1_Final(hash, &ctx); + + return 0; +} + +static int sign_evm(const char *file, const char *key) +{ + unsigned char hash[20]; + unsigned char sig[1024] = "\x03"; + int err; + + calc_evm_hash(file, key, hash); + + err = sign_hash(hash, sizeof(hash), key, sig + 1); + if (err < 0) + return err; + + if (set_xattr) { + err = setxattr(file, "security.evm", sig, err + 1, 0); + if (err < 0) { + log_errno("setxattr failed: %s", file); + return err; + } + } + + return 0; +} + +static int calc_file_hash(const char *file, uint8_t *hash) +{ + EVP_MD_CTX ctx; + const EVP_MD *md; + uint8_t *data; + int err, size, bs = DATA_SIZE; + size_t len; + unsigned int mdlen; + FILE *fp; + + data = malloc(bs); + if (!data) { + log_errno("malloc failed"); + return -1; + } + + fp = fopen(file, "r"); + if (!fp) { + log_errno("Unable to open %s", file); + return -1; + } + + OpenSSL_add_all_digests(); + + md = EVP_get_digestbyname(hash_algo); + if (!md) { + log_errno("EVP_get_digestbyname() failed"); + return -1; + } + + err = EVP_DigestInit(&ctx, md); + if (!err) { + log_errno("EVP_DigestInit() failed"); + return -1; + } + + for (size = get_fdsize(fileno(fp)); size; size -= len) { + len = MIN(size, bs); + err = fread(data, len, 1, fp); + if (!err) { + if (ferror(fp)) { + log_errno("fread() error\n"); + return -1; + } + break; + } + err = EVP_DigestUpdate(&ctx, data, len); + if (!err) { + log_errno("EVP_DigestUpdate() failed"); + return -1; + } + } + + err = EVP_DigestFinal(&ctx, hash, &mdlen); + if (!err) { + log_errno("EVP_DigestFinal() failed"); + return -1; + } + + fclose(fp); + + free(data); + + return mdlen; +} + +static int hash_ima(const char *file) +{ + unsigned char hash[65] = "\x01";// MAX hash size + 1 + int err; + + err = calc_file_hash(file, hash + 1); + if (err < 0) + return err; + + if (!set_xattr || verbose >= LOG_INFO) + dump(hash, err + 1); + + if (set_xattr) { + err = setxattr(file, "security.ima", hash, err + 1, 0); + if (err < 0) { + log_errno("setxattr failed: %s", file); + return err; + } + } + + return 0; +} + +static int cmd_hash_ima(struct command *cmd) +{ + char *file = g_argv[optind++]; + + if (!file) { + log_err("Parameters missing\n"); + print_usage(cmd); + return 1; + } + + return hash_ima(file); +} + +static int sign_ima(const char *file, const char *key) +{ + unsigned char hash[64]; + unsigned char sig[1024] = "\x03"; + int err; + + err = calc_file_hash(file, hash); + if (err < 0) + return err; + + err = sign_hash(hash, err, key, sig + 1); + if (err < 0) + return err; + + if (set_xattr) { + err = setxattr(file, "security.ima", sig, err + 1, 0); + if (err < 0) { + log_errno("setxattr failed: %s", file); + return err; + } + } + + return 0; +} + +static int cmd_sign_ima(struct command *cmd) +{ + char *key, *file = g_argv[optind++]; + + if (!file) { + log_err("Parameters missing\n"); + print_usage(cmd); + return 1; + } + + key = g_argv[optind++]; + if (!key) + key = "/etc/keys/privkey_evm.pem"; + + return sign_ima(file, key); + +} + +static int cmd_sign_evm(struct command *cmd) +{ + char *key, *file = g_argv[optind++]; + int err; + + if (!file) { + log_err("Parameters missing\n"); + print_usage(cmd); + return 1; + } + + key = g_argv[optind++]; + if (!key) + key = "/etc/keys/privkey_evm.pem"; + + if (digsig) { + err = sign_ima(file, key); + if (err) + return err; + } + + if (digest) { + err = hash_ima(file); + if (err) + return err; + } + + return sign_evm(file, key); +} + +static int verify_hash(const unsigned char *hash, int size, unsigned char *sig, int siglen, const char *keyfile) +{ + int err, len; + SHA_CTX ctx; + unsigned char out[1024]; + RSA *key = NULL, *key1; + FILE *fp; + unsigned char sighash[20]; + struct signature_hdr *hdr = (struct signature_hdr *)sig; + + log_info("hash: "); + log_dump(hash, size); + + fp = fopen(keyfile, "r"); + if (!fp) { + log_errno("Unable to open keyfile %s", keyfile); + return -1; + } + key1 = PEM_read_RSA_PUBKEY(fp, &key, NULL, NULL); + fclose(fp); + if (!key1) { + log_errno("PEM_read_RSA_PUBKEY() failed"); + return -1; + } + + SHA1_Init(&ctx); + SHA1_Update(&ctx, hash, size); + SHA1_Update(&ctx, hdr, sizeof(*hdr)); + SHA1_Final(sighash, &ctx); + log_info("sighash: "); + log_dump(sighash, sizeof(sighash)); + + err = RSA_public_decrypt(siglen - sizeof(*hdr) - 2, sig + sizeof(*hdr) + 2, out, key, RSA_PKCS1_PADDING); + RSA_free(key); + if (err < 0) { + log_errno("RSA_public_decrypt() failed: %d", err); + return -1; + } + + len = err; + + if (len != sizeof(sighash) || memcmp(out, sighash, len) != 0) { + log_errno("Verification failed: %d", err); + return -1; + } else { + //log_info("Verification is OK\n"); + printf("Verification is OK\n"); + } + + return 0; +} + +static int verify_evm(const char *file, const char *key) +{ + unsigned char hash[20]; + unsigned char sig[1024]; + int err; + + calc_evm_hash(file, key, hash); + + err = getxattr(file, "security.evm", sig, sizeof(sig)); + if (err < 0) { + log_errno("getxattr failed"); + return err; + } + + if (sig[0] != 0x03) { + log_errno("security.evm has not signature"); + return err; + } + + return verify_hash(hash, sizeof(hash), sig + 1, err - 1, key); +} + +static int cmd_verify_evm(struct command *cmd) +{ + char *key, *file = g_argv[optind++]; + + if (!file) { + log_err("Parameters missing\n"); + print_usage(cmd); + return 1; + } + + key = g_argv[optind++]; + if (!key) + key = "/etc/keys/pubkey_evm.pem"; + + return verify_evm(file, key); +} + +static int cmd_convert(struct command *cmd) +{ + char *inkey, *outkey = NULL; + unsigned char pub[1024]; + char name[20]; + int len; + uint8_t keyid[8]; + + inkey = g_argv[optind++]; + if (!inkey) + inkey = "/etc/keys/pubkey_evm.pem"; + else + outkey = g_argv[optind++]; + + if (!outkey) + outkey = "pubkey_evm.bin"; + + log_info("Convert public key %s to %s\n", inkey, outkey); + + len = read_key(inkey, pub); + if (len < 0) + return -1; + + calc_keyid(keyid, name, pub, len); + + bin2file(outkey, name, pub, len); + + return 0; +} + +static int cmd_import_bin(struct command *cmd) +{ + int len; + char *inkey, *ring = NULL; + char *key, name[20]; + key_serial_t id; + uint8_t keyid[8]; + + inkey = g_argv[optind++]; + if (!inkey) + inkey = "/etc/keys/pubkey_evm.bin"; + else + ring = g_argv[optind++]; + + if (!ring) + id = KEY_SPEC_USER_KEYRING; + else + id = atoi(ring); + + key = file2bin(inkey, &len); + if (!key) + return -1; + + calc_keyid(keyid, name, (unsigned char *)key, len); + + log_info("Importing public key %s from file %s into keyring %d\n", name, inkey, id); + + id = add_key("user", name, key, len, id); + if (id < 0) { + log_errno("add_key failed"); + return -1; + } + + log_info("keyid: %d\n", id); + printf("%d\n", id); + + free(key); + + return 0; +} + +static int cmd_import(struct command *cmd) +{ + char *inkey, *ring = NULL; + unsigned char key[1024]; + int id, len; + char name[20]; + uint8_t keyid[8]; + + if (binkey) + return cmd_import_bin(cmd); + + inkey = g_argv[optind++]; + if (!inkey) + inkey = "/etc/keys/pubkey_evm.pem"; + else + ring = g_argv[optind++]; + + if (!ring) + id = KEY_SPEC_USER_KEYRING; + else + id = atoi(ring); + + len = read_key(inkey, key); + if (len < 0) + return -1; + + calc_keyid(keyid, name, key, len); + + log_info("Importing public key %s from file %s into keyring %d\n", name, inkey, id); + + id = add_key("user", name, key, len, id); + if (id < 0) { + log_errno("add_key failed"); + return -1; + } + + log_info("keyid: %d\n", id); + printf("%d\n", id); + + return 0; +} + +static void print_usage(struct command *cmd) +{ + printf("usage: %s %s\n", cmd->name, cmd->arg ? cmd->arg : ""); +} + +static void print_full_usage(struct command *cmd) +{ + if (cmd->name) + printf("usage: %s %s\n", cmd->name, cmd->arg ? cmd->arg : ""); + if (cmd->msg) + printf("description:\n%s", cmd->msg); + +} + +static int print_command_usage(struct command *cmds, char *command) +{ + struct command *cmd; + + for (cmd = cmds; cmd->name; cmd++) { + if (strcmp(cmd->name, command) == 0) { + print_full_usage(cmd); + return 0; + } + } + printf("invalid command: %s\n", command); + return 1; +} + +static void print_all_usage(struct command *cmds) +{ + struct command *cmd; + + for (cmd = cmds; cmd->name; cmd++) { + if (cmd->arg) + printf("%s %s\n", cmd->name, cmd->arg); + else if (cmd->msg) + printf("%s", cmd->msg); + } +} + +static int call_command(struct command *cmds, char *command) +{ + struct command *cmd; + + for (cmd = cmds; cmd->name; cmd++) { + if (strcasecmp(cmd->name, command) == 0) + return cmd->func(cmd); + } + printf("Invalid command: %s\n", command); + return -1; +} + +static int cmd_help(struct command *cmd) +{ + if (!g_argv[optind]) { + print_usage(cmd); + return 0; + } else + return print_command_usage(cmds, g_argv[optind]); +} + +static void usage(void) +{ + printf("Usage: evmctl [parameters..]\n"); + + print_all_usage(cmds); +} + +struct command cmds[] = { + {"help", cmd_help, 0, ""}, + {"import", cmd_import, 0, "[--bin] inkey keyring", "Import public key (PEM/bin) into the keyring.\n" }, + {"convert", cmd_convert, 0, "inkey outkey", "Convert PEM public key into IMA/EVM kernel friendly format.\n" }, + {"sign", cmd_sign_evm, 0, "[--imahash | --imasig ] file [key]", "Sign file metadata.\n" }, + {"verify", cmd_verify_evm, 0, "file", "Verify EVM.\n" }, + {"ima_sign", cmd_sign_ima, 0, "file [key]", "Sign file content.\n" }, + {"ima_hash", cmd_hash_ima, 0, "file", "Hash file content.\n" }, + {0, 0, 0, NULL} +}; + +static struct option opts[] = { + {"help", 0, 0, 'h'}, + {"inkey", 1, 0, 'k'}, + {"imasig", 0, 0, 's'}, + {"imahash", 0, 0, 'd'}, + {"hashalgo", 1, 0, 'a'}, + {"bin", 0, 0, 'b'}, + {} + +}; + +int main(int argc, char *argv[]) +{ + int err = 0, c, lind; + + g_argv = argv; + g_argc = argc; + + while (1) { + c = getopt_long(argc, argv, "hk:vnsda:b", opts, &lind); + if (c == -1) + break; + + switch (c) { + case 'h': + usage(); + exit(0); + break; + case 'k': + printf("inkey: %s\n", optarg); + break; + case 'v': + verbose++; + break; + case 'd': + digest = 1; + break; + case 's': + digsig = 1; + break; + case 'n': + set_xattr = 0; // do not set Extended Attributes... just print signature + break; + case 'a': + hash_algo = optarg; + break; + case 'b': + binkey = 1; + break; + case '?': + exit(1); + break; + default: + log_err("getopt() returned: %d (%c)\n", c, c); + } + } + + if (argv[optind] == NULL) + usage(); + else + err = call_command(cmds, argv[optind++]); + + return err; +} diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..b84e36e --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,6 @@ +pkglib_PROGRAMS = openclose + +openclose_SOURCES = openclose.c + +dist_pkglib_SCRIPTS = evm_enable.sh evm_genkey.sh evm_sign_all.sh sign_modules_dir.sh ima_fix_dir.sh + diff --git a/tests/evm_enable.sh b/tests/evm_enable.sh new file mode 100755 index 0000000..97fa5e0 --- /dev/null +++ b/tests/evm_enable.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +# import EVM HMAC key +keyctl clear @u +keyctl add user kmk "testing123" @u +keyctl add encrypted evm-key "load `cat /etc/keys/evm-key`" @u + +# import Moule public key +mod_id=`keyctl newring _module @u` +evmctl import /etc/keys/pubkey_evm.pem $mod_id + +# import IMA public key +ima_id=`keyctl newring _ima @u` +evmctl import /etc/keys/pubkey_evm.pem $ima_id + +# import EVM public key +evm_id=`keyctl newring _evm @u` +evmctl import /etc/keys/pubkey_evm.pem $evm_id + +# enable EVM +echo "1" > /sys/kernel/security/evm + +# enable module checking +echo "1" > /sys/kernel/security/ima/module_check + diff --git a/tests/evm_genkey.sh b/tests/evm_genkey.sh new file mode 100755 index 0000000..86355e0 --- /dev/null +++ b/tests/evm_genkey.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +keyctl add user kmk "testing123" @u +key=`keyctl add encrypted evm-key "new user:kmk 32" @u` +keyctl print $key >/etc/keys/evm-key + +keyctl list @u + diff --git a/tests/evm_sign_all.sh b/tests/evm_sign_all.sh new file mode 100755 index 0000000..14a5c68 --- /dev/null +++ b/tests/evm_sign_all.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +verbose="" +if [ "$1" = "-v" ] ; then + verbose="-v" + shift 1 +fi + +dir=${1:-/} + +echo "Label: $dir" + +find $dir \( -fstype rootfs -o -fstype ext3 -o -fstype ext4 \) ! -path "/lib/modules/*" -type f -uid 0 -exec evmctl sign --imahash $verbose '{}' \; +find /lib/modules ! -name "*.ko" -type f -uid 0 -exec evmctl sign --imahash $verbose '{}' \; +# security.ima needs to have signature for modules +find /lib/modules -name "*.ko" -type f -uid 0 -exec evmctl sign --imasig $verbose '{}' \; + diff --git a/tests/ima_fix_dir.sh b/tests/ima_fix_dir.sh new file mode 100755 index 0000000..c1a1e88 --- /dev/null +++ b/tests/ima_fix_dir.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +dir=${1:-/} + +echo "Fixing dir: $dir" + +find $dir \( -fstype rootfs -o -fstype ext3 -o -fstype ext4 \) -type f -uid 0 -exec openclose '{}' \; + diff --git a/tests/openclose.c b/tests/openclose.c new file mode 100644 index 0000000..fd37a2b --- /dev/null +++ b/tests/openclose.c @@ -0,0 +1,20 @@ +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + int fd; + + fd = open(argv[1], O_RDONLY); + if (fd < 0) { + perror("open()"); + exit(1); + } + + close(fd); + + return 0; +} + diff --git a/tests/sign_modules_dir.sh b/tests/sign_modules_dir.sh new file mode 100755 index 0000000..eb875c4 --- /dev/null +++ b/tests/sign_modules_dir.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +verbose="" +if [ "$1" = "-v" ] ; then + verbose="-v" + shift 1 +fi + +dir=${1:-/lib/modules} + +echo "Signing modules: $dir" + +find $dir -name "*.ko" -type f -uid 0 -exec evmctl sign --imasig '{}' \; +find $dir ! -name "*.ko" -type f -uid 0 -exec evmctl sign --imahash '{}' \; +