summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJanusz Kozerski <j.kozerski@samsung.com>2014-10-02 14:23:32 +0200
committerJanusz Kozerski <j.kozerski@samsung.com>2014-10-02 14:59:52 +0200
commit47415fe6e82e2cd030a929bce08bf7113a0abb5d (patch)
tree8be142372e6fdac6224d03b3fe3a80c861464231
parent944cf4990dcb61804d0c9a311985e532b5af2610 (diff)
parent3d9bdc1de282846de3523fd7a698d473304650b0 (diff)
downloadima-evm-utils-sandbox/jkozerski/tizen.tar.gz
ima-evm-utils-sandbox/jkozerski/tizen.tar.bz2
ima-evm-utils-sandbox/jkozerski/tizen.zip
Merge branch 'upstream' into tizensandbox/jkozerski/tizen
Building documentation page is disabled until docbook-xsl is unavaliable on Tizen.org Conflicts: packaging/ima-evm-utils.spec Change-Id: Icc9fc09db4d18686e836251252b0a6ba9406dda2
-rw-r--r--.gitignore5
-rw-r--r--ChangeLog9
-rw-r--r--Makefile.am23
-rw-r--r--README436
-rw-r--r--configure.ac2
-rw-r--r--packaging/ima-evm-utils.spec9
-rw-r--r--src/evmctl.c496
-rw-r--r--src/imaevm.h23
-rw-r--r--src/libimaevm.c306
9 files changed, 862 insertions, 447 deletions
diff --git a/.gitignore b/.gitignore
index 0cae2b7..3819edf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,8 @@
*~
# Generated by autotools
+.libs
+m4
.deps
aclocal.m4
autom4te.cache
@@ -20,9 +22,12 @@ compile
libtool
ltmain.sh
+
# Compiled executables
*.o
*.a
+*.lo
+*.la
src/evmctl
tests/openclose
config.h
diff --git a/ChangeLog b/ChangeLog
index 3ead819..1129d42 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2014-09-23 Dmitry Kasatkin <d.kasatkin@samsung.com>
+
+ version 0.9
+ * Updated README
+ * man page generated and added to the package
+ * Use additional SMACK xattrs for EVM signature generation
+ * Signing functions moved to libimaevm for external use (RPM)
+ * Fixed setting of correct hash header
+
2014-05-05 Dmitry Kasatkin <d.kasatkin@samsung.com>
version 0.8
diff --git a/Makefile.am b/Makefile.am
index 73d3a4d..b35f05a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,10 @@
SUBDIRS = src
+#dist_man_MANS = evmctl.1
-EXTRA_DIST = autogen.sh
+doc_DATA = examples/ima-genkey-self.sh examples/ima-genkey.sh examples/ima-gen-local-ca.sh
+EXTRA_DIST = autogen.sh $(doc_DATA)
+
+CLEANFILES = *.html *.xsl
ACLOCAL_AMFLAGS = -I m4
@@ -19,4 +23,21 @@ rpm: $(tarname)
cp $(tarname) $(SRCS)/
rpmbuild -ba --nodeps $(SPEC)
+# requires asciidoc, xslproc, docbook-xsl
+# FIXME Disabled until docbook-xsl is unavaliable on tizen.org
+#MANPAGE_DOCBOOK_XSL = /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl
+#
+#evmctl.1.html: README
+# @asciidoc -o $@ $<
+#
+#evmctl.1:
+# asciidoc -d manpage -b docbook -o evmctl.1.xsl README
+# xsltproc --nonet -o $@ $(MANPAGE_DOCBOOK_XSL) evmctl.1.xsl
+# rm -f evmctl.1.xsl
+#
+#rmman:
+# rm -f evmctl.1
+#
+#doc: evmctl.1.html rmman evmctl.1
+
.PHONY: $(tarname)
diff --git a/README b/README
index f460ec7..c14a3ed 100644
--- a/README
+++ b/README
@@ -1,49 +1,187 @@
-ima-evm-utils - IMA/EVM signing utility
-=========================================
+EVMCTL(1)
+=========
-Contents:
+NAME
+----
- 1. Key and signature formats
- 2. Key generation
- 3. Initialization
- 4. Signing
+evmctl - IMA/EVM signing utility
+
+
+SYNOPSIS
+--------
+
+evmctl [options] <command> [OPTIONS]
+
+
+DESCRIPTION
+-----------
+
+The evmctl utility can be used for producing and verifying digital signatures,
+which are used by Linux kernel integrity subsystem (IMA/EVM). It can be also
+used to import keys into the kernel keyring.
+
+COMMANDS
+--------
+
+ --version
+ help <command>
+ import [--rsa] pubkey keyring
+ sign [-r] [--imahash | --imasig ] [--key key] [--pass password] file
+ verify file
+ ima_sign [--sigfile] [--key key] [--pass password] file
+ ima_verify file
+ ima_hash file
+ ima_measurement file
+ ima_fix [-t fdsxm] path
+ sign_hash [--key key] [--pass password]
+ hmac [--imahash | --imasig ] file
+
+
+OPTIONS
+-------
+
+ -a, --hashalgo sha1 (default), sha224, sha256, sha384, sha512
+ -s, --imasig make IMA signature
+ -d, --imahash make IMA hash
+ -f, --sigfile store IMA signature in .sig file instead of xattr
+ --rsa use RSA key type and signing scheme v1
+ -k, --key path to signing key (default: /etc/keys/{privkey,pubkey}_evm.pem)
+ -p, --pass password for encrypted signing key
+ -r, --recursive recurse into directories (sign)
+ -t, --type file types to fix 'fdsxm' (f: file, d: directory, s: block/char/symlink)
+ x - skip fixing if both ima and evm xattrs exist (use with caution)
+ m - stay on the same filesystem (like 'find -xdev')
+ -n print result to stdout instead of setting xattr
+ -u, --uuid use custom FS UUID for EVM (unspecified: from FS, empty: do not use)
+ --smack use extra SMACK xattrs for EVM
+ --m32 force EVM hmac/signature for 32 bit target system
+ --m64 force EVM hmac/signature for 64 bit target system
+ -v increase verbosity level
+ -h, --help display this help and exit
+
+
+INTRODUCTION
+------------
+
+Linux kernel integrity subsystem is comprised of a number of different components
+including the Integrity Measurement Architecture (IMA), Extended Verification Module
+(EVM), IMA-appraisal extension, digital signature verification extension and audit
+measurement log support.
+
+The evmctl utility is used for producing and verifying digital signatures, which
+are used by the Linux kernel integrity subsystem. It is also used for importing keys
+into the kernel keyring.
+
+Linux integrity subsystem allows to use IMA and EVM signatures. EVM signature
+protects file metadata, such as file attributes and extended attributes. IMA
+signature protects file content.
+
+For more detailed information about integrity subsystem it is recommended to follow
+resources in RESOURCES section.
+
+
+EVM HMAC and signature metadata
+-------------------------------
+
+EVM protects file metadata by including following attributes into HMAC and signature
+calculation: inode number, inode generation, UID, GID, file mode, security.selinux,
+security.SMACK64, security.ima, security.capability.
+
+EVM HMAC and signature in may also include additional file and file system attributes.
+Currently supported additional attributes are filesystem UUID and extra SMACK
+extended attributes.
+
+Kernel configuration option CONFIG_EVM_ATTR_FSUUID controls whether to include
+filesystem UUID into HMAC and enabled by default. Therefore evmctl also includes
+fsuuid by default. Providing '--uuid' option without parameter allows to disable
+usage of fs uuid. Providing '--uuid=UUID' option with parameter allows to use
+custom UUID.
+
+Kernel configuration option CONFIG_EVM_EXTRA_SMACK_XATTRS controls whether to
+include additional SMACK extended attributes into HMAC. They are following:
+security.SMACK64EXEC, security.SMACK64TRANSMUTE and security.SMACK64MMAP.
+evmctl '--smack' options enables that.
Key and signature formats
-------------------------
-EVM support (v2) in latest version of the kernel adds the file system UUID to
-the HMAC calculation. It is controlled by the CONFIG_EVM_HMAC_VERSION and
-version 2 is enabled by default. In this version default UUID is included by
-default. Custom value can be supplied via '--uuid=UUID' or '-uUUID' parameter
-to the 'sign' command. To use old format HMAC format use '-' as a parameter.
+Linux integrity subsystem supports two type of signature and respectively two
+key formats.
+
+First key format (v1) is pure RSA key encoded in PEM a format and uses own signature
+format. It is now non-default format and requires to provide evmctl '--rsa' option
+for signing and importing the key.
+
+Second key format uses X509 DER encoded public key certificates and uses asymmetric key support
+in the kernel (since kernel 3.9). CONFIG_INTEGRITY_ASYMMETRIC_KEYS must be enabled (default).
+
+
+Integrity keyrings
+----------------
+
+Integrity subsystem uses dedicated IMA/EVM keyrings to search for signature verification
+keys - '_ima' and '_evm' respectively.
+
+Since 3.13 IMA allows to declare IMA keyring as trusted. It allows only to load keys,
+signed by a key from the system keyring (.system). It means self-signed keys are not
+allowed. This is a default behavior unless CONFIG_IMA_TRUSTED_KEYRING is undefined.
+IMA trusted keyring is has different name '.ima'. Trusted keyring requires X509
+public key certificates. Old version RSA public keys are not compatible with trusted
+keyring.
+
+
+Generate EVM encrypted keys
+---------------------------
+
+EVM encrypted key is used for EVM HMAC calculation:
+
+ # create and save the key kernel master key (user type)
+ # LMK is used to encrypt encrypted keys
+ keyctl add user kmk "`dd if=/dev/urandom bs=1 count=32 2>/dev/null`" @u
+ keyctl pipe `keyctl search @u user kmk` > /etc/keys/kmk
+
+ # create the EVM encrypted key
+ keyctl add encrypted evm-key "new user:kmk 32" @u
+ keyctl pipe `keyctl search @u encrypted evm-key` >/etc/keys/evm-key
+
-Latest kernel got IMA/EVM support for using X509 certificates and asymmetric key
-support for verifying digital signatures. This version uses x509 format by default.
-Use '--rsa' or '-1' parameter to use old signature format and API.
+Generate EVM trusted keys (TPM based)
+-------------------------------------
+Trusted EVM keys are keys which a generate with the help of TPM.
+They are not related to integrity trusted keys.
-Key generation
---------------
+ # create and save the key kernel master key (user type)
+ keyctl add trusted kmk "new 32" @u
+ keyctl pipe `keyctl search @u trusted kmk` >kmk
-Generate private key in plain text format
+ # create the EVM trusted key
+ keyctl add encrypted evm-key "new trusted:kmk 32" @u
+ keyctl pipe `keyctl search @u encrypted evm-key` >evm-key
- $ openssl genrsa -out privkey_evm.pem 1024
-Generate encrypted private key
+Generate signing and verification keys
+--------------------------------------
- $ openssl genrsa -des3 -out privkey_evm.pem 1024
+Generate private key in plain text format:
-Make encrypted private key from unencrypted
+ openssl genrsa -out privkey_evm.pem 1024
- $ openssl rsa -in /etc/keys/privkey_evm.pem -out privkey_evm_enc.pem -des3
+Generate encrypted private key:
-Generate self-signed X509 certificate and private key for using kernel asymmetric
-keys support
+ openssl genrsa -des3 -out privkey_evm.pem 1024
- $ openssl req -new -nodes -utf8 -sha1 -days 36500 -batch \
- -x509 -config x509_evm.genkey \
- -outform DER -out x509_evm.der -keyout privkey_evm.pem
+Make encrypted private key from unencrypted:
+
+ openssl rsa -in /etc/keys/privkey_evm.pem -out privkey_evm_enc.pem -des3
+
+Generate self-signed X509 public key certificate and private key for using kernel
+asymmetric keys support:
+
+ openssl req -new -nodes -utf8 -sha1 -days 36500 -batch \
+ -x509 -config x509_evm.genkey \
+ -outform DER -out x509_evm.der -keyout privkey_evm.pem
Configuration file x509_evm.genkey:
@@ -68,88 +206,232 @@ Configuration file x509_evm.genkey:
# EOF
-Get public key
+Generate public key for using RSA key format:
- $ openssl rsa -pubout -in privkey_evm.pem -out pubkey_evm.pem
+ openssl rsa -pubout -in privkey_evm.pem -out pubkey_evm.pem
-Copy keys to /etc/keys
- $ cp pubkey_evm.pem /etc/keys
- $ scp pubkey_evm.pem target:/etc/keys
+Copy keys to /etc/keys:
-or
- $ cp x509_evm.pem /etc/keys
- $ scp x509_evm.pem target:/etc/keys
+ cp pubkey_evm.pem /etc/keys
+ scp pubkey_evm.pem target:/etc/keys
+ or
+ cp x509_evm.pem /etc/keys
+ scp x509_evm.pem target:/etc/keys
-Generation of EVM keys
+Generate trusted keys
+---------------------
- $ # create and save the kernel master key (user type)
- $ keyctl add user kmk "`dd if=/dev/urandom bs=1 count=32 2>/dev/null`" @u
- $ keyctl pipe `keyctl search @u user kmk` > /etc/keys/kmk
- $ # create the EVM encrypted key
- $ keyctl add encrypted evm-key "new user:kmk 32" @u
- $ keyctl pipe `keyctl search @u encrypted evm-key` >/etc/keys/evm-key
+Generation of trusted keys is a bit more complicated process and involves
+following steps:
+* Creation of local IMA certification authority (CA).
+ It consist of private and public key certificate which are used
+ to sign and verify other keys.
+* Build Linux kernel with embedded local IMA CA X509 certificate.
+ It is used to verify other keys added to the '.ima' trusted keyring
+* Generate IMA private signing key and verification public key certificate,
+ which is signed using local IMA CA private key.
+
+Configuration file ima-local-ca.genkey:
+
+ # Begining of the file
+ [ req ]
+ default_bits = 2048
+ distinguished_name = req_distinguished_name
+ prompt = no
+ string_mask = utf8only
+ x509_extensions = v3_ca
-Initialization
---------------
+ [ req_distinguished_name ]
+ O = IMA-CA
+ CN = IMA/EVM certificate signing key
+ emailAddress = ca@ima-ca
+
+ [ v3_ca ]
+ basicConstraints=CA:TRUE
+ subjectKeyIdentifier=hash
+ authorityKeyIdentifier=keyid:always,issuer
+ # keyUsage = cRLSign, keyCertSign
+ # EOF
+
+Generate private key and X509 public key certificate:
+
+ openssl req -new -x509 -utf8 -sha1 -days 3650 -batch -config $GENKEY \
+ -outform DER -out ima-local-ca.x509 -keyout ima-local-ca.priv
+
+Produce X509 in DER format for using while building the kernel:
+
+ openssl x509 -inform DER -in ima-local-ca.x509 -out ima-local-ca.pem
+
+Configuration file ima.genkey:
+
+ # Begining of the file
+ [ req ]
+ default_bits = 1024
+ distinguished_name = req_distinguished_name
+ prompt = no
+ string_mask = utf8only
+ x509_extensions = v3_usr
+
+ [ req_distinguished_name ]
+ O = `hostname`
+ CN = `whoami` signing key
+ emailAddress = `whoami`@`hostname`
+
+ [ v3_usr ]
+ basicConstraints=critical,CA:FALSE
+ #basicConstraints=CA:FALSE
+ keyUsage=digitalSignature
+ #keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+ subjectKeyIdentifier=hash
+ authorityKeyIdentifier=keyid
+ #authorityKeyIdentifier=keyid,issuer
+ # EOF
+
+
+Generate private key and X509 public key certificate signing request:
+
+ openssl req -new -nodes -utf8 -sha1 -days 365 -batch -config $GENKEY \
+ -out csr_ima.pem -keyout privkey_ima.pem
+
+Sign X509 public key certificate signing request with local IMA CA private key:
+
+ openssl x509 -req -in csr_ima.pem -days 365 -extfile $GENKEY -extensions v3_usr \
+ -CA ima-local-ca.pem -CAkey ima-local-ca.priv -CAcreateserial \
+ -outform DER -out x509_ima.der
+
+
+Sign file data and metadata
+---------------------------
+
+Default key locations:
+
+ Private RSA key: /etc/keys/privkey_evm.pem
+ Public RSA key: /etc/keys/pubkey_evm.pem
+ X509 certificate: /etc/keys/x509_evm.der
+
+Options to remember: '-k', '-r', '--rsa', '--uuid', '--smack'.
+
+Sign file with EVM signature and calculate hash value for IMA:
+
+ evmctl sign --imahash test.txt
+
+Sign file with both IMA and EVM signatures:
+
+ evmctl sign --imasig test.txt:
+
+Sign file with IMA signature:
+
+ evmctl ima_sign test.txt
+
+Sign recursively whole filesystem:
+
+ evmctl -r sign --imahash /
+
+Fix recursively whole filesystem:
+
+ evmctl -r ima_fix /
+
+Sign filesystem selectively using 'find' command:
+
+ find / \( -fstype rootfs -o -fstype ext4 \) -exec evmctl sign --imahash '{}' \;
+
+Fix filesystem selectively using 'find' command:
+
+ find / \( -fstype rootfs -o -fstype ext4 \) -exec sh -c "< '{}'" \;
+
+
+Initialize IMA/EVM at early boot
+--------------------------------
IMA/EVM initialization should be normally done from initial RAM file system
before mounting root filesystem.
-Here is an example script /etc/initramfs-tools/scripts/local-top/ima.sh
-
- # import EVM HMAC key
- keyctl clear @u
+Here is Ubuntu initramfs example script (/etc/initramfs-tools/scripts/local-top/ima.sh)
+
+ # mount securityfs if not mounted
+ SECFS=/sys/kernel/security
+ grep -q $SECFS /proc/mounts || mount -n -t securityfs securityfs $SECFS
+
+ # search for IMA trusted keyring, then for untrusted
+ ima_id="`awk '/\.ima/ { printf "%d", "0x"$1; }' /proc/keys`"
+ if [ -z "$ima_id" ]; then
+ ima_id=`keyctl search @u keyring _ima 2>/dev/null`
+ if [ -z "$ima_id" ]; then
+ ima_id=`keyctl newring _ima @u`
+ fi
+ fi
+ # import IMA X509 certificate
+ evmctl import /etc/keys/x509_ima.der $ima_id
+
+ # search for EVM keyring
+ evm_id=`keyctl search @u keyring _evm 2>/dev/null`
+ if [ -z "$evm_id" ]; then
+ evm_id=`keyctl newring _evm @u`
+ fi
+ # import EVM X509 certificate
+ evmctl import /etc/keys/x509_evm.der $evm_id
+
+ # a) import EVM encrypted key
cat /etc/keys/kmk | keyctl padd user kmk @u
keyctl add encrypted evm-key "load `cat /etc/keys/evm-key`" @u
-
- # import IMA public key
- ima_id=`keyctl newring _ima @u`
- evmctl --rsa import /etc/keys/pubkey_evm.pem $ima_id
-
- # import EVM public key
- evm_id=`keyctl newring _evm @u`
- evmctl --rsa import /etc/keys/pubkey_evm.pem $evm_id
+ # OR
+ # b) import EVM trusted key
+ keyctl add trusted kmk "load `cat /etc/keys/kmk`" @u
+ keyctl add encrypted evm-key "load `cat /etc/keys/evm-key`" @u
# enable EVM
echo "1" > /sys/kernel/security/evm
+Optionally it is possible also to forbid adding, removing of new public keys
+and certificates into keyrings and revoking keys using 'keyctl setperm' command:
-Import X509 certificate into the kernel keyring (since kernel 3.9?)
+ # protect EVM keyring
+ keyctl setperm $evm_id 0x0b0b0000
+ # protect IMA keyring
+ keyctl setperm $ima_id 0x0b0b0000
+ # protecting IMA key from revoking (against DoS)
+ ima_key=`evmctl import /etc/keys/x509_ima.der $ima_id`
+ keyctl setperm $ima_key 0x0b0b0000
- $ evmctl import /etc/keys/x509_evm.der `keyctl search @u keyring _ima`
- $ evmctl import /etc/keys/x509_evm.der `keyctl search @u keyring _evm`
+When using plain RSA public keys in PEM format, use 'evmctl import --rsa' for importing keys:
-Signing
--------
+ evmctl import --rsa /etc/keys/pubkey_evm.pem $evm_id
+
+Latest version of keyctl allows to import X509 public key certificates:
+
+ cat /etc/keys/x509_ima.der | keyctl padd asymmetric '' @ima_id
-Default public key: /etc/keys/pubkey_evm.pem
-Default private key: /etc/keys/privkey_evm.pem
-Default X509 certificate: /etc/keys/x509_evm.der
-Signing for using old RSA format is done using '-1' or '--rsa' parameter.
-Signing for using old EVM HMAC format is done using '-u-' or '--uuid=-' parameter.
+FILES
+-----
-Sign file with EVM signature and use hash value for IMA - common case
+Examples of scripts to generate X509 public key certificates:
- $ evmctl sign [-u] [-1] --imahash test.txt
+ /usr/share/doc/ima-evm-utils/ima-genkey-self.sh
+ /usr/share/doc/ima-evm-utils/ima-genkey.sh
+ /usr/share/doc/ima-evm-utils/ima-gen-local-ca.sh
-Sign file with both IMA and EVM signatures - for immutable files
- $ evmctl sign [-u] [-1] --imasig test.txt
+AUTHOR
+------
-Sign file with IMA signature - for immutable files
+Written by Dmitry Kasatkin, <dmitry.kasatkin at gmail.com> and others.
- $ evmctl ima_sign [-1] test.txt
-Label whole filesystem with EVM signatures
+RESOURCES
+---------
- $ find / \( -fstype rootfs -o -fstype ext4 \) -exec evmctl sign [-u] [-1] --imahash '{}' \;
+ http://sourceforge.net/p/linux-ima/wiki/Home
+ http://sourceforge.net/p/linux-ima/ima-evm-utils
-Label filesystem in fix mode - kernel sets correct values to IMA and EVM xattrs
- $ find / \( -fstype rootfs -o -fstype ext4 \) -exec sh -c "< '{}'" \;
+COPYING
+-------
+
+Copyright \(C) 2012 - 2014 Linux Integrity Project. Free use of this software is granted under
+the terms of the GNU Public License (GPL).
diff --git a/configure.ac b/configure.ac
index 78821c5..5e5ea21 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,7 +1,7 @@
# autoconf script
AC_PREREQ([2.65])
-AC_INIT(ima-evm-utils, 0.8, d.kasatkin@samsung.com)
+AC_INIT(ima-evm-utils, 0.9, d.kasatkin@samsung.com)
AM_INIT_AUTOMAKE
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIR([m4])
diff --git a/packaging/ima-evm-utils.spec b/packaging/ima-evm-utils.spec
index 42c56ef..9d19fb8 100644
--- a/packaging/ima-evm-utils.spec
+++ b/packaging/ima-evm-utils.spec
@@ -1,7 +1,7 @@
Name: ima-evm-utils
-Version: 0.8
-Release: 1
-Summary: IMA/EVM control utility
+Version: 0.9
+Release: 1%{?dist}
+Summary: ima-evm-utils - IMA/EVM control utility
Group: System/Libraries
License: GPL-2.0
#URL:
@@ -10,6 +10,8 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
BuildRequires: autoconf
BuildRequires: automake
+BuildRequires: asciidoc
+BuildRequires: xsltproc
BuildRequires: openssl-devel
BuildRequires: libattr-devel
BuildRequires: keyutils-devel
@@ -46,6 +48,7 @@ exit 0
%{_bindir}/*
%{_libdir}/*
%{_includedir}/*
+/usr/share/doc/ima-evm-utils/*
%changelog
* Thu Apr 05 2012 Dmitry Kasatkin <dmitry.kasatkin@intel.com>
diff --git a/src/evmctl.c b/src/evmctl.c
index c5529f8..109b82a 100644
--- a/src/evmctl.c
+++ b/src/evmctl.c
@@ -50,9 +50,9 @@
#include <string.h>
#include <dirent.h>
#include <attr/xattr.h>
+#include <linux/xattr.h>
#include <getopt.h>
#include <keyutils.h>
-#include <asm/byteorder.h>
#include <ctype.h>
#include <openssl/sha.h>
@@ -65,14 +65,27 @@
#include "imaevm.h"
-static char *evm_config_xattrnames[] = {
- "security.selinux",
- "security.SMACK64",
- "security.ima",
- "security.capability",
+static char *evm_default_xattrs[] = {
+ XATTR_NAME_SELINUX,
+ XATTR_NAME_SMACK,
+ XATTR_NAME_IMA,
+ XATTR_NAME_CAPS,
NULL
};
+static char *evm_extra_smack_xattrs[] = {
+ XATTR_NAME_SELINUX,
+ XATTR_NAME_SMACK,
+ XATTR_NAME_SMACKEXEC,
+ XATTR_NAME_SMACKTRANSMUTE,
+ XATTR_NAME_SMACKMMAP,
+ XATTR_NAME_IMA,
+ XATTR_NAME_CAPS,
+ NULL
+};
+
+static char **evm_config_xattrnames = evm_default_xattrs;
+
struct command {
char *name;
int (*func)(struct command *cmd);
@@ -84,18 +97,21 @@ struct command {
static int g_argc;
static char **g_argv;
static int xattr = 1;
+static bool check_xattr;
static int sigdump;
static int digest;
static int digsig;
-static char *keypass;
static int sigfile;
-static int x509 = 1;
-static char *uuid_str = "+";
+static char *uuid_str;
static char *search_type;
static int recursive;
static int msize;
static dev_t fs_dev;
+#define HMAC_FLAG_UUID 0x0001
+#define HMAC_FLAG_UUID_SET 0x0002
+static unsigned long hmac_flags = HMAC_FLAG_UUID;
+
typedef int (*find_cb_t)(const char *path);
static int find(const char *path, int dts, find_cb_t func);
@@ -105,10 +121,6 @@ static int find(const char *path, int dts, find_cb_t func);
#define CHR_MASK (1 << DT_CHR)
#define BLK_MASK (1 << DT_BLK)
-typedef int (*sign_hash_fn_t)(const char *algo, const unsigned char *hash, int size, const char *keyfile, unsigned char *sig);
-
-static sign_hash_fn_t sign_hash;
-
struct command cmds[];
static void print_usage(struct command *cmd);
@@ -127,7 +139,7 @@ static int bin2file(const char *file, const char *ext, const unsigned char *data
fp = fopen(name, "w");
if (!fp) {
- log_err("Unable to open %s for writing\n", name);
+ log_err("Failed to open: %s\n", name);
return -1;
}
err = fwrite(data, len, 1, fp);
@@ -152,7 +164,7 @@ static unsigned char *file2bin(const char *file, const char *ext, int *size)
len = get_filesize(name);
fp = fopen(name, "r");
if (!fp) {
- log_err("Unable to open %s\n", name);
+ log_err("Failed to open: %s\n", name);
return NULL;
}
data = malloc(len);
@@ -164,215 +176,6 @@ static unsigned char *file2bin(const char *file, const char *ext, int *size)
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);
-
- 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 void calc_keyid_v1(uint8_t *keyid, char *str, const unsigned char *pkey, int len)
-{
- uint8_t sha1[SHA_DIGEST_LENGTH];
- uint64_t id;
-
- SHA1(pkey, len, sha1);
-
- /* sha1[12 - 19] is exactly keyid from gpg file */
- memcpy(keyid, sha1 + 12, 8);
- log_debug("keyid: ");
- 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 void calc_keyid_v2(uint32_t *keyid, char *str, RSA *key)
-{
- uint8_t sha1[SHA_DIGEST_LENGTH];
- unsigned char *pkey = NULL;
- int len;
-
- len = i2d_RSAPublicKey(key, &pkey);
-
- SHA1(pkey, len, sha1);
-
- /* sha1[12 - 19] is exactly keyid from gpg file */
- memcpy(keyid, sha1 + 16, 4);
- log_debug("keyid: ");
- log_debug_dump(keyid, 4);
-
- sprintf(str, "%x", __be32_to_cpup(keyid));
- log_info("keyid: %s\n", str);
-
- free(pkey);
-}
-
-static RSA *read_priv_key(const char *keyfile)
-{
- FILE *fp;
- RSA *key;
-
- fp = fopen(keyfile, "r");
- if (!fp) {
- log_err("Unable to open keyfile %s\n", keyfile);
- return NULL;
- }
- key = PEM_read_RSAPrivateKey(fp, NULL, NULL, keypass);
- if (!key)
- log_err("PEM_read_RSAPrivateKey() failed\n");
-
- fclose(fp);
- return key;
-}
-
-static int get_hash_algo_v1(const char *algo)
-{
-
- if (!strcmp(algo, "sha1"))
- return DIGEST_ALGO_SHA1;
- else if (!strcmp(algo, "sha256"))
- return DIGEST_ALGO_SHA256;
-
- return -1;
-}
-
-static int sign_hash_v1(const char *hashalgo, const unsigned char *hash, int size, const char *keyfile, unsigned char *sig)
-{
- int len = -1, hashalgo_idx;
- SHA_CTX ctx;
- unsigned char pub[1024];
- RSA *key;
- 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);
-
- key = read_priv_key(keyfile);
- if (!key)
- return -1;
-
- /* now create a new hash */
- hdr->version = (uint8_t) DIGSIG_VERSION_1;
- hdr->timestamp = time(NULL);
- hdr->algo = PUBKEY_ALGO_RSA;
- hashalgo_idx = get_hash_algo_v1(hashalgo);
- if (hashalgo_idx < 0) {
- log_err("Signature version 1 does not support hash algo %s\n",
- hashalgo);
- goto out;
- }
- hdr->hash = (uint8_t) hashalgo_idx;
-
- len = key2bin(key, pub);
- calc_keyid_v1(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));
-
- len = RSA_private_encrypt(sizeof(sighash), sighash, sig + sizeof(*hdr) + 2, key, RSA_PKCS1_PADDING);
- if (len < 0) {
- log_err("RSA_private_encrypt() failed: %d\n", len);
- goto out;
- }
-
- /* 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 (sigdump || params.verbose >= LOG_INFO)
- dump(sig, len);
-out:
- RSA_free(key);
- return len;
-}
-
-static int sign_hash_v2(const char *algo, const unsigned char *hash, int size, const char *keyfile, unsigned char *sig)
-{
- struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
- int len = -1;
- RSA *key;
- char name[20];
- unsigned char *buf;
- const struct RSA_ASN1_template *asn1;
-
- log_info("hash: ");
- log_dump(hash, size);
-
- key = read_priv_key(keyfile);
- if (!key)
- return -1;
-
- hdr->version = (uint8_t) DIGSIG_VERSION_2;
- hdr->hash_algo = get_hash_algo(algo);
-
- calc_keyid_v2(&hdr->keyid, name, key);
-
- asn1 = &RSA_ASN1_templates[hdr->hash_algo];
-
- buf = malloc(size + asn1->size);
- if (!buf)
- goto out;
-
- memcpy(buf, asn1->data, asn1->size);
- memcpy(buf + asn1->size, hash, size);
- len = RSA_private_encrypt(size + asn1->size, buf, hdr->sig,
- key, RSA_PKCS1_PADDING);
- if (len < 0) {
- log_err("RSA_private_encrypt() failed: %d\n", len);
- goto out;
- }
-
- /* we add bit length of the signature to make it gnupg compatible */
- hdr->sig_size = __cpu_to_be16(len);
- len += sizeof(*hdr);
- log_info("evm/ima signature: %d bytes\n", len);
- if (sigdump || params.verbose >= LOG_INFO)
- dump(sig, len);
-out:
- if (buf)
- free(buf);
- RSA_free(key);
- return len;
-}
-
static int find_xattr(const char *list, int list_size, const char *xattr)
{
int len;
@@ -470,7 +273,7 @@ static int get_uuid(struct stat *st, char *uuid)
FILE *fp;
size_t len;
- if (uuid_str[0] != '+')
+ if (hmac_flags & HMAC_FLAG_UUID_SET)
return pack_uuid(uuid_str, uuid);
dev = st->st_dev;
@@ -481,19 +284,18 @@ static int get_uuid(struct stat *st, char *uuid)
sprintf(path, "blkid -s UUID -o value /dev/block/%u:%u", major, minor);
fp = popen(path, "r");
- if (!fp) {
- log_err("popen() failed\n");
- return -1;
- }
+ if (!fp)
+ goto err;
len = fread(_uuid, 1, sizeof(_uuid), fp);
pclose(fp);
- if (len != sizeof(_uuid)) {
- log_err("fread() failed\n");
- return -1;
- }
+ if (len != sizeof(_uuid))
+ goto err;
return pack_uuid(_uuid, uuid);
+err:
+ log_err("Failed to read UUID. Root access might require.\n");
+ return -1;
}
static int calc_evm_hash(const char *file, unsigned char *hash)
@@ -512,7 +314,7 @@ static int calc_evm_hash(const char *file, unsigned char *hash)
int hmac_size;
if (lstat(file, &st)) {
- log_err("lstat() failed\n");
+ log_err("Failed to stat: %s\n", file);
return -1;
}
@@ -520,11 +322,12 @@ static int calc_evm_hash(const char *file, unsigned char *hash)
/* we cannot at the momement to get generation of special files..
* kernel API does not support it */
int fd = open(file, 0);
+
if (fd < 0) {
- log_err("Unable to open %s\n", file);
+ log_err("Failed to open: %s\n", file);
return -1;
}
- if (ioctl(fd, EXT34_IOC_GETVERSION, &generation)) {
+ if (ioctl(fd, FS_IOC_GETVERSION, &generation)) {
log_err("ioctl() failed\n");
return -1;
}
@@ -569,6 +372,7 @@ static int calc_evm_hash(const char *file, unsigned char *hash)
if (msize == 0) {
struct h_misc *hmac = (struct h_misc *)&hmac_misc;
+
hmac_size = sizeof(*hmac);
hmac->ino = st.st_ino;
hmac->generation = generation;
@@ -577,6 +381,7 @@ static int calc_evm_hash(const char *file, unsigned char *hash)
hmac->mode = st.st_mode;
} else if (msize == 64) {
struct h_misc_64 *hmac = (struct h_misc_64 *)&hmac_misc;
+
hmac_size = sizeof(*hmac);
hmac->ino = st.st_ino;
hmac->generation = generation;
@@ -585,6 +390,7 @@ static int calc_evm_hash(const char *file, unsigned char *hash)
hmac->mode = st.st_mode;
} else {
struct h_misc_32 *hmac = (struct h_misc_32 *)&hmac_misc;
+
hmac_size = sizeof(*hmac);
hmac->ino = st.st_ino;
hmac->generation = generation;
@@ -602,7 +408,7 @@ static int calc_evm_hash(const char *file, unsigned char *hash)
return 1;
}
- if (*uuid_str != '-') {
+ if (hmac_flags & HMAC_FLAG_UUID) {
err = get_uuid(&st, uuid);
if (err)
return -1;
@@ -626,7 +432,7 @@ static int calc_evm_hash(const char *file, unsigned char *hash)
static int sign_evm(const char *file, const char *key)
{
unsigned char hash[20];
- unsigned char sig[1024] = "\x03";
+ unsigned char sig[1024];
int len, err;
len = calc_evm_hash(file, hash);
@@ -637,8 +443,15 @@ static int sign_evm(const char *file, const char *key)
if (len <= 1)
return len;
+ /* add header */
+ len++;
+ sig[0] = EVM_IMA_XATTR_DIGSIG;
+
+ if (sigdump || params.verbose >= LOG_INFO)
+ dump(sig, len);
+
if (xattr) {
- err = lsetxattr(file, "security.evm", sig, len + 1, 0);
+ err = lsetxattr(file, "security.evm", sig, len, 0);
if (err < 0) {
log_err("setxattr failed: %s\n", file);
return err;
@@ -650,21 +463,33 @@ static int sign_evm(const char *file, const char *key)
static int hash_ima(const char *file)
{
- unsigned char hash[65] = "\x01"; /* MAX hash size + 1 */
- int len, err;
+ unsigned char hash[66]; /* MAX hash size + 2 */
+ int len, err, offset;
+ int algo = get_hash_algo(params.hash_algo);
+
+ if (algo > PKEY_HASH_SHA1) {
+ hash[0] = IMA_XATTR_DIGEST_NG;
+ hash[1] = algo;
+ offset = 2;
+ } else {
+ hash[0] = IMA_XATTR_DIGEST;
+ offset = 1;
+ }
- len = ima_calc_hash(file, hash + 1);
+ len = ima_calc_hash(file, hash + offset);
if (len <= 1)
return len;
+ len += offset;
+
if (params.verbose >= LOG_INFO)
log_info("hash: ");
if (sigdump || params.verbose >= LOG_INFO)
- dump(hash, len + 1);
+ dump(hash, len);
if (xattr) {
- err = lsetxattr(file, "security.ima", hash, len + 1, 0);
+ err = lsetxattr(file, "security.ima", hash, len, 0);
if (err < 0) {
log_err("setxattr failed: %s\n", file);
return err;
@@ -690,7 +515,7 @@ static int cmd_hash_ima(struct command *cmd)
static int sign_ima(const char *file, const char *key)
{
unsigned char hash[64];
- unsigned char sig[1024] = "\x03";
+ unsigned char sig[1024];
int len, err;
len = ima_calc_hash(file, hash);
@@ -703,6 +528,10 @@ static int sign_ima(const char *file, const char *key)
/* add header */
len++;
+ sig[0] = EVM_IMA_XATTR_DIGSIG;
+
+ if (sigdump || params.verbose >= LOG_INFO)
+ dump(sig, len);
if (sigfile)
bin2file(file, "sig", sig, len);
@@ -731,11 +560,13 @@ static int get_file_type(const char *path, const char *search_type)
dts |= DIR_MASK; break;
case 's':
dts |= BLK_MASK | CHR_MASK | LNK_MASK; break;
+ case 'x':
+ check_xattr = true; break;
case 'm':
/* stay within the same filesystem*/
err = lstat(path, &st);
if (err < 0) {
- log_err("stat() failed\n");
+ log_err("Failed to stat: %s\n", path);
return err;
}
fs_dev = st.st_dev; /* filesystem to start from */
@@ -882,12 +713,12 @@ static int verify_evm(const char *file)
len = lgetxattr(file, "security.evm", sig, sizeof(sig));
if (len < 0) {
- log_err("getxattr failed\n");
+ log_err("getxattr failed: %s\n", file);
return len;
}
if (sig[0] != 0x03) {
- log_err("security.evm has not signature\n");
+ log_err("security.evm has no signature\n");
return -1;
}
@@ -915,14 +746,14 @@ static int verify_ima(const char *file)
if (xattr) {
len = lgetxattr(file, "security.ima", sig, sizeof(sig));
if (len < 0) {
- log_err("getxattr failed\n");
+ log_err("getxattr failed: %s\n", file);
return len;
}
}
if (sigfile) {
- void *tmp;
- tmp = file2bin(file, "sig", &len);
+ void *tmp = file2bin(file, "sig", &len);
+
memcpy(sig, tmp, len);
free(tmp);
}
@@ -954,8 +785,8 @@ static int cmd_import(struct command *cmd)
inkey = g_argv[optind++];
if (!inkey) {
- inkey = x509 ? "/etc/keys/x509_evm.der" :
- "/etc/keys/pubkey_evm.pem";
+ inkey = params.x509 ? "/etc/keys/x509_evm.der" :
+ "/etc/keys/pubkey_evm.pem";
} else
ring = g_argv[optind++];
@@ -963,7 +794,11 @@ static int cmd_import(struct command *cmd)
if (ring) {
if (ring[0] != '@') {
- id = atoi(ring);
+ int base = 10;
+
+ if (ring[0] == '0' && ring[1] == 'x')
+ base = 16;
+ id = strtoul(ring, NULL, base);
} else {
if (strcmp(ring, "@t") == 0)
id = -1;
@@ -980,11 +815,11 @@ static int cmd_import(struct command *cmd)
}
}
- key = read_pub_key(inkey, x509);
+ key = read_pub_key(inkey, params.x509);
if (!key)
return 1;
- if (x509) {
+ if (params.x509) {
pub = file2bin(inkey, NULL, &len);
if (!pub)
goto out;
@@ -996,7 +831,7 @@ static int cmd_import(struct command *cmd)
log_info("Importing public key %s from file %s into keyring %d\n", name, inkey, id);
- id = add_key(x509 ? "asymmetric" : "user", x509 ? NULL : name, pub, len, id);
+ id = add_key(params.x509 ? "asymmetric" : "user", params.x509 ? NULL : name, pub, len, id);
if (id < 0) {
log_err("add_key failed\n");
err = id;
@@ -1004,7 +839,7 @@ static int cmd_import(struct command *cmd)
log_info("keyid: %d\n", id);
printf("%d\n", id);
}
- if (x509)
+ if (params.x509)
free(pub);
out:
RSA_free(key);
@@ -1032,12 +867,12 @@ static int calc_evm_hmac(const char *file, const char *keyfile, unsigned char *h
key = file2bin(keyfile, NULL, &keylen);
if (!key) {
- log_err("Unable to read a key: %s\n\n", keyfile);
+ log_err("Failed to read a key: %s\n", keyfile);
return -1;
}
if (keylen > sizeof(evmkey)) {
- log_err("key is too long\n");
+ log_err("key is too long: %d\n", keylen);
goto out;
}
@@ -1046,7 +881,7 @@ static int calc_evm_hmac(const char *file, const char *keyfile, unsigned char *h
memset(evmkey + keylen, 0, sizeof(evmkey) - keylen);
if (lstat(file, &st)) {
- log_err("lstat() failed\n");
+ log_err("Failed to stat: %s\n", file);
goto out;
}
@@ -1054,11 +889,12 @@ static int calc_evm_hmac(const char *file, const char *keyfile, unsigned char *h
/* we cannot at the momement to get generation of special files..
* kernel API does not support it */
int fd = open(file, 0);
+
if (fd < 0) {
- log_err("Unable to open %s\n", file);
+ log_err("Failed to open %s\n", file);
goto out;
}
- if (ioctl(fd, EXT34_IOC_GETVERSION, &generation)) {
+ if (ioctl(fd, FS_IOC_GETVERSION, &generation)) {
log_err("ioctl() failed\n");
goto out;
}
@@ -1069,7 +905,7 @@ static int calc_evm_hmac(const char *file, const char *keyfile, unsigned char *h
list_size = llistxattr(file, list, sizeof(list));
if (list_size <= 0) {
- log_err("llistxattr() failed\n");
+ log_err("llistxattr() failed: %s\n", file);
goto out;
}
@@ -1103,6 +939,7 @@ static int calc_evm_hmac(const char *file, const char *keyfile, unsigned char *h
if (msize == 0) {
struct h_misc *hmac = (struct h_misc *)&hmac_misc;
+
hmac_size = sizeof(*hmac);
hmac->ino = st.st_ino;
hmac->generation = generation;
@@ -1111,6 +948,7 @@ static int calc_evm_hmac(const char *file, const char *keyfile, unsigned char *h
hmac->mode = st.st_mode;
} else if (msize == 64) {
struct h_misc_64 *hmac = (struct h_misc_64 *)&hmac_misc;
+
hmac_size = sizeof(*hmac);
hmac->ino = st.st_ino;
hmac->generation = generation;
@@ -1119,6 +957,7 @@ static int calc_evm_hmac(const char *file, const char *keyfile, unsigned char *h
hmac->mode = st.st_mode;
} else {
struct h_misc_32 *hmac = (struct h_misc_32 *)&hmac_misc;
+
hmac_size = sizeof(*hmac);
hmac->ino = st.st_ino;
hmac->generation = generation;
@@ -1148,7 +987,7 @@ out:
static int hmac_evm(const char *file, const char *key)
{
unsigned char hash[20];
- unsigned char sig[1024] = "\x02";
+ unsigned char sig[1024];
int len, err;
len = calc_evm_hmac(file, key, hash);
@@ -1160,6 +999,7 @@ static int hmac_evm(const char *file, const char *key)
memcpy(sig + 1, hash, len);
if (xattr) {
+ sig[0] = EVM_XATTR_HMAC;
err = lsetxattr(file, "security.evm", sig, len + 1, 0);
if (err < 0) {
log_err("setxattr failed: %s\n", file);
@@ -1205,13 +1045,13 @@ static int ima_fix(const char *path)
log_info("%s\n", path);
- if (xattr) {
+ if (check_xattr) {
/* re-measuring takes a time
* in some cases we can skip labeling if xattrs exists
*/
size = llistxattr(path, list, sizeof(buf));
if (size < 0) {
- log_errno("llistxattr() failed: %s\n", path);
+ log_errno("Failed to read xattrs (llistxattr): %s\n", path);
return -1;
}
for (; size > 0; len++, size -= len, list += len) {
@@ -1227,7 +1067,7 @@ static int ima_fix(const char *path)
fd = open(path, O_RDONLY);
if (fd < 0) {
- log_errno("%s open failed", path);
+ log_errno("Failed to open file: %s", path);
return -1;
}
@@ -1244,8 +1084,9 @@ static int find(const char *path, int dts, find_cb_t func)
if (fs_dev) {
struct stat st;
int err = lstat(path, &st);
+
if (err < 0) {
- log_err("stat() failed\n");
+ log_err("Failed to stat: %s\n", path);
return err;
}
if (st.st_dev != fs_dev)
@@ -1254,12 +1095,12 @@ static int find(const char *path, int dts, find_cb_t func)
dir = opendir(path);
if (!dir) {
- log_err("Unable to open %s\n", path);
+ log_err("Failed to open directory %s\n", path);
return -1;
}
if (fchdir(dirfd(dir))) {
- log_err("Unable to chdir %s\n", path);
+ log_err("Failed to chdir %s\n", path);
return -1;
}
@@ -1274,7 +1115,7 @@ static int find(const char *path, int dts, find_cb_t func)
}
if (chdir("..")) {
- log_err("Unable to chdir %s\n", path);
+ log_err("Failed to chdir: %s\n", path);
return -1;
}
@@ -1290,7 +1131,6 @@ static int cmd_ima_fix(struct command *cmd)
{
char *path = g_argv[optind++];
int err, dts = REG_MASK; /* only regular files by default */
- struct stat st;
if (!path) {
log_err("Parameters missing\n");
@@ -1298,41 +1138,18 @@ static int cmd_ima_fix(struct command *cmd)
return -1;
}
- xattr = 0; /* do not check xattrs, fix everything */
-
- if (search_type) {
- int i;
-
- dts = 0;
- for (i = 0; search_type[i]; i++) {
- switch (search_type[i]) {
- case 'f':
- dts |= REG_MASK; break;
- case 'd':
- dts |= DIR_MASK; break;
- case 's':
- dts |= BLK_MASK | CHR_MASK | LNK_MASK; break;
- case 'x':
- /* check xattrs */
- xattr = 1; break;
- case 'm':
- /* stay within the same filesystem*/
- err = lstat(path, &st);
- if (err < 0) {
- log_err("stat() failed\n");
- return err;
- }
- fs_dev = st.st_dev; /* filesystem to start from */
- break;
- }
+ if (recursive) {
+ if (search_type) {
+ dts = get_file_type(path, search_type);
+ if (dts < 0)
+ return dts;
}
+ err = find(path, dts, ima_fix);
+ } else {
+ err = ima_fix(path);
}
- err = find(path, dts, ima_fix);
- if (err)
- return err;
-
- return 0;
+ return err;
}
@@ -1492,7 +1309,7 @@ static int ima_measurement(const char *file)
uint8_t pcr10[SHA_DIGEST_LENGTH];
struct template_entry entry = { .template = 0 };
FILE *fp;
- int err;
+ int err = -1;
memset(fox, 0xff, SHA_DIGEST_LENGTH);
@@ -1501,23 +1318,23 @@ static int ima_measurement(const char *file)
fp = fopen(file, "rb");
if (!fp) {
- log_err("Unable to open measurement file\n");
+ log_err("Failed to open measurement file: %s\n", file);
return -1;
}
- while ((err = fread(&entry.header, sizeof(entry.header), 1, fp))) {
+ while (fread(&entry.header, sizeof(entry.header), 1, fp)) {
ima_extend_pcr(pcr, entry.header.digest, SHA_DIGEST_LENGTH);
if (!fread(entry.name, entry.header.name_len, 1, fp)) {
log_err("Unable to read template name\n");
- return -1;
+ goto out;
}
entry.name[entry.header.name_len] = '\0';
if (!fread(&entry.template_len, sizeof(entry.template_len), 1, fp)) {
log_err("Unable to read template length\n");
- return -1;
+ goto out;
}
if (entry.template_buf_len < entry.template_len) {
@@ -1528,7 +1345,7 @@ static int ima_measurement(const char *file)
if (!fread(entry.template, entry.template_len, 1, fp)) {
log_err("Unable to read template\n");
- return -1;
+ goto out;
}
if (validate)
@@ -1540,8 +1357,6 @@ static int ima_measurement(const char *file)
ima_ng_show(&entry);
}
- fclose(fp);
-
tpm_pcr_read(10, pcr10, sizeof(pcr10));
log_info("PCRAgg: ");
@@ -1552,10 +1367,14 @@ static int ima_measurement(const char *file)
if (memcmp(pcr, pcr10, sizeof(pcr))) {
log_err("PCRAgg does not match PCR-10\n");
- return -1;
+ goto out;
}
- return 0;
+ err = 0;
+out:
+ fclose(fp);
+
+ return err;
}
static int cmd_ima_measurement(struct command *cmd)
@@ -1642,26 +1461,28 @@ static void usage(void)
printf(
"\n"
" -a, --hashalgo sha1 (default), sha224, sha256, sha384, sha512\n"
- " -s, --imasig also make IMA signature\n"
- " -d, --imahash also make IMA hash\n"
+ " -s, --imasig make IMA signature\n"
+ " -d, --imahash make IMA hash\n"
" -f, --sigfile store IMA signature in .sig file instead of xattr\n"
- " -1, --rsa signing key is in RSA DER format (signing v1)\n"
- " -k, --key path to signing key (default keys are /etc/keys/{privkey,pubkey}_evm.pem)\n"
+ " --rsa use RSA key type and signing scheme v1\n"
+ " -k, --key path to signing key (default: /etc/keys/{privkey,pubkey}_evm.pem)\n"
" -p, --pass password for encrypted signing key\n"
- " -u, --uuid use file system UUID in HMAC calculation (EVM v2)\n"
- " -t, --type file types to fix 'fdsxm' (f - file, d - directory, s - block/char/symlink)\n"
- " x - skip fixing if both ima and evm xattrs exist (caution: they may be wrong)\n"
+ " -r, --recursive recurse into directories (sign)\n"
+ " -t, --type file types to fix 'fdsxm' (f: file, d: directory, s: block/char/symlink)\n"
+ " x - skip fixing if both ima and evm xattrs exist (use with caution)\n"
" m - stay on the same filesystem (like 'find -xdev')\n"
" -n print result to stdout instead of setting xattr\n"
- " -r, --recursive recurse into directories (sign)\n"
- " --m32 force signature for 32 bit target system\n"
- " --m64 force signature for 32 bit target system\n"
+ " -u, --uuid use custom FS UUID for EVM (unspecified: from FS, empty: do not use)\n"
+ " --smack use extra SMACK xattrs for EVM\n"
+ " --m32 force EVM hmac/signature for 32 bit target system\n"
+ " --m64 force EVM hmac/signature for 64 bit target system\n"
" -v increase verbosity level\n"
" -h, --help display this help and exit\n"
"\n");
}
struct command cmds[] = {
+ {"--version", NULL, 0, ""},
{"help", cmd_help, 0, "<command>"},
{"import", cmd_import, 0, "[--rsa] pubkey keyring", "Import public key into the keyring.\n"},
{"sign", cmd_sign_evm, 0, "[-r] [--imahash | --imasig ] [--key key] [--pass password] file", "Sign file metadata.\n"},
@@ -1692,6 +1513,8 @@ static struct option opts[] = {
{"recursive", 0, 0, 'r'},
{"m32", 0, 0, '3'},
{"m64", 0, 0, '6'},
+ {"smack", 0, 0, 256},
+ {"version", 0, 0, 257},
{}
};
@@ -1731,17 +1554,21 @@ int main(int argc, char *argv[])
params.hash_algo = optarg;
break;
case 'p':
- keypass = optarg;
+ params.keypass = optarg;
break;
case 'f':
sigfile = 1;
xattr = 0;
break;
case 'u':
- uuid_str = optarg ?: "+";
+ uuid_str = optarg;
+ if (uuid_str)
+ hmac_flags |= HMAC_FLAG_UUID_SET;
+ else
+ hmac_flags &= ~HMAC_FLAG_UUID;
break;
case '1':
- x509 = 0;
+ params.x509 = 0;
break;
case 'k':
params.keyfile = optarg;
@@ -1758,6 +1585,13 @@ int main(int argc, char *argv[])
case '6':
msize = 64;
break;
+ case 256:
+ evm_config_xattrnames = evm_extra_smack_xattrs;
+ break;
+ case 257:
+ printf("evmctl %s\n", VERSION);
+ exit(0);
+ break;
case '?':
exit(1);
break;
@@ -1766,11 +1600,6 @@ int main(int argc, char *argv[])
}
}
- if (x509)
- sign_hash = sign_hash_v2;
- else
- sign_hash = sign_hash_v1;
-
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
@@ -1781,6 +1610,7 @@ int main(int argc, char *argv[])
if (err) {
unsigned long error;
+
if (errno)
log_err("errno: %s (%d)\n", strerror(errno), errno);
for (;;) {
diff --git a/src/imaevm.h b/src/imaevm.h
index 7dc2651..72de47a 100644
--- a/src/imaevm.h
+++ b/src/imaevm.h
@@ -41,6 +41,7 @@
#ifndef _LIBIMAEVM_H
#define _LIBIMAEVM_H
+#include <linux/fs.h>
#include <stdint.h>
#include <syslog.h>
#include <stdbool.h>
@@ -74,16 +75,15 @@
#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)
-
#define __packed __attribute__((packed))
+enum evm_ima_xattr_type {
+ IMA_XATTR_DIGEST = 0x01,
+ EVM_XATTR_HMAC,
+ EVM_IMA_XATTR_DIGSIG,
+ IMA_XATTR_DIGEST_NG,
+};
+
struct h_misc {
unsigned long ino;
uint32_t generation;
@@ -170,8 +170,10 @@ typedef int (*verify_hash_fn_t)(const unsigned char *hash, int size, unsigned ch
struct libevm_params {
int verbose;
+ int x509;
const char *hash_algo;
char *keyfile;
+ char *keypass;
};
struct RSA_ASN1_template {
@@ -189,6 +191,11 @@ int ima_calc_hash(const char *file, uint8_t *hash);
int get_hash_algo(const char *algo);
RSA *read_pub_key(const char *keyfile, int x509);
+void calc_keyid_v1(uint8_t *keyid, char *str, const unsigned char *pkey, int len);
+void calc_keyid_v2(uint32_t *keyid, char *str, RSA *key);
+int key2bin(RSA *key, unsigned char *pub);
+
+int sign_hash(const char *algo, const unsigned char *hash, int size, const char *keyfile, unsigned char *sig);
int verify_hash(const unsigned char *hash, int size, unsigned char *sig, int siglen);
int ima_verify_signature(const char *file, unsigned char *sig, int siglen);
diff --git a/src/libimaevm.c b/src/libimaevm.c
index 40b5229..2ce819f 100644
--- a/src/libimaevm.c
+++ b/src/libimaevm.c
@@ -44,6 +44,7 @@
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
+#include <asm/byteorder.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
@@ -125,6 +126,7 @@ const struct RSA_ASN1_template RSA_ASN1_templates[PKEY_HASH__LAST] = {
struct libevm_params params = {
.verbose = LOG_INFO - 1,
+ .x509 = 1,
.hash_algo = "sha1",
};
@@ -152,54 +154,54 @@ int get_filesize(const char *filename)
return (int)stats.st_size;
}
-static inline int get_fdsize(int fd)
+static inline off_t get_fdsize(int fd)
{
struct stat stats;
/* Need to know the file length */
fstat(fd, &stats);
- return (int)stats.st_size;
+ return stats.st_size;
}
static int add_file_hash(const char *file, EVP_MD_CTX *ctx)
{
uint8_t *data;
- int err, size, bs = DATA_SIZE;
- size_t len;
+ int err = -1, bs = DATA_SIZE;
+ off_t size, len;
FILE *fp;
- data = malloc(bs);
- if (!data) {
- log_err("malloc failed\n");
- return -1;
- }
-
fp = fopen(file, "r");
if (!fp) {
- log_err("Unable to open %s\n", file);
+ log_err("Failed to open: %s\n", file);
return -1;
}
+ data = malloc(bs);
+ if (!data) {
+ log_err("malloc failed\n");
+ goto out;
+ }
+
for (size = get_fdsize(fileno(fp)); size; size -= len) {
len = MIN(size, bs);
- err = fread(data, len, 1, fp);
- if (!err) {
+ if (!fread(data, len, 1, fp)) {
if (ferror(fp)) {
- log_err("fread() error\n\n");
- return -1;
+ log_err("fread() failed\n\n");
+ goto out;
}
break;
}
- err = EVP_DigestUpdate(ctx, data, len);
- if (!err) {
+ if (!EVP_DigestUpdate(ctx, data, len)) {
log_err("EVP_DigestUpdate() failed\n");
- return 1;
+ err = 1;
+ goto out;
}
}
-
+ err = 0;
+out:
fclose(fp);
free(data);
- return 0;
+ return err;
}
static int add_dir_hash(const char *file, EVP_MD_CTX *ctx)
@@ -212,7 +214,7 @@ static int add_dir_hash(const char *file, EVP_MD_CTX *ctx)
dir = opendir(file);
if (!dir) {
- log_err("Unable to open %s\n", file);
+ log_err("Failed to open: %s\n", file);
return -1;
}
@@ -255,22 +257,23 @@ static int add_dev_hash(struct stat *st, EVP_MD_CTX *ctx)
uint32_t dev = st->st_rdev;
unsigned major = (dev & 0xfff00) >> 8;
unsigned minor = (dev & 0xff) | ((dev >> 12) & 0xfff00);
+
log_info("device: %u:%u\n", major, minor);
return !EVP_DigestUpdate(ctx, &dev, sizeof(dev));
}
int ima_calc_hash(const char *file, uint8_t *hash)
{
+ const EVP_MD *md;
struct stat st;
EVP_MD_CTX ctx;
- const EVP_MD *md;
unsigned int mdlen;
int err;
/* Need to know the file length */
err = lstat(file, &st);
if (err < 0) {
- log_err("stat() failed\n");
+ log_err("Failed to stat: %s\n", file);
return err;
}
@@ -326,7 +329,7 @@ RSA *read_pub_key(const char *keyfile, int x509)
fp = fopen(keyfile, "r");
if (!fp) {
- log_err("Unable to open keyfile %s\n", keyfile);
+ log_err("Failed to open keyfile: %s\n", keyfile);
return NULL;
}
@@ -533,3 +536,258 @@ int ima_verify_signature(const char *file, unsigned char *sig, int siglen)
return verify_hash(hash, hashlen, sig + 1, siglen - 1);
}
+
+/*
+ * Create binary key representation suitable for kernel
+ */
+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);
+
+ 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;
+}
+
+void calc_keyid_v1(uint8_t *keyid, char *str, const unsigned char *pkey, int len)
+{
+ uint8_t sha1[SHA_DIGEST_LENGTH];
+ uint64_t id;
+
+ SHA1(pkey, len, sha1);
+
+ /* sha1[12 - 19] is exactly keyid from gpg file */
+ memcpy(keyid, sha1 + 12, 8);
+ log_debug("keyid: ");
+ log_debug_dump(keyid, 8);
+
+ id = __be64_to_cpup((__be64 *) keyid);
+ sprintf(str, "%llX", (unsigned long long)id);
+ log_info("keyid: %s\n", str);
+}
+
+void calc_keyid_v2(uint32_t *keyid, char *str, RSA *key)
+{
+ uint8_t sha1[SHA_DIGEST_LENGTH];
+ unsigned char *pkey = NULL;
+ int len;
+
+ len = i2d_RSAPublicKey(key, &pkey);
+
+ SHA1(pkey, len, sha1);
+
+ /* sha1[12 - 19] is exactly keyid from gpg file */
+ memcpy(keyid, sha1 + 16, 4);
+ log_debug("keyid: ");
+ log_debug_dump(keyid, 4);
+
+ sprintf(str, "%x", __be32_to_cpup(keyid));
+ log_info("keyid: %s\n", str);
+
+ free(pkey);
+}
+
+static RSA *read_priv_key(const char *keyfile, char *keypass)
+{
+ FILE *fp;
+ RSA *key;
+
+ fp = fopen(keyfile, "r");
+ if (!fp) {
+ log_err("Failed to open keyfile: %s\n", keyfile);
+ return NULL;
+ }
+ key = PEM_read_RSAPrivateKey(fp, NULL, NULL, keypass);
+ if (!key)
+ log_err("PEM_read_RSAPrivateKey() failed\n");
+
+ fclose(fp);
+ return key;
+}
+
+static int get_hash_algo_v1(const char *algo)
+{
+
+ if (!strcmp(algo, "sha1"))
+ return DIGEST_ALGO_SHA1;
+ else if (!strcmp(algo, "sha256"))
+ return DIGEST_ALGO_SHA256;
+
+ return -1;
+}
+
+int sign_hash_v1(const char *hashalgo, const unsigned char *hash, int size, const char *keyfile, unsigned char *sig)
+{
+ int len = -1, hashalgo_idx;
+ SHA_CTX ctx;
+ unsigned char pub[1024];
+ RSA *key;
+ char name[20];
+ unsigned char sighash[20];
+ struct signature_hdr *hdr;
+ uint16_t *blen;
+
+ if (!hash) {
+ log_err("sign_hash_v1: hash is null\n");
+ return -1;
+ }
+
+ if (size < 0) {
+ log_err("sign_hash_v1: size is negative: %d\n", size);
+ return -1;
+ }
+
+ if (!hashalgo) {
+ log_err("sign_hash_v1: hashalgo is null\n");
+ return -1;
+ }
+
+ if (!sig) {
+ log_err("sign_hash_v1: sig is null\n");
+ return -1;
+ }
+
+ log_info("hash: ");
+ log_dump(hash, size);
+
+ key = read_priv_key(keyfile, params.keypass);
+ if (!key)
+ return -1;
+
+ hdr = (struct signature_hdr *)sig;
+
+ /* now create a new hash */
+ hdr->version = (uint8_t) DIGSIG_VERSION_1;
+ hdr->timestamp = time(NULL);
+ hdr->algo = PUBKEY_ALGO_RSA;
+ hashalgo_idx = get_hash_algo_v1(hashalgo);
+ if (hashalgo_idx < 0) {
+ log_err("Signature version 1 does not support hash algo %s\n",
+ hashalgo);
+ goto out;
+ }
+ hdr->hash = (uint8_t) hashalgo_idx;
+
+ len = key2bin(key, pub);
+ calc_keyid_v1(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));
+
+ len = RSA_private_encrypt(sizeof(sighash), sighash, sig + sizeof(*hdr) + 2, key, RSA_PKCS1_PADDING);
+ if (len < 0) {
+ log_err("RSA_private_encrypt() failed: %d\n", len);
+ goto out;
+ }
+
+ /* 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);
+out:
+ RSA_free(key);
+ return len;
+}
+
+int sign_hash_v2(const char *algo, const unsigned char *hash, int size, const char *keyfile, unsigned char *sig)
+{
+ struct signature_v2_hdr *hdr;
+ int len = -1;
+ RSA *key;
+ char name[20];
+ unsigned char *buf;
+ const struct RSA_ASN1_template *asn1;
+
+ if (!hash) {
+ log_err("sign_hash_v2: hash is null\n");
+ return -1;
+ }
+
+ if (size < 0) {
+ log_err("sign_hash_v2: size is negative: %d\n", size);
+ return -1;
+ }
+
+ if (!sig) {
+ log_err("sign_hash_v2: sig is null\n");
+ return -1;
+ }
+
+ if (!algo) {
+ log_err("sign_hash_v2: algo is null\n");
+ return -1;
+ }
+
+ log_info("hash: ");
+ log_dump(hash, size);
+
+ key = read_priv_key(keyfile, params.keypass);
+ if (!key)
+ return -1;
+
+ hdr = (struct signature_v2_hdr *)sig;
+ hdr->version = (uint8_t) DIGSIG_VERSION_2;
+
+ hdr->hash_algo = get_hash_algo(algo);
+
+ calc_keyid_v2(&hdr->keyid, name, key);
+
+ asn1 = &RSA_ASN1_templates[hdr->hash_algo];
+
+ buf = malloc(size + asn1->size);
+ if (!buf)
+ goto out;
+
+ memcpy(buf, asn1->data, asn1->size);
+ memcpy(buf + asn1->size, hash, size);
+ len = RSA_private_encrypt(size + asn1->size, buf, hdr->sig,
+ key, RSA_PKCS1_PADDING);
+ if (len < 0) {
+ log_err("RSA_private_encrypt() failed: %d\n", len);
+ goto out;
+ }
+
+ /* we add bit length of the signature to make it gnupg compatible */
+ hdr->sig_size = __cpu_to_be16(len);
+ len += sizeof(*hdr);
+ log_info("evm/ima signature: %d bytes\n", len);
+out:
+ if (buf)
+ free(buf);
+ RSA_free(key);
+ return len;
+}
+
+int sign_hash(const char *hashalgo, const unsigned char *hash, int size, const char *keyfile, unsigned char *sig)
+{
+ return params.x509 ? sign_hash_v2(hashalgo, hash, size, keyfile, sig) :
+ sign_hash_v1(hashalgo, hash, size, keyfile, sig);
+}