diff options
author | Janusz Kozerski <j.kozerski@samsung.com> | 2014-10-02 14:23:32 +0200 |
---|---|---|
committer | Janusz Kozerski <j.kozerski@samsung.com> | 2014-10-02 14:59:52 +0200 |
commit | 47415fe6e82e2cd030a929bce08bf7113a0abb5d (patch) | |
tree | 8be142372e6fdac6224d03b3fe3a80c861464231 | |
parent | 944cf4990dcb61804d0c9a311985e532b5af2610 (diff) | |
parent | 3d9bdc1de282846de3523fd7a698d473304650b0 (diff) | |
download | ima-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-- | .gitignore | 5 | ||||
-rw-r--r-- | ChangeLog | 9 | ||||
-rw-r--r-- | Makefile.am | 23 | ||||
-rw-r--r-- | README | 436 | ||||
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | packaging/ima-evm-utils.spec | 9 | ||||
-rw-r--r-- | src/evmctl.c | 496 | ||||
-rw-r--r-- | src/imaevm.h | 23 | ||||
-rw-r--r-- | src/libimaevm.c | 306 |
9 files changed, 862 insertions, 447 deletions
@@ -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 @@ -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) @@ -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); +} |