1
0
mirror of https://git.code.sf.net/p/linux-ima/ima-evm-utils synced 2025-07-03 22:23:16 +02:00

51 Commits
v1.1 ... v1.2

Author SHA1 Message Date
515c99856e ima-evm-utils: Release version 1.2
Updated both the release and library (ABI change) versions.  See the
"Changelog" for a short list of the new features, bug fixes, and code
cleanup.

Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-26 07:42:17 -04:00
28cbfa1769 ima-evm-utils: functions/arrays define as static
Make sign_hash_v1(), sign_hash_v2(), get_hash_algo_by_id, and
 pkey_hash_algo[] and pkey_hash_algo_kern[] static.

Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-25 12:14:21 -04:00
c317d4618f ima-evm-utils: Namespace some too generic object names
Prefix `dump', `do_dump', and `params' with `imaevm_' to avoid colliding
with other global symbols.
Also, rename `libevm_' to `libimaevm_`, only used with `params'.
Additionally, rename `dump' into `hexdump'.
Finally, rename `get_hash_algo' to `imaevm_get_hash_algo' as suggested by
Mimi Zohar.

Lines that became too long are split, indent corrected. No code changes.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Reviewed-by: Bruno E. O. Meneguele <bmeneg@redhat.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-25 12:14:21 -04:00
a1b149bda4 ima-evm-utils: Show used hash algo in verbose mode
This could be useful for users.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-25 12:14:10 -04:00
25fce6e76a ima-evm-utils: Do not allow fallback and unknown hash algos
Falling back and permissiveness could have security implications.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-25 12:14:01 -04:00
31ceff7eb6 ima-evm-utils: use tsspcrread to read the TPM 2.0 PCRs
The kernel does not expose the crypto agile TPM 2.0 PCR banks to
userspace like it exposes PCRs for TPM 1.2.  As a result, a userspace
application is required to read PCRs.

This patch adds tsspcrread support for reading the TPM 2.0 PCRs.
tsspcrread is one application included in the ibmtss package.

Sample error messages:
Failed to read PCRs: (tsspcrread failed: No such file or directory)
Failed to read PCRs: (TSS_Dev_Open: Error opening /dev/tpmrm0)

Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
Reviewed-by: Petr Vorel <pvorel@suse.cz>
Reviewed-by: Bruno E. O. Meneguele <bmeneg@redhat.com>
2019-07-24 06:09:17 -04:00
340f7eb7dd ima-evm-utils: Remove ERR_load_crypto_strings from read_priv_pkey
ERR_load_crypto_strings() is already called in other place.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-23 20:27:41 -04:00
3eac3710a9 ima-evm-utils: log unknown keyid's as errors
Each tima a new unknown key is encountered, emit a message of the format
"key #: <keyid> (unknown keyid)".  The individual files using unknown
keys are then only logged in verbose mode.  Also update the message
emitted to be consistent with other "verification failed" messages.

Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>

Changlog:
- Incorporated Vitaly's fix to prevent a null dereference in `tail->next`
2019-07-23 20:24:58 -04:00
15afdbeae7 ima-evm-utils: Improve OpenSSL error reporting
Previously OpenSSL errors was delayed until evmctl exit (sometimes not).
Since we try to make libimaevm more robust, there could be many errors
accumulated, so it's useful to output OpenSSL errors as they happen.
This will also make output more understandable as you can see which
openssl error correspond to which libimaevm error.

Additionally, change spelling of read_pub_pkey and read_priv_pkey to
include key file name.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-18 21:09:32 -04:00
42d1636f52 ima-evm-utils: Remove not needed argument from verify_hash_v2
Since we now always call verify_hash_v2() with NULL keyfile (assuming
all keys are already loaded into public_keys list), remove keyfile
argument and its handling from verify_hash_v2().

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
[zohar@linux.ibm.com: make verify_hash_v1() and verify_hash_v2() static.]
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-18 21:09:32 -04:00
3359563dbe ima-evm-utils: Remove indirect call to subfunctions in verify_hash
This is more human understandable and also will improve handling of
the sources by cscope.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-18 21:09:32 -04:00
388f807a0f ima_evm_utils: erroneous "verification failed: 0 (invalid padding)" message
When public keys are specified on the boot command line (--key "<public
key file>,[<public key file>,...]"), the appropriate public key is used
to verify EVM or file signatures.  If no keys are specified, the default
x509_evm.der or pubkey_evm.pem file is used to verify the DIGSIG_VERSION_2
or DIGSIG_VERSION_1 signatures respectively, without first checking the
keyids.  Instead of emitting a "verification failed: 0 (invalid
padding)" message, an "unknown keyid" message would be clearer.

To address this problem, when no public keys are specified, this patch
loads the x509_evm.der default public key onto the "public_keys" list,
while the pubkey_evm.pem continues to be passed to verify_hash_v1()

Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-18 21:07:48 -04:00
a225728550 ima-evm-utils: Add more error checking in add_file_hash
Check return value of fstat(2) in add_file_hash() and remove
now unused get_fdsize().

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-16 14:14:10 -04:00
08a51e7460 ima-evm-utils: Fix file2bin stat and fopen relations
Check stat(2) return value, use fstat(2) to avoid race between
stat() and fopen(), remove now unused get_filesize().

Fixes: CID 229889.
Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-16 14:14:03 -04:00
9d52489bd3 ima-evm-utils: Fix memory leak in get_password
Free allocated password buffer when returning NULL.

Fixes: CID 229894 (partially).
Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-16 14:13:50 -04:00
4b7a74cc41 ima-evm-utils: Fix possible xattr_value overflows in calc_evm_hash
`selinux_str',`caps_str', and `ima_str' are passed from the command line
but copied into the fixed-size buffer.

Yes, length of `selinux_str' is calculated differently than of `caps_str'.

Fixes: CID 229895.
Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-16 14:13:39 -04:00
d47951c6e1 ima-evm-utils: Fix null dereference from file2bin to memcpy
file2bin() may return NULL, which is set to tmp, which is passed to
memcpy. Add explicit check for it.

Fixes: CID 229904.
Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-16 14:12:47 -04:00
164c51ff2b ima-evm-utils: support template "buf" field
Other than the "boot-aggregate" measurement entry in the IMA
measuremeent list, all other measurements are of file data.  Kernel
support was recently added to support measuring the kexec boot command
line buffer, which is stored in a new template field named 'buf'.

This patch adds support for a new template named "ima-buf", defined as
"d-ng|n-ng|buf".

Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 10:00:13 -04:00
4ec7c1d028 ima-evm-utils: limit "remain unprocessed data" messages
New, unknown template formats containing unknown fields are not
processed, resulting in "remain unprocessed data" messages.  Processing
these unknown fields is unnecessary for walking the measurement list to
re-calculate the PCRs.

The "remain unproccessed data" may also be emitted for malformed, known
template records.

This patch limits the number of messages emitted to once per template
format and includes the template name in the message.

Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 10:00:13 -04:00
40c842ace3 ima-evm-utils: Fix clang warning about possible unaligned pointer for hdr->keyid
Struct signature_v2_hdr is packed so clang complains that taking address
of packed member may result in an unaligned pointer value:

libimaevm.c:481:21: warning: taking address of packed member 'keyid' of class or structure 'signature_v2_hdr' may result in an unaligned pointer value
      [-Waddress-of-packed-member]
                                __be32_to_cpup(&hdr->keyid));
                                                ^~~~~~~~~~

libimaevm.c:905:17: warning: taking address of packed member 'keyid' of class or structure 'signature_v2_hdr' may result in an unaligned pointer value
      [-Waddress-of-packed-member]
        calc_keyid_v2(&hdr->keyid, name, pkey);
                       ^~~~~~~~~~

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 10:00:13 -04:00
ae57a3e1e4 ima-evm-utils: Allow multiple files in ima_verify
This allows testing multiple verify in a row, similar to ima_measurement.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 10:00:13 -04:00
556f2fc66d ima-evm-utils: Preload public keys for ima_verify
This allows testing verify_hash_v2() with multiple public keys.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 10:00:13 -04:00
bb35e696df ima-evm-utils: Fix memory leak in init_public_keys
strdup'ed string should be freed. Found with ASan.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 10:00:13 -04:00
faea7ca2b1 ima-evm-utils: Fix EVP_MD_CTX leak in ima_calc_hash
When pctx is allocated using EVP_MD_CTX_new() it should be freed.
Found with ASan.

Fixes: 81010f0 ("ima-evm-utils: Add backward compatible support for openssl 1.1")
Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 10:00:13 -04:00
30add9c2eb ima-evm-utils: Log hash_algo with hash value in verbose mode
It's useful to know not just a hash value but also which algorithm is
used.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 10:00:13 -04:00
5f126d1d25 ima-evm-utils: Pass status codes from sign and hash functions to the callers
Move sign_hash()/ima_calc_hash()/calc_evm_hmac()/calc_evm_hash() status
checking before assert()'ing of their return values, so it can be passed
to the upper level callers. Especially useful for showing errors.

Fixes: 1d9c279279 ("Define hash and sig buffer sizes and add asserts")
Fixes: 9643544701 ("Fix hash buffer overflow in verify_evm and hmac_evm")
Signed-off-by: Vitaly Chikunov <vt@altlinux.org>

ima-evm-utils: Fix assert after ima_calc_hash
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 10:00:13 -04:00
28d3a1b293 ima-evm-utils: Remove RSA_ASN1_templates
RSA_ASN1_templates[] are not needed anymore, because we switched to the
generic EVP_PKEY OpenSSL API to generate v2 signatures instead of
constructing PKCS1 ourselves.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Acked-by: Dmitry Kasatkin <dmitry.kasatkin@gmail.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 10:00:06 -04:00
13d4521dbf ima-evm-utils: Replace calc_keyid_v2 with calc_pkeyid_v2
Finish conversion of calc keyid v2 to EVP_PKEY API. After sign_hash_v2()
is switched to EVP_PKEY API (in previous commit), older RSA-specific
calc_keyid_v2() does not needed anymore and can be replaced with
calc_pkeyid_v2().

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Acked-by: Dmitry Kasatkin <dmitry.kasatkin@gmail.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 09:59:59 -04:00
07e623b608 ima-evm-utils: Convert sign_hash_v2 to EVP_PKEY API
Convert sign_hash_v2() to use more generic EVP_PKEY API instead of RSA
API. This enables generation of more signatures out of the box, such as
EC-RDSA (GOST) and any other that OpenSSL supports. This conversion also
fixes generation of MD4 signatures, because it didn't have proper
RSA_ASN1_template.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Acked-by: Dmitry Kasatkin <dmitry.kasatkin@gmail.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 09:59:53 -04:00
e0d778c608 ima-evm-utils: Convert verify_hash_v2 and find_keyid to EVP_PKEY API
Rely on OpenSSL API to verify v2 signatures instead of manual PKCS1
decoding. Also, convert find_keyid() to return EVP_PKEY because
verify_hash_v2() is sole user of it.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Acked-by: Dmitry Kasatkin <dmitry.kasatkin@gmail.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 09:59:45 -04:00
af4e3c06bb ima-evm-utils: Convert cmd_import and calc keyid v2 to EVP_PKEY API
Introduce calc_pkeyid_v2() (which accepts EVP_PKEY) to replace
calc_keyid_v2() (which accepts RSA) in the future and use it in
cmd_import().

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Acked-by: Dmitry Kasatkin <dmitry.kasatkin@gmail.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 09:59:14 -04:00
3df7b5d779 ima-evm-utils: Convert read_priv_key to EVP_PKEY API
Introduce read_priv_pkey() to read keys using EVP_PKEY, and change
read_priv_key() to be wrapper for it.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Acked-by: Dmitry Kasatkin <dmitry.kasatkin@gmail.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 09:59:07 -04:00
71c1be47e7 ima-evm-utils: Convert read_pub_key to EVP_PKEY API
Introduce read_pub_pkey() to read keys using EVP_PKEY, and change
read_pub_key() to be wrapper for it.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Acked-by: Dmitry Kasatkin <dmitry.kasatkin@gmail.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-07-08 09:58:49 -04:00
2308132957 ima-evm-utils: update .gitignore files
Signed-off-by: Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-05-23 10:07:27 -04:00
103b90d27c ima-evm-utils: include hash-info.gen into distribution
Include hash-info.gen into tarball and call it from the sourcedir to fix
out-of-tree build (and thus 'make distcheck').

Signed-off-by: Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-05-23 10:07:27 -04:00
8acbae598b ima-evm-utils: replace INCLUDES with AM_CPPFLAGS
Replace INCLUDES variable with AM_CPPFLAGS to stop Automake from warning
about deprecated variable usage.

Signed-off-by: Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-05-23 10:07:27 -04:00
b09a25690f ima-evm-utils: link to libcrypto instead of OpenSSL
There is no need to link to full libssl. evmctl uses functions from
libcrypto, so let's link only against that library.

Signed-off-by: Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-05-23 10:07:27 -04:00
782224f33c ima-evm-utils: Rework openssl init
Remove deprecated call to OpenSSL_add_all_algorithms().
Allow to disable openssl config loading by evmctl via configure
`--disable-openssl-conf' option. Show status of that in configure.
Move config loading from libimaevm to evmctl.
Finish engine initialization properly by calling ENGINE_set_default(),
as suggested by James Bottomley.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-05-21 10:41:29 -04:00
ebbfc41ad6 ima-evm-utils: try to load digest by its alias
Primary names of the algorithms are different for OpenSSL and Kernel.
"Streebog" is a name of the hash algorithm in the Kernel Crypto API.
"md_gost12_X" is the name used by most versions of OpenSSL, it's
placed in pkey_hash_algo[] so that algo IDs are resolved to them.
Allow to use both names.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
[zohar@linux.ibm.com: updated patch description based input from Vitaly]
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-05-21 10:41:02 -04:00
b628d8bfdb ima-evm-utils: Extract digest algorithms from hash_info.h
If configured with "--with-kernel-headers=PATH" try to extract hash
algorithms from "hash_info.h" from the kernel source tree or
kernel-headers package located in the specified path. (Otherwise, it
will be tried to get from the installed kernel.)

This also introduces two algorithm lists, one is built-in and another is
from the kernel source. (They should never contain conflicting algorithm
IDs by their append-only nature.) If the digest is not found in the
built-in list it will be searched in the list from kernel's
"hash_info.h".

This patch will allow evmctl to be just recompiled to work with digest
algorithms introduced in the newer kernels.

Suggested-by: Mimi Zohar <zohar@linux.ibm.com>
Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2019-04-03 16:47:37 -04:00
07d799cb6c ima-evm-utils: Preload OpenSSL engine via '--engine' option
Another method of using GOST algorithms (and cryptographic accelerators)
is via direct preloading of appropriate engine using '--engine' option.
For the gost-engine it should be '--engine gost'.

Usage example:

1. Install gost-engine appropriately. (No need to edit openssl.cnf).

2. Then GOST algorithms should work:

  # cp /dev/null a
  # evmctl -v ima_hash --engine gost -a streebog256 a
  hash: 04123f539a213e97c802cc229d474c6aa32a825a360b2a933a949fd925208d9ce1bb

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2018-12-03 06:08:36 -05:00
7e2a7840a7 ima-evm-utils: Allow using Streebog hash function
This patch will allow using GOST algorithms from OpenSSL's
gost-engine[1] via config extension (which is the usual way).

[1] https://github.com/gost-engine/engine

Full usage example:

1. Install the gost-engine package for your distro, this could be
libengine-gost-openssl1.1, openssl-gost-engine, or openssl-engines.

2. Edit openssl.cnf appropriately. Reference INSTALL.md of gost-engine
for the detailed instructions.

3. Then GOST algorithms should work:

  $ cp /dev/null a
  $ openssl dgst -streebog256 a
  md_gost12_256(a)= 3f539a213e97c802cc229d474c6aa32a825a360b2a933a949fd925208d9ce1bb
  $ evmctl -v ima_hash -a streebog256 --xattr-user a
  hash: 04123f539a213e97c802cc229d474c6aa32a825a360b2a933a949fd925208d9ce1bb
  $ getfattr -d -m. -ehex a
  # file: a
  user.ima=0x04123f539a213e97c802cc229d474c6aa32a825a360b2a933a949fd925208d9ce1bb

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2018-12-03 06:08:36 -05:00
b853b7ba93 ima-evm-utils: Define the '--xattr-user' option for testing
The IMA/EVM attributes are currently stored in the "security" namespace,
which requires root privileges. Storing the ima/evm attributes in the
"user" namespace, instead of the "security" namespace, would be useful
for debugging and testing purposes, and because "--sigfile" does not
work for evm signatures.

Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2018-12-03 06:08:36 -05:00
1d9c279279 ima-evm-utils: Define hash and sig buffer sizes and add asserts
To prevent hash and sig buffers size mismatch, define their maximum
sizes and add sanity checking asserts.

Suggested-by: Mimi Zohar <zohar@linux.ibm.com>
Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2018-12-03 06:08:36 -05:00
9643544701 ima-evm-utils: Fix hash buffer overflow in verify_evm and hmac_evm
Commit ae1319eeab ("Remove hardcoding of SHA1 in EVM signatures")
introduces overflow of 20 byte buffer on the stack while calculating
hash. Also, invalid hash length is passed to the underlying verification
function in verify_evm. This prevents any non-SHA1 hashes from being
properly validated using evmctl.

Fixes: ae1319eeab ("Remove hardcoding of SHA1 in EVM signatures")
Signed-off-by: Vitaly Chikunov <vt@altlinux.org>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2018-12-03 06:08:36 -05:00
15410695c7 ima-evm-utils: libimaevm: get key description out of verbose condition
Key description in keyring is being filled with memory garbage during
import process if the LOG_LEVEL is not satisfied (using '-vv').

Testing in kernels without trusted keyring support, and importing a v1
(RSA) key pair, the kernel fails to find the key since it looks for the
key description, which is not found due to this issue:

    "digsig: key not found, id: DD0558FEB7DDBD26"

Looking at:
    # keyctl show
    Session Keyring
     635748007 --alswrv      0     0  keyring: _ses
     673181018 --alswrv      0 65534   \_ keyring: _uid.0
     360651479 --alswrv      0     0       \_ keyring: _ima
     499360916 --alswrv      0     0       |   \_ user: .N=
     266933436 --alswrv      0     0       |   \_ user: B641632DA94DEE26

Key id 499360916 and 266933436 are both the same key, but the first was added
without '-vv' in the command line, while the second one was using it.

Signed-off-by: Bruno E. O. Meneguele <bmeneg@redhat.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2018-12-02 11:16:23 -05:00
8c8f29e870 ima-evm-utils: check the return code from tpm_pcr_read() in ima_measurement()
Don't log garbage when neither /sys/class/tpm/tpm0/device/pcrs nor
/sys/class/misc/tpm0/device/pcrs can be read and exit nicely with a
value of 1.

Signed-off-by: George Wilson <gcwilson@linux.ibm.com>
Signed-off-by: Mimi Zohar <zohar@linux.vnet.ibm.com>
2018-07-01 14:55:19 -04:00
9119f771a2 Add security.apparmor to the set of extended attributes used by EVM
The kernel is taking security.apparmor into account when validating EVM,
so evmctl should be doing the same.

Signed-off-by: Matthew Garrett <mjg59@google.com>
Signed-off-by: Mimi Zohar <zoahr@linux.vnet.ibm.com>

Changelog:
- Prevent compilation from failing due to XATTR_NAME_APPARMOR not being
included in the kernel-headers package.
2018-07-01 14:48:51 -04:00
ae1319eeab Remove hardcoding of SHA1 in EVM signatures
EVM signatures are always being generated with SHA1 even if the -a
argument has been provided to evmctl. Fix this so the provided hash
algorithm is used instead.

Signed-off-by: Matthew Garrett <mjg59@google.com>
Signed-off-by: Mimi Zohar <zohar@linux.vnet.ibm.com>

Changelog:
- Like for cmd_sign_hash() and sign_ima(), the hash size for sign_evm()
should now be 64.
2018-07-01 14:48:51 -04:00
6aea54d2ad evmctl: use correct include for xattr.h
The xattr API/ABI is provided by both the c-library, as well as by the
libattr package. The c-library's header file is sys/xattr.h, whereas
libattr's header file can be found in attr/xattr.h.

Given none of the code here *links* against the libattr.so shared library, it
is wrong to *compile* against libattr's API (header file).

Doing so avoids confusion as to which xattr.h is used as the least problem,
and potential ABI differences as the worst problem due the mismatching header
file used.

So make sure we compile and link against the same thing, the c-library in
both cases.

Signed-off-by: André Draszik <git@andred.net>
Signed-off-by: Mimi Zohar <zohar@linux.vnet.ibm.com>
2018-07-01 14:47:17 -04:00
5fa7d35de5 autotools: Try to find correct manpage stylesheet path
xslt docbook styles differ across distributions, which requires some
workarounds [1]. Try to check the manpage stylesheet path with
xmlcatalog and fallback to the original one.

Add option --with-xml-catalog to use non-default catalog.

+ remove trailing whitespace

[1] https://github.com/CESNET/ipfixcol/blob/master/base/m4/lbr_check_xsltproc.m4

Signed-off-by: Petr Vorel <pvorel@suse.cz>
Signed-off-by: Mimi Zohar <zohar@linux.vnet.ibm.com>
2018-05-07 07:51:32 -04:00
14 changed files with 828 additions and 361 deletions

1
.gitignore vendored
View File

@ -45,6 +45,7 @@ cscope.*
ncscope.* ncscope.*
# Generated documentation # Generated documentation
*.1
*.8 *.8
*.5 *.5
manpage.links manpage.links

View File

@ -1,3 +1,38 @@
2019-07-24 Mimi Zohar <zohar@linux.ibm.com>
version 1.2 new features:
* Generate EVM signatures based on the specified hash algorithm
* include "security.apparmor" in EVM signature
* Add support for writing & verifying "user.xxxx" xattrs for testing
* Support Strebog/Gost hash functions
* Add OpenSSL engine support
* Use of EVP_PKEY OpenSSL API to generate/verify v2 signatures
* Support verifying multiple signatures at once
* Support new template "buf" field and warn about other unknown fields
* Improve OpenSSL error reporting
* Support reading TPM 2.0 PCRs using tsspcrread
Bug fixes and code cleanup:
* Update manpage stylesheet detection
* Fix xattr.h include file
* On error when reading TPM PCRs, don't log gargabe
* Properly return keyid string to calc_keyid_v1/v2 callers, caused by
limiting keyid output to verbose mode
* Fix hash buffer overflow caused by EVM support for larger hashes,
defined MAX_DIGEST_SIZE and MAX_SIGNATURE_SIZE, and added "asserts".
* Linked with libcrypto instead of OpenSSL
* Updated Autotools, replacing INCLUDES with AM_CPPFLAGS
* Include new "hash-info.gen" in tar
* Log the hash algorithm, not just the hash value
* Fixed memory leaks in: EV_MD_CTX, init_public_keys
* Fixed other warnings/bugs discovered by clang, coverity
* Remove indirect calls in verify_hash() to improve code readability
* Don't fallback to using sha1
* Namespace some too generic object names
* Make functions/arrays static if possible
2018-01-28 Mimi Zohar <zohar@us.ibm.com> 2018-01-28 Mimi Zohar <zohar@us.ibm.com>
version 1.1 version 1.1

View File

@ -23,9 +23,6 @@ rpm: $(tarname)
cp $(tarname) $(SRCS)/ cp $(tarname) $(SRCS)/
rpmbuild -ba --nodeps $(SPEC) rpmbuild -ba --nodeps $(SPEC)
# requires asciidoc, xslproc, docbook-xsl
MANPAGE_DOCBOOK_XSL = /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl
evmctl.1.html: README evmctl.1.html: README
@asciidoc -o $@ $< @asciidoc -o $@ $<

2
README
View File

@ -44,6 +44,7 @@ OPTIONS
-s, --imasig make IMA signature -s, --imasig make IMA signature
-d, --imahash make IMA hash -d, --imahash make IMA hash
-f, --sigfile store IMA signature in .sig file instead of xattr -f, --sigfile store IMA signature in .sig file instead of xattr
--xattr-user store xattrs in user namespace (for testing purposes)
--rsa use RSA key type and signing scheme v1 --rsa use RSA key type and signing scheme v1
-k, --key path to signing key (default: /etc/keys/{privkey,pubkey}_evm.pem) -k, --key path to signing key (default: /etc/keys/{privkey,pubkey}_evm.pem)
-o, --portable generate portable EVM signatures -o, --portable generate portable EVM signatures
@ -57,6 +58,7 @@ OPTIONS
--smack use extra SMACK xattrs for EVM --smack use extra SMACK xattrs for EVM
--m32 force EVM hmac/signature for 32 bit target system --m32 force EVM hmac/signature for 32 bit target system
--m64 force EVM hmac/signature for 64 bit target system --m64 force EVM hmac/signature for 64 bit target system
--engine e preload OpenSSL engine e (such as: gost)
-v increase verbosity level -v increase verbosity level
-h, --help display this help and exit -h, --help display this help and exit

View File

@ -1,12 +1,13 @@
# autoconf script # autoconf script
AC_PREREQ([2.65]) AC_PREREQ([2.65])
AC_INIT(ima-evm-utils, 1.1, zohar@linux.vnet.ibm.com) AC_INIT(ima-evm-utils, 1.2, zohar@linux.ibm.com)
AM_INIT_AUTOMAKE AM_INIT_AUTOMAKE
AC_CONFIG_HEADERS([config.h]) AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_MACRO_DIR([m4])
AC_CANONICAL_HOST AC_CANONICAL_HOST
AC_USE_SYSTEM_EXTENSIONS
# Checks for programs. # Checks for programs.
AC_PROG_CC AC_PROG_CC
@ -24,15 +25,30 @@ LT_INIT
# Checks for header files. # Checks for header files.
AC_HEADER_STDC AC_HEADER_STDC
PKG_CHECK_MODULES(OPENSSL, [ openssl >= 0.9.8 ]) PKG_CHECK_MODULES(LIBCRYPTO, [libcrypto >= 0.9.8 ])
AC_SUBST(OPENSSL_CFLAGS) AC_SUBST(KERNEL_HEADERS)
AC_SUBST(OPENSSL_LIBS)
AC_CHECK_HEADER(unistd.h) AC_CHECK_HEADER(unistd.h)
AC_CHECK_HEADERS(openssl/conf.h) AC_CHECK_HEADERS(openssl/conf.h)
AC_CHECK_HEADERS(attr/xattr.h, , [AC_MSG_ERROR([attr/xattr.h header not found. You need the libattr development package.])]) AC_CHECK_PROG(TSSPCRREAD, [tsspcrread], yes, no)
if test "x$TSSPCRREAD" = "xyes"; then
AC_DEFINE(HAVE_TSSPCRREAD, 1, [Define to 1 if you have tsspcrread binary installed])],
fi
AC_CHECK_HEADERS(sys/xattr.h, , [AC_MSG_ERROR([sys/xattr.h header not found. You need the c-library development package.])])
AC_CHECK_HEADERS(keyutils.h, , [AC_MSG_ERROR([keyutils.h header not found. You need the libkeyutils development package.])]) AC_CHECK_HEADERS(keyutils.h, , [AC_MSG_ERROR([keyutils.h header not found. You need the libkeyutils development package.])])
AC_ARG_WITH(kernel_headers, [AS_HELP_STRING([--with-kernel-headers=PATH],
[specifies the Linux kernel-headers package location or kernel root directory you want to use])],
[KERNEL_HEADERS="$withval"],
[KERNEL_HEADERS=/lib/modules/$(uname -r)/source])
AC_ARG_ENABLE([openssl_conf],
[AS_HELP_STRING([--disable-openssl-conf], [disable loading of openssl config by evmctl])],
[if test "$enable_openssl_conf" = "no"; then
AC_DEFINE(DISABLE_OPENSSL_CONF, 1, [Define to disable loading of openssl config by evmctl.])
fi], [enable_openssl_conf=yes])
#debug support - yes for a while #debug support - yes for a while
PKG_ARG_ENABLE(debug, "yes", DEBUG, [Enable Debug support]) PKG_ARG_ENABLE(debug, "yes", DEBUG, [Enable Debug support])
if test $pkg_cv_enable_debug = yes; then if test $pkg_cv_enable_debug = yes; then
@ -41,6 +57,8 @@ else
CFLAGS="$CFLAGS -Wall -Wstrict-prototypes -pipe -fomit-frame-pointer" CFLAGS="$CFLAGS -Wall -Wstrict-prototypes -pipe -fomit-frame-pointer"
fi fi
EVMCTL_MANPAGE_DOCBOOK_XSL
# for gcov # for gcov
#CFLAGS="$CFLAGS -Wall -fprofile-arcs -ftest-coverage" #CFLAGS="$CFLAGS -Wall -fprofile-arcs -ftest-coverage"
#CXXFLAGS="$CXXFLAGS -Wall -fprofile-arcs -ftest-coverage" #CXXFLAGS="$CXXFLAGS -Wall -fprofile-arcs -ftest-coverage"
@ -58,5 +76,6 @@ echo
echo echo
echo "Configuration:" echo "Configuration:"
echo " debug: $pkg_cv_enable_debug" echo " debug: $pkg_cv_enable_debug"
echo " openssl-conf: $enable_openssl_conf"
echo " tsspcrread: $TSSPCRREAD"
echo echo

28
m4/manpage-docbook-xsl.m4 Normal file
View File

@ -0,0 +1,28 @@
dnl Copyright (c) 2018 Petr Vorel <pvorel@suse.cz>
dnl Find docbook manpage stylesheet
AC_DEFUN([EVMCTL_MANPAGE_DOCBOOK_XSL], [
AC_PATH_PROGS(XMLCATALOG, xmlcatalog)
AC_ARG_WITH([xml-catalog],
AC_HELP_STRING([--with-xml-catalog=CATALOG],
[path to xml catalog to use]),,
[with_xml_catalog=/etc/xml/catalog])
XML_CATALOG_FILE="$with_xml_catalog"
AC_SUBST([XML_CATALOG_FILE])
AC_MSG_CHECKING([for XML catalog ($XML_CATALOG_FILE)])
if test -f "$XML_CATALOG_FILE"; then
have_xmlcatalog_file=yes
AC_MSG_RESULT([found])
else
AC_MSG_RESULT([not found])
fi
if test "x${XMLCATALOG}" != "x" -a "x$have_xmlcatalog_file" = "xyes"; then
DOCBOOK_XSL_URI="http://docbook.sourceforge.net/release/xsl/current"
DOCBOOK_XSL_PATH="manpages/docbook.xsl"
MANPAGE_DOCBOOK_XSL=$(${XMLCATALOG} ${XML_CATALOG_FILE} ${DOCBOOK_XSL_URI}/${DOCBOOK_XSL_PATH} | sed -n 's|^file:/\+|/|p;q')
fi
if test "x${MANPAGE_DOCBOOK_XSL}" = "x"; then
MANPAGE_DOCBOOK_XSL="/usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl"
fi
AC_SUBST(MANPAGE_DOCBOOK_XSL)
])

View File

@ -1,5 +1,5 @@
Name: ima-evm-utils Name: ima-evm-utils
Version: 1.1 Version: 1.2
Release: 1%{?dist} Release: 1%{?dist}
Summary: ima-evm-utils - IMA/EVM control utility Summary: ima-evm-utils - IMA/EVM control utility
Group: System/Libraries Group: System/Libraries
@ -11,7 +11,6 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
BuildRequires: autoconf BuildRequires: autoconf
BuildRequires: automake BuildRequires: automake
BuildRequires: openssl-devel BuildRequires: openssl-devel
BuildRequires: libattr-devel
BuildRequires: keyutils-libs-devel BuildRequires: keyutils-libs-devel
%description %description

View File

@ -11,7 +11,6 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
BuildRequires: autoconf BuildRequires: autoconf
BuildRequires: automake BuildRequires: automake
BuildRequires: openssl-devel BuildRequires: openssl-devel
BuildRequires: libattr-devel
BuildRequires: keyutils-libs-devel BuildRequires: keyutils-libs-devel
%description %description

1
src/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
hash_info.h

View File

@ -1,22 +1,28 @@
lib_LTLIBRARIES = libimaevm.la lib_LTLIBRARIES = libimaevm.la
libimaevm_la_SOURCES = libimaevm.c libimaevm_la_SOURCES = libimaevm.c
libimaevm_la_CPPFLAGS = $(OPENSSL_CFLAGS) libimaevm_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBCRYPTO_CFLAGS)
# current[:revision[:age]] # current[:revision[:age]]
# result: [current-age].age.revision # result: [current-age].age.revision
libimaevm_la_LDFLAGS = -version-info 0:0:0 libimaevm_la_LDFLAGS = -version-info 1:0:0
libimaevm_la_LIBADD = $(OPENSSL_LIBS) libimaevm_la_LIBADD = $(LIBCRYPTO_LIBS)
include_HEADERS = imaevm.h include_HEADERS = imaevm.h
nodist_libimaevm_la_SOURCES = hash_info.h
BUILT_SOURCES = hash_info.h
EXTRA_DIST = hash_info.gen
hash_info.h: Makefile
$(srcdir)/hash_info.gen $(KERNEL_HEADERS) >$@
bin_PROGRAMS = evmctl bin_PROGRAMS = evmctl
evmctl_SOURCES = evmctl.c evmctl_SOURCES = evmctl.c
evmctl_CPPFLAGS = $(OPENSSL_CFLAGS) evmctl_CPPFLAGS = $(AM_CPPFLAGS) $(LIBCRYPTO_CFLAGS)
evmctl_LDFLAGS = $(LDFLAGS_READLINE) evmctl_LDFLAGS = $(LDFLAGS_READLINE)
evmctl_LDADD = $(OPENSSL_LIBS) -lkeyutils libimaevm.la evmctl_LDADD = $(LIBCRYPTO_LIBS) -lkeyutils libimaevm.la
INCLUDES = -I$(top_srcdir) -include config.h AM_CPPFLAGS = -I$(top_srcdir) -include config.h
CLEANFILES = hash_info.h
DISTCLEANFILES = @DISTCLEANFILES@ DISTCLEANFILES = @DISTCLEANFILES@

View File

@ -49,18 +49,25 @@
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include <dirent.h> #include <dirent.h>
#include <attr/xattr.h> #include <sys/xattr.h>
#include <linux/xattr.h> #include <linux/xattr.h>
#include <getopt.h> #include <getopt.h>
#include <keyutils.h> #include <keyutils.h>
#include <ctype.h> #include <ctype.h>
#include <termios.h> #include <termios.h>
#include <assert.h>
#include <openssl/sha.h> #include <openssl/sha.h>
#include <openssl/pem.h> #include <openssl/pem.h>
#include <openssl/hmac.h> #include <openssl/hmac.h>
#include <openssl/err.h> #include <openssl/err.h>
#include <openssl/rsa.h> #include <openssl/rsa.h>
#include <openssl/engine.h>
#ifndef XATTR_APPAARMOR_SUFFIX
#define XATTR_APPARMOR_SUFFIX "apparmor"
#define XATTR_NAME_APPARMOR XATTR_SECURITY_PREFIX XATTR_APPARMOR_SUFFIX
#endif
#define USE_FPRINTF #define USE_FPRINTF
@ -69,6 +76,7 @@
static char *evm_default_xattrs[] = { static char *evm_default_xattrs[] = {
XATTR_NAME_SELINUX, XATTR_NAME_SELINUX,
XATTR_NAME_SMACK, XATTR_NAME_SMACK,
XATTR_NAME_APPARMOR,
XATTR_NAME_IMA, XATTR_NAME_IMA,
XATTR_NAME_CAPS, XATTR_NAME_CAPS,
NULL NULL
@ -80,6 +88,7 @@ static char *evm_extra_smack_xattrs[] = {
XATTR_NAME_SMACKEXEC, XATTR_NAME_SMACKEXEC,
XATTR_NAME_SMACKTRANSMUTE, XATTR_NAME_SMACKTRANSMUTE,
XATTR_NAME_SMACKMMAP, XATTR_NAME_SMACKMMAP,
XATTR_NAME_APPARMOR,
XATTR_NAME_IMA, XATTR_NAME_IMA,
XATTR_NAME_CAPS, XATTR_NAME_CAPS,
NULL NULL
@ -137,6 +146,9 @@ static int find(const char *path, int dts, find_cb_t func);
struct command cmds[]; struct command cmds[];
static void print_usage(struct command *cmd); static void print_usage(struct command *cmd);
static const char *xattr_ima = "security.ima";
static const char *xattr_evm = "security.evm";
static int bin2file(const char *file, const char *ext, const unsigned char *data, int len) static int bin2file(const char *file, const char *ext, const unsigned char *data, int len)
{ {
FILE *fp; FILE *fp;
@ -163,9 +175,10 @@ static int bin2file(const char *file, const char *ext, const unsigned char *data
static unsigned char *file2bin(const char *file, const char *ext, int *size) static unsigned char *file2bin(const char *file, const char *ext, int *size)
{ {
FILE *fp; FILE *fp;
int len; size_t len;
unsigned char *data; unsigned char *data;
char name[strlen(file) + (ext ? strlen(ext) : 0) + 2]; char name[strlen(file) + (ext ? strlen(ext) : 0) + 2];
struct stat stats;
if (ext) if (ext)
sprintf(name, "%s.%s", file, ext); sprintf(name, "%s.%s", file, ext);
@ -174,18 +187,33 @@ static unsigned char *file2bin(const char *file, const char *ext, int *size)
log_info("Reading to %s\n", name); log_info("Reading to %s\n", name);
len = get_filesize(name);
fp = fopen(name, "r"); fp = fopen(name, "r");
if (!fp) { if (!fp) {
log_err("Failed to open: %s\n", name); log_err("Failed to open: %s\n", name);
return NULL; return NULL;
} }
if (fstat(fileno(fp), &stats) == -1) {
log_err("Failed to fstat: %s (%s)\n", name, strerror(errno));
fclose(fp);
return NULL;
}
len = stats.st_size;
data = malloc(len); data = malloc(len);
if (!fread(data, len, 1, fp)) if (!data) {
len = 0; log_err("Failed to malloc %zu bytes: %s\n", len, name);
fclose(fp);
return NULL;
}
if (fread(data, len, 1, fp) != len) {
log_err("Failed to fread %zu bytes: %s\n", len, name);
fclose(fp);
free(data);
return NULL;
}
fclose(fp); fclose(fp);
*size = len; *size = (int)len;
return data; return data;
} }
@ -313,6 +341,7 @@ err:
static int calc_evm_hash(const char *file, unsigned char *hash) static int calc_evm_hash(const char *file, unsigned char *hash)
{ {
const EVP_MD *md;
struct stat st; struct stat st;
int err; int err;
uint32_t generation = 0; uint32_t generation = 0;
@ -374,7 +403,14 @@ static int calc_evm_hash(const char *file, unsigned char *hash)
return -1; return -1;
} }
err = EVP_DigestInit(pctx, EVP_sha1()); md = EVP_get_digestbyname(imaevm_params.hash_algo);
if (!md) {
log_err("EVP_get_digestbyname(%s) failed\n",
imaevm_params.hash_algo);
return 1;
}
err = EVP_DigestInit(pctx, md);
if (!err) { if (!err) {
log_err("EVP_DigestInit() failed\n"); log_err("EVP_DigestInit() failed\n");
return 1; return 1;
@ -382,16 +418,31 @@ static int calc_evm_hash(const char *file, unsigned char *hash)
for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) { for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) {
if (!strcmp(*xattrname, XATTR_NAME_SELINUX) && selinux_str) { if (!strcmp(*xattrname, XATTR_NAME_SELINUX) && selinux_str) {
strcpy(xattr_value, selinux_str);
err = strlen(selinux_str) + 1; err = strlen(selinux_str) + 1;
if (err > sizeof(xattr_value)) {
log_err("selinux[%u] value is too long to fit into xattr[%zu]\n",
err, sizeof(xattr_value));
return -1;
}
strcpy(xattr_value, selinux_str);
} else if (!strcmp(*xattrname, XATTR_NAME_IMA) && ima_str) { } else if (!strcmp(*xattrname, XATTR_NAME_IMA) && ima_str) {
hex2bin(xattr_value, ima_str, strlen(ima_str) / 2);
err = strlen(ima_str) / 2; err = strlen(ima_str) / 2;
if (err > sizeof(xattr_value)) {
log_err("ima[%u] value is too long to fit into xattr[%zu]\n",
err, sizeof(xattr_value));
return -1;
}
hex2bin(xattr_value, ima_str, err);
} else if (!strcmp(*xattrname, XATTR_NAME_CAPS) && (hmac_flags & HMAC_FLAG_CAPS_SET)) { } else if (!strcmp(*xattrname, XATTR_NAME_CAPS) && (hmac_flags & HMAC_FLAG_CAPS_SET)) {
if (!caps_str) if (!caps_str)
continue; continue;
strcpy(xattr_value, caps_str);
err = strlen(caps_str); err = strlen(caps_str);
if (err >= sizeof(xattr_value)) {
log_err("caps[%u] value is too long to fit into xattr[%zu]\n",
err + 1, sizeof(xattr_value));
return -1;
}
strcpy(xattr_value, caps_str);
} else { } else {
err = lgetxattr(file, *xattrname, xattr_value, sizeof(xattr_value)); err = lgetxattr(file, *xattrname, xattr_value, sizeof(xattr_value));
if (err < 0) { if (err < 0) {
@ -490,17 +541,19 @@ static int calc_evm_hash(const char *file, unsigned char *hash)
static int sign_evm(const char *file, const char *key) static int sign_evm(const char *file, const char *key)
{ {
unsigned char hash[20]; unsigned char hash[MAX_DIGEST_SIZE];
unsigned char sig[1024]; unsigned char sig[MAX_SIGNATURE_SIZE];
int len, err; int len, err;
len = calc_evm_hash(file, hash); len = calc_evm_hash(file, hash);
if (len <= 1) if (len <= 1)
return len; return len;
assert(len <= sizeof(hash));
len = sign_hash("sha1", hash, len, key, NULL, sig + 1); len = sign_hash(imaevm_params.hash_algo, hash, len, key, NULL, sig + 1);
if (len <= 1) if (len <= 1)
return len; return len;
assert(len < sizeof(sig));
/* add header */ /* add header */
len++; len++;
@ -512,11 +565,11 @@ static int sign_evm(const char *file, const char *key)
if (evm_immutable) if (evm_immutable)
sig[1] = 3; /* immutable signature version */ sig[1] = 3; /* immutable signature version */
if (sigdump || params.verbose >= LOG_INFO) if (sigdump || imaevm_params.verbose >= LOG_INFO)
dump(sig, len); imaevm_hexdump(sig, len);
if (xattr) { if (xattr) {
err = lsetxattr(file, "security.evm", sig, len, 0); err = lsetxattr(file, xattr_evm, sig, len, 0);
if (err < 0) { if (err < 0) {
log_err("setxattr failed: %s\n", file); log_err("setxattr failed: %s\n", file);
return err; return err;
@ -528,10 +581,14 @@ static int sign_evm(const char *file, const char *key)
static int hash_ima(const char *file) static int hash_ima(const char *file)
{ {
unsigned char hash[66]; /* MAX hash size + 2 */ unsigned char hash[MAX_DIGEST_SIZE + 2]; /* +2 byte xattr header */
int len, err, offset; int len, err, offset;
int algo = get_hash_algo(params.hash_algo); int algo = imaevm_get_hash_algo(imaevm_params.hash_algo);
if (algo < 0) {
log_err("Unknown hash algo: %s\n", imaevm_params.hash_algo);
return -1;
}
if (algo > PKEY_HASH_SHA1) { if (algo > PKEY_HASH_SHA1) {
hash[0] = IMA_XATTR_DIGEST_NG; hash[0] = IMA_XATTR_DIGEST_NG;
hash[1] = algo; hash[1] = algo;
@ -544,17 +601,18 @@ static int hash_ima(const char *file)
len = ima_calc_hash(file, hash + offset); len = ima_calc_hash(file, hash + offset);
if (len <= 1) if (len <= 1)
return len; return len;
assert(len + offset <= sizeof(hash));
len += offset; len += offset;
if (params.verbose >= LOG_INFO) if (imaevm_params.verbose >= LOG_INFO)
log_info("hash: "); log_info("hash(%s): ", imaevm_params.hash_algo);
if (sigdump || params.verbose >= LOG_INFO) if (sigdump || imaevm_params.verbose >= LOG_INFO)
dump(hash, len); imaevm_hexdump(hash, len);
if (xattr) { if (xattr) {
err = lsetxattr(file, "security.ima", hash, len, 0); err = lsetxattr(file, xattr_ima, hash, len, 0);
if (err < 0) { if (err < 0) {
log_err("setxattr failed: %s\n", file); log_err("setxattr failed: %s\n", file);
return err; return err;
@ -566,30 +624,32 @@ static int hash_ima(const char *file)
static int sign_ima(const char *file, const char *key) static int sign_ima(const char *file, const char *key)
{ {
unsigned char hash[64]; unsigned char hash[MAX_DIGEST_SIZE];
unsigned char sig[1024]; unsigned char sig[MAX_SIGNATURE_SIZE];
int len, err; int len, err;
len = ima_calc_hash(file, hash); len = ima_calc_hash(file, hash);
if (len <= 1) if (len <= 1)
return len; return len;
assert(len <= sizeof(hash));
len = sign_hash(params.hash_algo, hash, len, key, NULL, sig + 1); len = sign_hash(imaevm_params.hash_algo, hash, len, key, NULL, sig + 1);
if (len <= 1) if (len <= 1)
return len; return len;
assert(len < sizeof(sig));
/* add header */ /* add header */
len++; len++;
sig[0] = EVM_IMA_XATTR_DIGSIG; sig[0] = EVM_IMA_XATTR_DIGSIG;
if (sigdump || params.verbose >= LOG_INFO) if (sigdump || imaevm_params.verbose >= LOG_INFO)
dump(sig, len); imaevm_hexdump(sig, len);
if (sigfile) if (sigfile)
bin2file(file, "sig", sig, len); bin2file(file, "sig", sig, len);
if (xattr) { if (xattr) {
err = lsetxattr(file, "security.ima", sig, len, 0); err = lsetxattr(file, xattr_ima, sig, len, 0);
if (err < 0) { if (err < 0) {
log_err("setxattr failed: %s\n", file); log_err("setxattr failed: %s\n", file);
return err; return err;
@ -663,7 +723,7 @@ static int sign_ima_file(const char *file)
{ {
const char *key; const char *key;
key = params.keyfile ? : "/etc/keys/privkey_evm.pem"; key = imaevm_params.keyfile ? : "/etc/keys/privkey_evm.pem";
return sign_ima(file, key); return sign_ima(file, key);
} }
@ -680,11 +740,11 @@ static int cmd_sign_hash(struct command *cmd)
int hashlen = 0; int hashlen = 0;
size_t line_len; size_t line_len;
ssize_t len; ssize_t len;
unsigned char hash[64]; unsigned char hash[MAX_DIGEST_SIZE];
unsigned char sig[1024] = "\x03"; unsigned char sig[MAX_SIGNATURE_SIZE] = "\x03";
int siglen; int siglen;
key = params.keyfile ? : "/etc/keys/privkey_evm.pem"; key = imaevm_params.keyfile ? : "/etc/keys/privkey_evm.pem";
/* support reading hash (eg. output of shasum) */ /* support reading hash (eg. output of shasum) */
while ((len = getline(&line, &line_len, stdin)) > 0) { while ((len = getline(&line, &line_len, stdin)) > 0) {
@ -696,11 +756,13 @@ static int cmd_sign_hash(struct command *cmd)
token = strpbrk(line, ", \t"); token = strpbrk(line, ", \t");
hashlen = token ? token - line : strlen(line); hashlen = token ? token - line : strlen(line);
hex2bin(hash, line, hashlen); assert(hashlen / 2 <= sizeof(hash));
siglen = sign_hash(params.hash_algo, hash, hashlen/2, hex2bin(hash, line, hashlen / 2);
siglen = sign_hash(imaevm_params.hash_algo, hash, hashlen / 2,
key, NULL, sig + 1); key, NULL, sig + 1);
if (siglen <= 1) if (siglen <= 1)
return siglen; return siglen;
assert(siglen < sizeof(sig));
fwrite(line, len, 1, stdout); fwrite(line, len, 1, stdout);
fprintf(stdout, " "); fprintf(stdout, " ");
@ -722,7 +784,7 @@ static int sign_evm_path(const char *file)
const char *key; const char *key;
int err; int err;
key = params.keyfile ? : "/etc/keys/privkey_evm.pem"; key = imaevm_params.keyfile ? : "/etc/keys/privkey_evm.pem";
if (digsig) { if (digsig) {
err = sign_ima(file, key); err = sign_ima(file, key);
@ -746,26 +808,28 @@ static int cmd_sign_evm(struct command *cmd)
static int verify_evm(const char *file) static int verify_evm(const char *file)
{ {
unsigned char hash[20]; unsigned char hash[MAX_DIGEST_SIZE];
unsigned char sig[1024]; unsigned char sig[MAX_SIGNATURE_SIZE];
int mdlen;
int len; int len;
len = calc_evm_hash(file, hash); mdlen = calc_evm_hash(file, hash);
if (len <= 1) if (mdlen <= 1)
return len; return mdlen;
assert(mdlen <= sizeof(hash));
len = lgetxattr(file, "security.evm", sig, sizeof(sig)); len = lgetxattr(file, xattr_evm, sig, sizeof(sig));
if (len < 0) { if (len < 0) {
log_err("getxattr failed: %s\n", file); log_err("getxattr failed: %s\n", file);
return len; return len;
} }
if (sig[0] != 0x03) { if (sig[0] != 0x03) {
log_err("security.evm has no signature\n"); log_err("%s has no signature\n", xattr_evm);
return -1; return -1;
} }
return verify_hash(file, hash, sizeof(hash), sig + 1, len - 1); return verify_hash(file, hash, mdlen, sig + 1, len - 1);
} }
static int cmd_verify_evm(struct command *cmd) static int cmd_verify_evm(struct command *cmd)
@ -779,24 +843,38 @@ static int cmd_verify_evm(struct command *cmd)
return -1; return -1;
} }
if (imaevm_params.keyfile) /* Support multiple public keys */
init_public_keys(imaevm_params.keyfile);
else /* assume read pubkey from x509 cert */
init_public_keys("/etc/keys/x509_evm.der");
err = verify_evm(file); err = verify_evm(file);
if (!err && params.verbose >= LOG_INFO) if (!err && imaevm_params.verbose >= LOG_INFO)
log_info("%s: verification is OK\n", file); log_info("%s: verification is OK\n", file);
return err; return err;
} }
static int verify_ima(const char *file) static int verify_ima(const char *file)
{ {
unsigned char sig[1024]; unsigned char sig[MAX_SIGNATURE_SIZE];
int len; int len;
if (sigfile) { if (sigfile) {
void *tmp = file2bin(file, "sig", &len); void *tmp = file2bin(file, "sig", &len);
if (!tmp) {
log_err("Failed reading: %s\n", file);
return -1;
}
if (len > sizeof(sig)) {
log_err("Signature file is too big: %s\n", file);
free(tmp);
return -1;
}
memcpy(sig, tmp, len); memcpy(sig, tmp, len);
free(tmp); free(tmp);
} else { } else {
len = lgetxattr(file, "security.ima", sig, sizeof(sig)); len = lgetxattr(file, xattr_ima, sig, sizeof(sig));
if (len < 0) { if (len < 0) {
log_err("getxattr failed: %s\n", file); log_err("getxattr failed: %s\n", file);
return len; return len;
@ -811,6 +889,11 @@ static int cmd_verify_ima(struct command *cmd)
char *file = g_argv[optind++]; char *file = g_argv[optind++];
int err; int err;
if (imaevm_params.keyfile) /* Support multiple public keys */
init_public_keys(imaevm_params.keyfile);
else /* assume read pubkey from x509 cert */
init_public_keys("/etc/keys/x509_evm.der");
errno = 0; errno = 0;
if (!file) { if (!file) {
log_err("Parameters missing\n"); log_err("Parameters missing\n");
@ -818,9 +901,11 @@ static int cmd_verify_ima(struct command *cmd)
return -1; return -1;
} }
do {
err = verify_ima(file); err = verify_ima(file);
if (!err && params.verbose >= LOG_INFO) if (!err && imaevm_params.verbose >= LOG_INFO)
log_info("%s: verification is OK\n", file); log_info("%s: verification is OK\n", file);
} while ((file = g_argv[optind++]));
return err; return err;
} }
@ -833,15 +918,15 @@ static int cmd_convert(struct command *cmd)
uint8_t keyid[8]; uint8_t keyid[8];
RSA *key; RSA *key;
params.x509 = 0; imaevm_params.x509 = 0;
inkey = g_argv[optind++]; inkey = g_argv[optind++];
if (!inkey) { if (!inkey) {
inkey = params.x509 ? "/etc/keys/x509_evm.der" : inkey = imaevm_params.x509 ? "/etc/keys/x509_evm.der" :
"/etc/keys/pubkey_evm.pem"; "/etc/keys/pubkey_evm.pem";
} }
key = read_pub_key(inkey, params.x509); key = read_pub_key(inkey, imaevm_params.x509);
if (!key) if (!key)
return 1; return 1;
@ -862,11 +947,10 @@ static int cmd_import(struct command *cmd)
int id, len, err = 0; int id, len, err = 0;
char name[20]; char name[20];
uint8_t keyid[8]; uint8_t keyid[8];
RSA *key;
inkey = g_argv[optind++]; inkey = g_argv[optind++];
if (!inkey) { if (!inkey) {
inkey = params.x509 ? "/etc/keys/x509_evm.der" : inkey = imaevm_params.x509 ? "/etc/keys/x509_evm.der" :
"/etc/keys/pubkey_evm.pem"; "/etc/keys/pubkey_evm.pem";
} else } else
ring = g_argv[optind++]; ring = g_argv[optind++];
@ -896,23 +980,32 @@ static int cmd_import(struct command *cmd)
} }
} }
key = read_pub_key(inkey, params.x509); if (imaevm_params.x509) {
EVP_PKEY *pkey = read_pub_pkey(inkey, imaevm_params.x509);
if (!pkey)
return 1;
pub = file2bin(inkey, NULL, &len);
if (!pub) {
EVP_PKEY_free(pkey);
return 1;
}
calc_keyid_v2((uint32_t *)keyid, name, pkey);
EVP_PKEY_free(pkey);
} else {
RSA *key = read_pub_key(inkey, imaevm_params.x509);
if (!key) if (!key)
return 1; return 1;
if (params.x509) {
pub = file2bin(inkey, NULL, &len);
if (!pub)
goto out;
calc_keyid_v2((uint32_t *)keyid, name, key);
} else {
len = key2bin(key, pub); len = key2bin(key, pub);
calc_keyid_v1(keyid, name, pub, len); calc_keyid_v1(keyid, name, pub, len);
RSA_free(key);
} }
log_info("Importing public key %s from file %s into keyring %d\n", name, inkey, id); log_info("Importing public key %s from file %s into keyring %d\n", name, inkey, id);
id = add_key(params.x509 ? "asymmetric" : "user", params.x509 ? NULL : name, pub, len, id); id = add_key(imaevm_params.x509 ? "asymmetric" : "user",
imaevm_params.x509 ? NULL : name, pub, len, id);
if (id < 0) { if (id < 0) {
log_err("add_key failed\n"); log_err("add_key failed\n");
err = id; err = id;
@ -920,10 +1013,8 @@ static int cmd_import(struct command *cmd)
log_info("keyid: %d\n", id); log_info("keyid: %d\n", id);
printf("%d\n", id); printf("%d\n", id);
} }
if (params.x509) if (imaevm_params.x509)
free(pub); free(pub);
out:
RSA_free(key);
return err; return err;
} }
@ -939,7 +1030,7 @@ static int setxattr_ima(const char *file, char *sig_file)
if (!sig) if (!sig)
return 0; return 0;
err = lsetxattr(file, "security.ima", sig, len, 0); err = lsetxattr(file, xattr_ima, sig, len, 0);
if (err < 0) if (err < 0)
log_err("setxattr failed: %s\n", file); log_err("setxattr failed: %s\n", file);
free(sig); free(sig);
@ -967,6 +1058,7 @@ static int cmd_setxattr_ima(struct command *cmd)
static int calc_evm_hmac(const char *file, const char *keyfile, unsigned char *hash) static int calc_evm_hmac(const char *file, const char *keyfile, unsigned char *hash)
{ {
const EVP_MD *md;
struct stat st; struct stat st;
int err = -1; int err = -1;
uint32_t generation = 0; uint32_t generation = 0;
@ -1033,7 +1125,14 @@ static int calc_evm_hmac(const char *file, const char *keyfile, unsigned char *h
goto out; goto out;
} }
err = !HMAC_Init_ex(pctx, evmkey, sizeof(evmkey), EVP_sha1(), NULL); md = EVP_get_digestbyname(imaevm_params.hash_algo);
if (!md) {
log_err("EVP_get_digestbyname(%s) failed\n",
imaevm_params.hash_algo);
goto out;
}
err = !HMAC_Init_ex(pctx, evmkey, sizeof(evmkey), md, NULL);
if (err) { if (err) {
log_err("HMAC_Init() failed\n"); log_err("HMAC_Init() failed\n");
goto out; goto out;
@ -1114,21 +1213,23 @@ out:
static int hmac_evm(const char *file, const char *key) static int hmac_evm(const char *file, const char *key)
{ {
unsigned char hash[20]; unsigned char hash[MAX_DIGEST_SIZE];
unsigned char sig[1024]; unsigned char sig[MAX_SIGNATURE_SIZE];
int len, err; int len, err;
len = calc_evm_hmac(file, key, hash); len = calc_evm_hmac(file, key, hash);
if (len <= 1) if (len <= 1)
return len; return len;
assert(len <= sizeof(hash));
log_info("hmac: "); log_info("hmac: ");
log_dump(hash, len); log_dump(hash, len);
assert(len < sizeof(sig));
memcpy(sig + 1, hash, len); memcpy(sig + 1, hash, len);
if (xattr) { if (xattr) {
sig[0] = EVM_XATTR_HMAC; sig[0] = EVM_XATTR_HMAC;
err = lsetxattr(file, "security.evm", sig, len + 1, 0); err = lsetxattr(file, xattr_evm, sig, len + 1, 0);
if (err < 0) { if (err < 0) {
log_err("setxattr failed: %s\n", file); log_err("setxattr failed: %s\n", file);
return err; return err;
@ -1149,7 +1250,7 @@ static int cmd_hmac_evm(struct command *cmd)
return -1; return -1;
} }
key = params.keyfile ? : "/etc/keys/privkey_evm.pem"; key = imaevm_params.keyfile ? : "/etc/keys/privkey_evm.pem";
if (digsig) { if (digsig) {
err = sign_ima(file, key); err = sign_ima(file, key);
@ -1184,9 +1285,9 @@ static int ima_fix(const char *path)
} }
for (; size > 0; len++, size -= len, list += len) { for (; size > 0; len++, size -= len, list += len) {
len = strlen(list); len = strlen(list);
if (!strcmp(list, "security.ima")) if (!strcmp(list, xattr_ima))
ima = 1; ima = 1;
else if (!strcmp(list, "security.evm")) else if (!strcmp(list, xattr_evm))
evm = 1; evm = 1;
} }
if (ima && evm) if (ima && evm)
@ -1263,8 +1364,8 @@ static int cmd_ima_fix(struct command *cmd)
static int ima_clear(const char *path) static int ima_clear(const char *path)
{ {
log_info("%s\n", path); log_info("%s\n", path);
lremovexattr(path, "security.ima"); lremovexattr(path, xattr_ima);
lremovexattr(path, "security.evm"); lremovexattr(path, xattr_evm);
return 0; return 0;
} }
@ -1289,10 +1390,8 @@ static int tpm_pcr_read(int idx, uint8_t *pcr, int len)
if (!fp) if (!fp)
fp = fopen(misc_pcrs, "r"); fp = fopen(misc_pcrs, "r");
if (!fp) { if (!fp)
log_err("Unable to open %s or %s\n", pcrs, misc_pcrs);
return -1; return -1;
}
for (;;) { for (;;) {
p = fgets(buf, sizeof(buf), fp); p = fgets(buf, sizeof(buf), fp);
@ -1308,6 +1407,43 @@ static int tpm_pcr_read(int idx, uint8_t *pcr, int len)
return result; return result;
} }
#ifdef HAVE_TSSPCRREAD
static int tpm2_pcr_read(int idx, uint8_t *hwpcr, int len, char **errmsg)
{
FILE *fp;
char pcr[100]; /* may contain an error */
char cmd[50];
int ret;
sprintf(cmd, "tsspcrread -halg sha1 -ha %d -ns 2> /dev/null", idx);
fp = popen(cmd, "r");
if (!fp) {
ret = asprintf(errmsg, "popen failed: %s", strerror(errno));
if (ret == -1) /* the contents of errmsg is undefined */
*errmsg = NULL;
return -1;
}
if (fgets(pcr, sizeof(pcr), fp) == NULL) {
ret = asprintf(errmsg, "tsspcrread failed: %s",
strerror(errno));
if (ret == -1) /* the contents of errmsg is undefined */
*errmsg = NULL;
ret = pclose(fp);
return -1;
}
/* get the popen "cmd" return code */
ret = pclose(fp);
if (!ret)
hex2bin(hwpcr, pcr, SHA_DIGEST_LENGTH);
else
*errmsg = strndup(pcr, strlen(pcr) - 1); /* remove newline */
return ret;
}
#endif
#define TCG_EVENT_NAME_LEN_MAX 255 #define TCG_EVENT_NAME_LEN_MAX 255
struct template_entry { struct template_entry {
@ -1363,13 +1499,42 @@ void ima_show(struct template_entry *entry)
log_debug_dump(entry->header.digest, sizeof(entry->header.digest)); log_debug_dump(entry->header.digest, sizeof(entry->header.digest));
} }
/*
* Keep track of unknown or malformed template names.
*
* Return 1 for found, return 0 for not found.
*/
static int lookup_template_name_entry(char *template_name)
{
struct template_name_entry {
struct template_name_entry *next;
char name[];
} *entry;
static struct template_name_entry *template_names = NULL;
for (entry = template_names; entry != NULL; entry = entry->next) {
if (strcmp(entry->name, template_name) == 0)
return 1;
}
entry = malloc(sizeof(struct template_name_entry) +
strlen(template_name) + 1);
if (entry) {
strcpy(entry->name, template_name);
entry->next = template_names;
template_names = entry;
}
return 0;
}
void ima_ng_show(struct template_entry *entry) void ima_ng_show(struct template_entry *entry)
{ {
uint8_t *fieldp = entry->template; uint8_t *fieldp = entry->template;
uint32_t field_len; uint32_t field_len;
int total_len = entry->template_len, digest_len, len, sig_len; int total_len = entry->template_len, digest_len, len, sig_len, fbuf_len;
uint8_t *digest, *sig = NULL; uint8_t *digest, *sig = NULL, *fbuf = NULL;
char *algo, *path; char *algo, *path;
int found;
int err; int err;
/* get binary digest */ /* get binary digest */
@ -1407,6 +1572,18 @@ void ima_ng_show(struct template_entry *entry)
sig = fieldp; sig = fieldp;
sig_len = field_len; sig_len = field_len;
/* move to next field */
fieldp += field_len;
total_len -= field_len;
}
} else if (!strcmp(entry->name, "ima-buf")) {
field_len = *(uint32_t *)fieldp;
fieldp += sizeof(field_len);
total_len -= sizeof(field_len);
if (field_len) {
fbuf = fieldp;
fbuf_len = field_len;
/* move to next field */ /* move to next field */
fieldp += field_len; fieldp += field_len;
total_len -= field_len; total_len -= field_len;
@ -1414,16 +1591,20 @@ void ima_ng_show(struct template_entry *entry)
} }
/* ascii_runtime_measurements */ /* ascii_runtime_measurements */
if (params.verbose > LOG_INFO) { if (imaevm_params.verbose > LOG_INFO) {
log_info("%d ", entry->header.pcr); log_info("%d ", entry->header.pcr);
log_dump_n(entry->header.digest, sizeof(entry->header.digest)); log_dump_n(entry->header.digest, sizeof(entry->header.digest));
log_info(" %s %s", entry->name, algo); log_info(" %s %s", entry->name, algo);
log_dump_n(digest, digest_len); log_dump_n(digest, digest_len);
log_info(" %s", path); log_info(" %s", path);
if (fbuf) {
log_info(" ");
log_dump_n(fbuf, fbuf_len);
}
} }
if (sig) { if (sig) {
if (params.verbose > LOG_INFO) { if (imaevm_params.verbose > LOG_INFO) {
log_info(" "); log_info(" ");
log_dump(sig, sig_len); log_dump(sig, sig_len);
} }
@ -1432,15 +1613,19 @@ void ima_ng_show(struct template_entry *entry)
digest, digest_len); digest, digest_len);
else else
err = ima_verify_signature(path, sig, sig_len, NULL, 0); err = ima_verify_signature(path, sig, sig_len, NULL, 0);
if (!err && params.verbose > LOG_INFO) if (!err && imaevm_params.verbose > LOG_INFO)
log_info("%s: verification is OK\n", path); log_info("%s: verification is OK\n", path);
} else { } else {
if (params.verbose > LOG_INFO) if (imaevm_params.verbose > LOG_INFO)
log_info("\n"); log_info("\n");
} }
if (total_len) if (total_len) {
log_err("Remain unprocessed data: %d\n", total_len); found = lookup_template_name_entry(entry->name);
if (!found)
log_err("Template \"%s\" contains unprocessed data: "
"%d bytes\n", entry->name, total_len);
}
} }
static int ima_measurement(const char *file) static int ima_measurement(const char *file)
@ -1466,9 +1651,10 @@ static int ima_measurement(const char *file)
return -1; return -1;
} }
/* Support multiple public keys */ if (imaevm_params.keyfile) /* Support multiple public keys */
if (params.keyfile) init_public_keys(imaevm_params.keyfile);
init_public_keys(params.keyfile); else /* assume read pubkey from x509 cert */
init_public_keys("/etc/keys/x509_evm.der");
while (fread(&entry.header, sizeof(entry.header), 1, fp)) { while (fread(&entry.header, sizeof(entry.header), 1, fp)) {
ima_extend_pcr(pcr[entry.header.pcr], entry.header.digest, ima_extend_pcr(pcr[entry.header.pcr], entry.header.digest,
@ -1514,7 +1700,22 @@ static int ima_measurement(const char *file)
log_info("PCRAgg %.2d: ", i); log_info("PCRAgg %.2d: ", i);
log_dump(pcr[i], SHA_DIGEST_LENGTH); log_dump(pcr[i], SHA_DIGEST_LENGTH);
tpm_pcr_read(i, hwpcr, sizeof(hwpcr)); if (tpm_pcr_read(i, hwpcr, sizeof(hwpcr))) {
#ifdef HAVE_TSSPCRREAD
char *errmsg = NULL;
err = tpm2_pcr_read(i, hwpcr, sizeof(hwpcr), &errmsg);
if (err) {
log_info("Failed to read PCRs: (%s)\n", errmsg);
free(errmsg);
exit(1);
}
#else
log_info("Failed to read TPM 1.2 PCRs.\n");
exit(1);
#endif
}
log_info("HW PCR-%d: ", i); log_info("HW PCR-%d: ", i);
log_dump(hwpcr, sizeof(hwpcr)); log_dump(hwpcr, sizeof(hwpcr));
@ -1615,10 +1816,11 @@ static void usage(void)
printf( printf(
"\n" "\n"
" -a, --hashalgo sha1 (default), sha224, sha256, sha384, sha512\n" " -a, --hashalgo sha1 (default), sha224, sha256, sha384, sha512, streebog256, streebog512\n"
" -s, --imasig make IMA signature\n" " -s, --imasig make IMA signature\n"
" -d, --imahash make IMA hash\n" " -d, --imahash make IMA hash\n"
" -f, --sigfile store IMA signature in .sig file instead of xattr\n" " -f, --sigfile store IMA signature in .sig file instead of xattr\n"
" --xattr-user store xattrs in user namespace (for testing purposes)\n"
" --rsa use RSA key type and signing scheme v1\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" " -k, --key path to signing key (default: /etc/keys/{privkey,pubkey}_evm.pem)\n"
" -o, --portable generate portable EVM signatures\n" " -o, --portable generate portable EVM signatures\n"
@ -1641,6 +1843,7 @@ static void usage(void)
" --selinux use custom Selinux label for EVM\n" " --selinux use custom Selinux label for EVM\n"
" --caps use custom Capabilities for EVM(unspecified: from FS, empty: do not use)\n" " --caps use custom Capabilities for EVM(unspecified: from FS, empty: do not use)\n"
" --list measurement list verification\n" " --list measurement list verification\n"
" --engine e preload OpenSSL engine e (such as: gost)\n"
" -v increase verbosity level\n" " -v increase verbosity level\n"
" -h, --help display this help and exit\n" " -h, --help display this help and exit\n"
"\n"); "\n");
@ -1693,6 +1896,8 @@ static struct option opts[] = {
{"selinux", 1, 0, 136}, {"selinux", 1, 0, 136},
{"caps", 2, 0, 137}, {"caps", 2, 0, 137},
{"list", 0, 0, 138}, {"list", 0, 0, 138},
{"engine", 1, 0, 139},
{"xattr-user", 0, 0, 140},
{} {}
}; };
@ -1716,6 +1921,7 @@ static char *get_password(void)
if (tcsetattr(fileno(stdin), TCSANOW, &tmp_flags) != 0) { if (tcsetattr(fileno(stdin), TCSANOW, &tmp_flags) != 0) {
perror("tcsetattr"); perror("tcsetattr");
free(password);
return NULL; return NULL;
} }
@ -1725,6 +1931,7 @@ static char *get_password(void)
/* restore terminal */ /* restore terminal */
if (tcsetattr(fileno(stdin), TCSANOW, &flags) != 0) { if (tcsetattr(fileno(stdin), TCSANOW, &flags) != 0) {
perror("tcsetattr"); perror("tcsetattr");
free(password);
return NULL; return NULL;
} }
@ -1734,7 +1941,13 @@ static char *get_password(void)
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
int err = 0, c, lind; int err = 0, c, lind;
ENGINE *eng = NULL;
OPENSSL_init_crypto(
#ifndef DISABLE_OPENSSL_CONF
OPENSSL_INIT_LOAD_CONFIG |
#endif
OPENSSL_INIT_ENGINE_ALL_BUILTIN, NULL);
g_argv = argv; g_argv = argv;
g_argc = argc; g_argc = argc;
@ -1749,7 +1962,7 @@ int main(int argc, char *argv[])
exit(0); exit(0);
break; break;
case 'v': case 'v':
params.verbose++; imaevm_params.verbose++;
break; break;
case 'd': case 'd':
digest = 1; digest = 1;
@ -1763,13 +1976,13 @@ int main(int argc, char *argv[])
sigdump = 1; sigdump = 1;
break; break;
case 'a': case 'a':
params.hash_algo = optarg; imaevm_params.hash_algo = optarg;
break; break;
case 'p': case 'p':
if (optarg) if (optarg)
params.keypass = optarg; imaevm_params.keypass = optarg;
else else
params.keypass = get_password(); imaevm_params.keypass = get_password();
break; break;
case 'f': case 'f':
sigfile = 1; sigfile = 1;
@ -1780,10 +1993,10 @@ int main(int argc, char *argv[])
hmac_flags |= HMAC_FLAG_NO_UUID; hmac_flags |= HMAC_FLAG_NO_UUID;
break; break;
case '1': case '1':
params.x509 = 0; imaevm_params.x509 = 0;
break; break;
case 'k': case 'k':
params.keyfile = optarg; imaevm_params.keyfile = optarg;
break; break;
case 'i': case 'i':
if (evm_portable) if (evm_portable)
@ -1844,6 +2057,23 @@ int main(int argc, char *argv[])
case 138: case 138:
measurement_list = 1; measurement_list = 1;
break; break;
case 139: /* --engine e */
eng = ENGINE_by_id(optarg);
if (!eng) {
log_err("engine %s isn't available\n", optarg);
ERR_print_errors_fp(stderr);
} else if (!ENGINE_init(eng)) {
log_err("engine %s init failed\n", optarg);
ERR_print_errors_fp(stderr);
ENGINE_free(eng);
eng = NULL;
}
ENGINE_set_default(eng, ENGINE_METHOD_ALL);
break;
case 140: /* --xattr-user */
xattr_ima = "user.ima";
xattr_evm = "user.evm";
break;
case '?': case '?':
exit(1); exit(1);
break; break;
@ -1870,6 +2100,13 @@ int main(int argc, char *argv[])
} }
} }
if (eng) {
ENGINE_finish(eng);
ENGINE_free(eng);
#if OPENSSL_API_COMPAT < 0x10100000L
ENGINE_cleanup();
#endif
}
ERR_free_strings(); ERR_free_strings();
EVP_cleanup(); EVP_cleanup();
BIO_free(NULL); BIO_free(NULL);

49
src/hash_info.gen Executable file
View File

@ -0,0 +1,49 @@
#!/bin/sh
#
# Generate hash_info.h from kernel headers
#
# Copyright (C) 2018 <vt@altlinux.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
KERNEL_HEADERS=$1
HASH_INFO_H=uapi/linux/hash_info.h
HASH_INFO=$KERNEL_HEADERS/include/$HASH_INFO_H
# Allow to specify kernel-headers past include/
if [ ! -e $HASH_INFO ]; then
HASH_INFO2=$KERNEL_HEADERS/$HASH_INFO_H
if [ -e $HASH_INFO2 ]; then
HASH_INFO=$HASH_INFO2
fi
fi
if [ ! -e $HASH_INFO ]; then
echo "/* $HASH_INFO is not found */"
HASH_INFO=/dev/null
else
echo "/* $HASH_INFO is found */"
fi
echo "enum hash_algo {"
grep HASH_ALGO_.*, $HASH_INFO
printf "\tHASH_ALGO__LAST\n"
echo "};"
echo "const char *const hash_algo_name[HASH_ALGO__LAST] = {"
sed -n 's/HASH_ALGO_\(.*\),/\1 \L\1\E/p' $HASH_INFO | \
while read a b; do
# Normalize text hash name: if it contains underscore between
# digits replace it with a dash, other underscores are removed.
b=$(echo "$b" | sed "s/\([0-9]\)_\([0-9]\)/\1-\2/g;s/_//g")
printf '\t%-26s = "%s",\n' "[HASH_ALGO_$a]" "$b"
done
echo "};"

View File

@ -50,8 +50,10 @@
#include <openssl/rsa.h> #include <openssl/rsa.h>
#ifdef USE_FPRINTF #ifdef USE_FPRINTF
#define do_log(level, fmt, args...) ({ if (level <= params.verbose) fprintf(stderr, fmt, ##args); }) #define do_log(level, fmt, args...) \
#define do_log_dump(level, p, len, cr) ({ if (level <= params.verbose) do_dump(stderr, p, len, cr); }) ({ if (level <= imaevm_params.verbose) fprintf(stderr, fmt, ##args); })
#define do_log_dump(level, p, len, cr) \
({ if (level <= imaevm_params.verbose) imaevm_do_hexdump(stderr, p, len, cr); })
#else #else
#define do_log(level, fmt, args...) syslog(level, fmt, ##args) #define do_log(level, fmt, args...) syslog(level, fmt, ##args)
#define do_log_dump(level, p, len, cr) #define do_log_dump(level, p, len, cr)
@ -75,6 +77,9 @@
#define DATA_SIZE 4096 #define DATA_SIZE 4096
#define SHA1_HASH_LEN 20 #define SHA1_HASH_LEN 20
#define MAX_DIGEST_SIZE 64
#define MAX_SIGNATURE_SIZE 1024
#define __packed __attribute__((packed)) #define __packed __attribute__((packed))
enum evm_ima_xattr_type { enum evm_ima_xattr_type {
@ -149,6 +154,7 @@ struct signature_hdr {
char mpi[0]; char mpi[0];
} __packed; } __packed;
/* reflect enum hash_algo from include/uapi/linux/hash_info.h */
enum pkey_hash_algo { enum pkey_hash_algo {
PKEY_HASH_MD4, PKEY_HASH_MD4,
PKEY_HASH_MD5, PKEY_HASH_MD5,
@ -158,6 +164,18 @@ enum pkey_hash_algo {
PKEY_HASH_SHA384, PKEY_HASH_SHA384,
PKEY_HASH_SHA512, PKEY_HASH_SHA512,
PKEY_HASH_SHA224, PKEY_HASH_SHA224,
PKEY_HASH_RIPE_MD_128,
PKEY_HASH_RIPE_MD_256,
PKEY_HASH_RIPE_MD_320,
PKEY_HASH_WP_256,
PKEY_HASH_WP_384,
PKEY_HASH_WP_512,
PKEY_HASH_TGR_128,
PKEY_HASH_TGR_160,
PKEY_HASH_TGR_192,
PKEY_HASH_SM3_256,
PKEY_HASH_STREEBOG_256,
PKEY_HASH_STREEBOG_512,
PKEY_HASH__LAST PKEY_HASH__LAST
}; };
@ -172,10 +190,7 @@ struct signature_v2_hdr {
uint8_t sig[0]; /* signature payload */ uint8_t sig[0]; /* signature payload */
} __packed; } __packed;
struct libimaevm_params {
typedef int (*verify_hash_fn_t)(const char *file, const unsigned char *hash, int size, unsigned char *sig, int siglen, const char *keyfile);
struct libevm_params {
int verbose; int verbose;
int x509; int x509;
const char *hash_algo; const char *hash_algo;
@ -191,18 +206,17 @@ struct RSA_ASN1_template {
#define NUM_PCRS 20 #define NUM_PCRS 20
#define DEFAULT_PCR 10 #define DEFAULT_PCR 10
extern const struct RSA_ASN1_template RSA_ASN1_templates[PKEY_HASH__LAST]; extern struct libimaevm_params imaevm_params;
extern struct libevm_params params;
void do_dump(FILE *fp, const void *ptr, int len, bool cr); void imaevm_do_hexdump(FILE *fp, const void *ptr, int len, bool cr);
void dump(const void *ptr, int len); void imaevm_hexdump(const void *ptr, int len);
int get_filesize(const char *filename);
int ima_calc_hash(const char *file, uint8_t *hash); int ima_calc_hash(const char *file, uint8_t *hash);
int get_hash_algo(const char *algo); int imaevm_get_hash_algo(const char *algo);
RSA *read_pub_key(const char *keyfile, int x509); RSA *read_pub_key(const char *keyfile, int x509);
EVP_PKEY *read_pub_pkey(const char *keyfile, int x509);
void calc_keyid_v1(uint8_t *keyid, char *str, const unsigned char *pkey, int len); 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); void calc_keyid_v2(uint32_t *keyid, char *str, EVP_PKEY *pkey);
int key2bin(RSA *key, unsigned char *pub); int key2bin(RSA *key, unsigned char *pub);
int sign_hash(const char *algo, const unsigned char *hash, int size, const char *keyfile, const char *keypass, unsigned char *sig); int sign_hash(const char *algo, const unsigned char *hash, int size, const char *keyfile, const char *keypass, unsigned char *sig);

View File

@ -49,15 +49,20 @@
#include <dirent.h> #include <dirent.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <openssl/crypto.h>
#include <openssl/pem.h> #include <openssl/pem.h>
#include <openssl/evp.h> #include <openssl/evp.h>
#include <openssl/x509.h> #include <openssl/x509.h>
#include <openssl/err.h> #include <openssl/err.h>
#include "imaevm.h" #include "imaevm.h"
#include "hash_info.h"
const char *const pkey_hash_algo[PKEY_HASH__LAST] = { /* Names that are primary for OpenSSL. */
static const char *const pkey_hash_algo[PKEY_HASH__LAST] = {
[PKEY_HASH_MD4] = "md4", [PKEY_HASH_MD4] = "md4",
[PKEY_HASH_MD5] = "md5", [PKEY_HASH_MD5] = "md5",
[PKEY_HASH_SHA1] = "sha1", [PKEY_HASH_SHA1] = "sha1",
@ -66,66 +71,17 @@ const char *const pkey_hash_algo[PKEY_HASH__LAST] = {
[PKEY_HASH_SHA384] = "sha384", [PKEY_HASH_SHA384] = "sha384",
[PKEY_HASH_SHA512] = "sha512", [PKEY_HASH_SHA512] = "sha512",
[PKEY_HASH_SHA224] = "sha224", [PKEY_HASH_SHA224] = "sha224",
[PKEY_HASH_STREEBOG_256] = "md_gost12_256",
[PKEY_HASH_STREEBOG_512] = "md_gost12_512",
}; };
/* /* Names that are primary for the kernel. */
* Hash algorithm OIDs plus ASN.1 DER wrappings [RFC4880 sec 5.2.2]. static const char *const pkey_hash_algo_kern[PKEY_HASH__LAST] = {
*/ [PKEY_HASH_STREEBOG_256] = "streebog256",
static const uint8_t RSA_digest_info_MD5[] = { [PKEY_HASH_STREEBOG_512] = "streebog512",
0x30, 0x20, 0x30, 0x0C, 0x06, 0x08,
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, /* OID */
0x05, 0x00, 0x04, 0x10
}; };
static const uint8_t RSA_digest_info_SHA1[] = { struct libimaevm_params imaevm_params = {
0x30, 0x21, 0x30, 0x09, 0x06, 0x05,
0x2B, 0x0E, 0x03, 0x02, 0x1A,
0x05, 0x00, 0x04, 0x14
};
static const uint8_t RSA_digest_info_RIPE_MD_160[] = {
0x30, 0x21, 0x30, 0x09, 0x06, 0x05,
0x2B, 0x24, 0x03, 0x02, 0x01,
0x05, 0x00, 0x04, 0x14
};
static const uint8_t RSA_digest_info_SHA224[] = {
0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09,
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04,
0x05, 0x00, 0x04, 0x1C
};
static const uint8_t RSA_digest_info_SHA256[] = {
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09,
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
0x05, 0x00, 0x04, 0x20
};
static const uint8_t RSA_digest_info_SHA384[] = {
0x30, 0x41, 0x30, 0x0d, 0x06, 0x09,
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02,
0x05, 0x00, 0x04, 0x30
};
static const uint8_t RSA_digest_info_SHA512[] = {
0x30, 0x51, 0x30, 0x0d, 0x06, 0x09,
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
0x05, 0x00, 0x04, 0x40
};
const struct RSA_ASN1_template RSA_ASN1_templates[PKEY_HASH__LAST] = {
#define _(X) { RSA_digest_info_##X, sizeof(RSA_digest_info_##X) }
[PKEY_HASH_MD5] = _(MD5),
[PKEY_HASH_SHA1] = _(SHA1),
[PKEY_HASH_RIPE_MD_160] = _(RIPE_MD_160),
[PKEY_HASH_SHA256] = _(SHA256),
[PKEY_HASH_SHA384] = _(SHA384),
[PKEY_HASH_SHA512] = _(SHA512),
[PKEY_HASH_SHA224] = _(SHA224),
#undef _
};
struct libevm_params params = {
.verbose = LOG_INFO - 1, .verbose = LOG_INFO - 1,
.x509 = 1, .x509 = 1,
.hash_algo = "sha1", .hash_algo = "sha1",
@ -133,7 +89,7 @@ struct libevm_params params = {
static void __attribute__ ((constructor)) libinit(void); static void __attribute__ ((constructor)) libinit(void);
void do_dump(FILE *fp, const void *ptr, int len, bool cr) void imaevm_do_hexdump(FILE *fp, const void *ptr, int len, bool cr)
{ {
int i; int i;
uint8_t *data = (uint8_t *) ptr; uint8_t *data = (uint8_t *) ptr;
@ -144,25 +100,32 @@ void do_dump(FILE *fp, const void *ptr, int len, bool cr)
fprintf(fp, "\n"); fprintf(fp, "\n");
} }
void dump(const void *ptr, int len) void imaevm_hexdump(const void *ptr, int len)
{ {
do_dump(stdout, ptr, len, true); imaevm_do_hexdump(stdout, ptr, len, true);
} }
int get_filesize(const char *filename) static const char *get_hash_algo_by_id(int algo)
{ {
struct stat stats; if (algo < PKEY_HASH__LAST)
/* Need to know the file length */ return pkey_hash_algo[algo];
stat(filename, &stats); if (algo < HASH_ALGO__LAST)
return (int)stats.st_size; return hash_algo_name[algo];
log_err("digest %d not found\n", algo);
return "unknown";
} }
static inline off_t get_fdsize(int fd) /* Output all remaining openssl error messages. */
static void output_openssl_errors(void)
{ {
struct stat stats; while (ERR_peek_error()) {
/* Need to know the file length */ char buf[256];
fstat(fd, &stats); /* buf must be at least 256 bytes long according to man */
return stats.st_size;
ERR_error_string(ERR_get_error(), buf);
log_err("openssl: %s\n", buf);
}
} }
static int add_file_hash(const char *file, EVP_MD_CTX *ctx) static int add_file_hash(const char *file, EVP_MD_CTX *ctx)
@ -171,6 +134,7 @@ static int add_file_hash(const char *file, EVP_MD_CTX *ctx)
int err = -1, bs = DATA_SIZE; int err = -1, bs = DATA_SIZE;
off_t size, len; off_t size, len;
FILE *fp; FILE *fp;
struct stat stats;
fp = fopen(file, "r"); fp = fopen(file, "r");
if (!fp) { if (!fp) {
@ -184,7 +148,12 @@ static int add_file_hash(const char *file, EVP_MD_CTX *ctx)
goto out; goto out;
} }
for (size = get_fdsize(fileno(fp)); size; size -= len) { if (fstat(fileno(fp), &stats) == -1) {
log_err("Failed to fstat: %s (%s)\n", file, strerror(errno));
goto out;
}
for (size = stats.st_size; size; size -= len) {
len = MIN(size, bs); len = MIN(size, bs);
if (!fread(data, len, 1, fp)) { if (!fread(data, len, 1, fp)) {
if (ferror(fp)) { if (ferror(fp)) {
@ -234,6 +203,7 @@ static int add_dir_hash(const char *file, EVP_MD_CTX *ctx)
err |= EVP_DigestUpdate(ctx, &type, sizeof(type)); err |= EVP_DigestUpdate(ctx, &type, sizeof(type));
if (!err) { if (!err) {
log_err("EVP_DigestUpdate() failed\n"); log_err("EVP_DigestUpdate() failed\n");
output_openssl_errors();
result = 1; result = 1;
break; break;
} }
@ -285,19 +255,22 @@ int ima_calc_hash(const char *file, uint8_t *hash)
err = lstat(file, &st); err = lstat(file, &st);
if (err < 0) { if (err < 0) {
log_err("Failed to stat: %s\n", file); log_err("Failed to stat: %s\n", file);
return err; goto err;
} }
md = EVP_get_digestbyname(params.hash_algo); md = EVP_get_digestbyname(imaevm_params.hash_algo);
if (!md) { if (!md) {
log_err("EVP_get_digestbyname() failed\n"); log_err("EVP_get_digestbyname(%s) failed\n",
return 1; imaevm_params.hash_algo);
err = 1;
goto err;
} }
err = EVP_DigestInit(pctx, md); err = EVP_DigestInit(pctx, md);
if (!err) { if (!err) {
log_err("EVP_DigestInit() failed\n"); log_err("EVP_DigestInit() failed\n");
return 1; err = 1;
goto err;
} }
switch (st.st_mode & S_IFMT) { switch (st.st_mode & S_IFMT) {
@ -316,28 +289,37 @@ int ima_calc_hash(const char *file, uint8_t *hash)
break; break;
default: default:
log_errno("Unsupported file type"); log_errno("Unsupported file type");
return -1; err = -1;
goto err;
} }
if (err) if (err)
return err; goto err;
err = EVP_DigestFinal(pctx, hash, &mdlen); err = EVP_DigestFinal(pctx, hash, &mdlen);
if (!err) { if (!err) {
log_err("EVP_DigestFinal() failed\n"); log_err("EVP_DigestFinal() failed\n");
return 1; err = 1;
goto err;
} }
err = mdlen;
return mdlen; err:
if (err == 1)
output_openssl_errors();
#if OPENSSL_VERSION_NUMBER >= 0x10100000
EVP_MD_CTX_free(pctx);
#endif
return err;
} }
RSA *read_pub_key(const char *keyfile, int x509) EVP_PKEY *read_pub_pkey(const char *keyfile, int x509)
{ {
FILE *fp; FILE *fp;
RSA *key = NULL;
X509 *crt = NULL;
EVP_PKEY *pkey = NULL; EVP_PKEY *pkey = NULL;
if (!keyfile)
return NULL;
fp = fopen(keyfile, "r"); fp = fopen(keyfile, "r");
if (!fp) { if (!fp) {
log_err("Failed to open keyfile: %s\n", keyfile); log_err("Failed to open keyfile: %s\n", keyfile);
@ -345,34 +327,53 @@ RSA *read_pub_key(const char *keyfile, int x509)
} }
if (x509) { if (x509) {
crt = d2i_X509_fp(fp, NULL); X509 *crt = d2i_X509_fp(fp, NULL);
if (!crt) { if (!crt) {
log_err("d2i_X509_fp() failed\n"); log_err("Failed to d2i_X509_fp key file: %s\n",
keyfile);
goto out; goto out;
} }
pkey = X509_extract_key(crt); pkey = X509_extract_key(crt);
X509_free(crt);
if (!pkey) { if (!pkey) {
log_err("X509_extract_key() failed\n"); log_err("Failed to X509_extract_key key file: %s\n",
keyfile);
goto out; goto out;
} }
key = EVP_PKEY_get1_RSA(pkey);
} else { } else {
key = PEM_read_RSA_PUBKEY(fp, NULL, NULL, NULL); pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
if (!pkey)
log_err("Failed to PEM_read_PUBKEY key file: %s\n",
keyfile);
} }
if (!key)
log_err("PEM_read_RSA_PUBKEY() failed\n");
out: out:
if (pkey) if (!pkey)
EVP_PKEY_free(pkey); output_openssl_errors();
if (crt)
X509_free(crt);
fclose(fp); fclose(fp);
return pkey;
}
RSA *read_pub_key(const char *keyfile, int x509)
{
EVP_PKEY *pkey;
RSA *key;
pkey = read_pub_pkey(keyfile, x509);
if (!pkey)
return NULL;
key = EVP_PKEY_get1_RSA(pkey);
EVP_PKEY_free(pkey);
if (!key) {
log_err("read_pub_key: unsupported key type\n");
output_openssl_errors();
return NULL;
}
return key; return key;
} }
int verify_hash_v1(const char *file, const unsigned char *hash, int size, static int verify_hash_v1(const char *file, const unsigned char *hash, int size,
unsigned char *sig, int siglen, const char *keyfile) unsigned char *sig, int siglen, const char *keyfile)
{ {
int err, len; int err, len;
@ -400,6 +401,7 @@ int verify_hash_v1(const char *file, const unsigned char *hash, int size,
RSA_free(key); RSA_free(key);
if (err < 0) { if (err < 0) {
log_err("%s: RSA_public_decrypt() failed: %d\n", file, err); log_err("%s: RSA_public_decrypt() failed: %d\n", file, err);
output_openssl_errors();
return 1; return 1;
} }
@ -417,29 +419,46 @@ struct public_key_entry {
struct public_key_entry *next; struct public_key_entry *next;
uint32_t keyid; uint32_t keyid;
char name[9]; char name[9];
RSA *key; EVP_PKEY *key;
}; };
static struct public_key_entry *public_keys = NULL; static struct public_key_entry *public_keys = NULL;
static RSA *find_keyid(uint32_t keyid) static EVP_PKEY *find_keyid(uint32_t keyid)
{ {
struct public_key_entry *entry; struct public_key_entry *entry, *tail = public_keys;
int i = 1;
for (entry = public_keys; entry != NULL; entry = entry->next) { for (entry = public_keys; entry != NULL; entry = entry->next) {
if (entry->keyid == keyid) if (entry->keyid == keyid)
return entry->key; return entry->key;
i++;
tail = entry;
} }
return NULL;
/* add unknown keys to list */
entry = calloc(1, sizeof(struct public_key_entry));
if (!entry) {
perror("calloc");
return 0;
}
entry->keyid = keyid;
if (tail)
tail->next = entry;
else
public_keys = entry;
log_err("key %d: %x (unknown keyid)\n", i, __be32_to_cpup(&keyid));
return 0;
} }
void init_public_keys(const char *keyfiles) void init_public_keys(const char *keyfiles)
{ {
struct public_key_entry *entry; struct public_key_entry *entry;
char *tmp_keyfiles; char *tmp_keyfiles, *keyfiles_free;
char *keyfile; char *keyfile;
int i = 1; int i = 1;
tmp_keyfiles = strdup(keyfiles); tmp_keyfiles = strdup(keyfiles);
keyfiles_free = tmp_keyfiles;
while ((keyfile = strsep(&tmp_keyfiles, ", \t")) != NULL) { while ((keyfile = strsep(&tmp_keyfiles, ", \t")) != NULL) {
if (!keyfile) if (!keyfile)
@ -454,7 +473,7 @@ void init_public_keys(const char *keyfiles)
break; break;
} }
entry->key = read_pub_key(keyfile, 1); entry->key = read_pub_pkey(keyfile, 1);
if (!entry->key) { if (!entry->key) {
free(entry); free(entry);
continue; continue;
@ -466,71 +485,94 @@ void init_public_keys(const char *keyfiles)
entry->next = public_keys; entry->next = public_keys;
public_keys = entry; public_keys = entry;
} }
free(keyfiles_free);
} }
int verify_hash_v2(const char *file, const unsigned char *hash, int size, /*
unsigned char *sig, int siglen, const char *keyfile) * Return: 0 verification good, 1 verification bad, -1 error.
*/
static int verify_hash_v2(const char *file, const unsigned char *hash, int size,
unsigned char *sig, int siglen)
{ {
int err, len; int ret = -1;
unsigned char out[1024]; EVP_PKEY *pkey, *pkey_free = NULL;
RSA *key;
struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig; struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
const struct RSA_ASN1_template *asn1; EVP_PKEY_CTX *ctx;
const EVP_MD *md;
const char *st;
if (params.verbose > LOG_INFO) { if (imaevm_params.verbose > LOG_INFO) {
log_info("hash: "); log_info("hash(%s): ", imaevm_params.hash_algo);
log_dump(hash, size); log_dump(hash, size);
} }
if (public_keys) { pkey = find_keyid(hdr->keyid);
key = find_keyid(hdr->keyid); if (!pkey) {
if (!key) { uint32_t keyid = hdr->keyid;
log_err("%s: unknown keyid: %x\n", file,
__be32_to_cpup(&hdr->keyid));
return -1;
}
} else {
key = read_pub_key(keyfile, 1);
if (!key)
return 1;
}
log_info("%s: verification failed: unknown keyid %x\n",
err = RSA_public_decrypt(siglen - sizeof(*hdr), sig + sizeof(*hdr), file, __be32_to_cpup(&keyid));
out, key, RSA_PKCS1_PADDING);
if (err < 0) {
log_err("%s: RSA_public_decrypt() failed: %d\n", file, err);
return 1;
}
len = err;
asn1 = &RSA_ASN1_templates[hdr->hash_algo];
if (len < asn1->size || memcmp(out, asn1->data, asn1->size)) {
log_err("%s: verification failed: %d\n", file, err);
return -1; return -1;
} }
len -= asn1->size; st = "EVP_PKEY_CTX_new";
if (!(ctx = EVP_PKEY_CTX_new(pkey, NULL)))
if (len != size || memcmp(out + asn1->size, hash, len)) { goto err;
log_err("%s: verification failed: %d\n", file, err); st = "EVP_PKEY_verify_init";
return -1; if (!EVP_PKEY_verify_init(ctx))
goto err;
st = "EVP_get_digestbyname";
if (!(md = EVP_get_digestbyname(imaevm_params.hash_algo)))
goto err;
st = "EVP_PKEY_CTX_set_signature_md";
if (!EVP_PKEY_CTX_set_signature_md(ctx, md))
goto err;
st = "EVP_PKEY_verify";
ret = EVP_PKEY_verify(ctx, sig + sizeof(*hdr),
siglen - sizeof(*hdr), hash, size);
if (ret == 1)
ret = 0;
else if (ret == 0) {
log_err("%s: verification failed: %d (%s)\n",
file, ret, ERR_reason_error_string(ERR_get_error()));
output_openssl_errors();
ret = 1;
} }
err:
return 0; if (ret < 0 || ret > 1) {
log_err("%s: verification failed: %d (%s) in %s\n",
file, ret, ERR_reason_error_string(ERR_peek_error()),
st);
output_openssl_errors();
ret = -1;
}
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(pkey_free);
return ret;
} }
int get_hash_algo(const char *algo) int imaevm_get_hash_algo(const char *algo)
{ {
int i; int i;
/* first iterate over builtin algorithms */
for (i = 0; i < PKEY_HASH__LAST; i++) for (i = 0; i < PKEY_HASH__LAST; i++)
if (!strcmp(algo, pkey_hash_algo[i])) if (pkey_hash_algo[i] &&
!strcmp(algo, pkey_hash_algo[i]))
return i; return i;
return PKEY_HASH_SHA1; for (i = 0; i < PKEY_HASH__LAST; i++)
if (pkey_hash_algo_kern[i] &&
!strcmp(algo, pkey_hash_algo_kern[i]))
return i;
/* iterate over algorithms provided by kernel-headers */
for (i = 0; i < HASH_ALGO__LAST; i++)
if (hash_algo_name[i] &&
!strcmp(algo, hash_algo_name[i]))
return i;
return -1;
} }
static int get_hash_algo_from_sig(unsigned char *sig) static int get_hash_algo_from_sig(unsigned char *sig)
@ -563,38 +605,28 @@ static int get_hash_algo_from_sig(unsigned char *sig)
int verify_hash(const char *file, const unsigned char *hash, int size, unsigned char *sig, int verify_hash(const char *file, const unsigned char *hash, int size, unsigned char *sig,
int siglen) int siglen)
{ {
const char *key;
int x509;
verify_hash_fn_t verify_hash;
/* Get signature type from sig header */ /* Get signature type from sig header */
if (sig[0] == DIGSIG_VERSION_1) { if (sig[0] == DIGSIG_VERSION_1) {
verify_hash = verify_hash_v1; const char *key = NULL;
/* Read pubkey from RSA key */ /* Read pubkey from RSA key */
x509 = 0; if (!imaevm_params.keyfile)
key = "/etc/keys/pubkey_evm.pem";
return verify_hash_v1(file, hash, size, sig, siglen, key);
} else if (sig[0] == DIGSIG_VERSION_2) { } else if (sig[0] == DIGSIG_VERSION_2) {
verify_hash = verify_hash_v2; return verify_hash_v2(file, hash, size, sig, siglen);
/* Read pubkey from x509 cert */
x509 = 1;
} else } else
return -1; return -1;
/* Determine what key to use for verification*/
key = params.keyfile ? : x509 ?
"/etc/keys/x509_evm.der" :
"/etc/keys/pubkey_evm.pem";
return verify_hash(file, hash, size, sig, siglen, key);
} }
int ima_verify_signature(const char *file, unsigned char *sig, int siglen, int ima_verify_signature(const char *file, unsigned char *sig, int siglen,
unsigned char *digest, int digestlen) unsigned char *digest, int digestlen)
{ {
unsigned char hash[64]; unsigned char hash[MAX_DIGEST_SIZE];
int hashlen, sig_hash_algo; int hashlen, sig_hash_algo;
if (sig[0] != 0x03) { if (sig[0] != 0x03) {
log_err("security.ima has no signature\n"); log_err("xattr ima has no signature\n");
return -1; return -1;
} }
@ -604,7 +636,7 @@ int ima_verify_signature(const char *file, unsigned char *sig, int siglen,
return -1; return -1;
} }
/* Use hash algorithm as retrieved from signature */ /* Use hash algorithm as retrieved from signature */
params.hash_algo = pkey_hash_algo[sig_hash_algo]; imaevm_params.hash_algo = get_hash_algo_by_id(sig_hash_algo);
/* /*
* Validate the signature based on the digest included in the * Validate the signature based on the digest included in the
@ -616,6 +648,7 @@ int ima_verify_signature(const char *file, unsigned char *sig, int siglen,
hashlen = ima_calc_hash(file, hash); hashlen = ima_calc_hash(file, hash);
if (hashlen <= 1) if (hashlen <= 1)
return hashlen; return hashlen;
assert(hashlen <= sizeof(hash));
return verify_hash(file, hash, hashlen, sig + 1, siglen - 1); return verify_hash(file, hash, hashlen, sig + 1, siglen - 1);
} }
@ -672,57 +705,79 @@ void calc_keyid_v1(uint8_t *keyid, char *str, const unsigned char *pkey, int len
memcpy(keyid, sha1 + 12, 8); memcpy(keyid, sha1 + 12, 8);
log_debug("keyid: "); log_debug("keyid: ");
log_debug_dump(keyid, 8); log_debug_dump(keyid, 8);
if (params.verbose > LOG_INFO) {
id = __be64_to_cpup((__be64 *) keyid); id = __be64_to_cpup((__be64 *) keyid);
sprintf(str, "%llX", (unsigned long long)id); sprintf(str, "%llX", (unsigned long long)id);
if (imaevm_params.verbose > LOG_INFO)
log_info("keyid-v1: %s\n", str); log_info("keyid-v1: %s\n", str);
}
} }
void calc_keyid_v2(uint32_t *keyid, char *str, RSA *key) /*
* Calculate keyid of the public_key part of EVP_PKEY
*/
void calc_keyid_v2(uint32_t *keyid, char *str, EVP_PKEY *pkey)
{ {
uint8_t sha1[SHA_DIGEST_LENGTH]; X509_PUBKEY *pk = NULL;
unsigned char *pkey = NULL; const unsigned char *public_key = NULL;
int len; int len;
len = i2d_RSAPublicKey(key, &pkey); /* This is more generic than i2d_PublicKey() */
if (X509_PUBKEY_set(&pk, pkey) &&
SHA1(pkey, len, sha1); X509_PUBKEY_get0_param(NULL, &public_key, &len, NULL, pk)) {
uint8_t sha1[SHA_DIGEST_LENGTH];
SHA1(public_key, len, sha1);
/* sha1[12 - 19] is exactly keyid from gpg file */ /* sha1[12 - 19] is exactly keyid from gpg file */
memcpy(keyid, sha1 + 16, 4); memcpy(keyid, sha1 + 16, 4);
} else
*keyid = 0;
log_debug("keyid: "); log_debug("keyid: ");
log_debug_dump(keyid, 4); log_debug_dump(keyid, 4);
if (params.verbose > LOG_INFO) {
sprintf(str, "%x", __be32_to_cpup(keyid)); sprintf(str, "%x", __be32_to_cpup(keyid));
log_info("keyid: %s\n", str);
}
free(pkey); if (imaevm_params.verbose > LOG_INFO)
log_info("keyid: %s\n", str);
X509_PUBKEY_free(pk);
} }
static RSA *read_priv_key(const char *keyfile, const char *keypass) static EVP_PKEY *read_priv_pkey(const char *keyfile, const char *keypass)
{ {
FILE *fp; FILE *fp;
RSA *key; EVP_PKEY *pkey;
fp = fopen(keyfile, "r"); fp = fopen(keyfile, "r");
if (!fp) { if (!fp) {
log_err("Failed to open keyfile: %s\n", keyfile); log_err("Failed to open keyfile: %s\n", keyfile);
return NULL; return NULL;
} }
ERR_load_crypto_strings(); pkey = PEM_read_PrivateKey(fp, NULL, NULL, (void *)keypass);
key = PEM_read_RSAPrivateKey(fp, NULL, NULL, (void *)keypass); if (!pkey) {
if (!key) { log_err("Failed to PEM_read_PrivateKey key file: %s\n",
char str[256]; keyfile);
output_openssl_errors();
ERR_error_string(ERR_get_error(), str);
log_err("PEM_read_RSAPrivateKey() failed: %s\n", str);
} }
fclose(fp); fclose(fp);
return pkey;
}
static RSA *read_priv_key(const char *keyfile, const char *keypass)
{
EVP_PKEY *pkey;
RSA *key;
pkey = read_priv_pkey(keyfile, keypass);
if (!pkey)
return NULL;
key = EVP_PKEY_get1_RSA(pkey);
EVP_PKEY_free(pkey);
if (!key) {
log_err("read_priv_key: unsupported key type\n");
output_openssl_errors();
return NULL;
}
return key; return key;
} }
@ -737,7 +792,8 @@ static int get_hash_algo_v1(const char *algo)
return -1; return -1;
} }
int sign_hash_v1(const char *hashalgo, const unsigned char *hash, int size, const char *keyfile, unsigned char *sig) 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; int len = -1, hashalgo_idx;
SHA_CTX ctx; SHA_CTX ctx;
@ -768,10 +824,10 @@ int sign_hash_v1(const char *hashalgo, const unsigned char *hash, int size, cons
return -1; return -1;
} }
log_info("hash: "); log_info("hash(%s): ", hashalgo);
log_dump(hash, size); log_dump(hash, size);
key = read_priv_key(keyfile, params.keypass); key = read_priv_key(keyfile, imaevm_params.keypass);
if (!key) if (!key)
return -1; return -1;
@ -804,6 +860,7 @@ int sign_hash_v1(const char *hashalgo, const unsigned char *hash, int size, cons
len = RSA_private_encrypt(sizeof(sighash), sighash, sig + sizeof(*hdr) + 2, key, RSA_PKCS1_PADDING); len = RSA_private_encrypt(sizeof(sighash), sighash, sig + sizeof(*hdr) + 2, key, RSA_PKCS1_PADDING);
if (len < 0) { if (len < 0) {
log_err("RSA_private_encrypt() failed: %d\n", len); log_err("RSA_private_encrypt() failed: %d\n", len);
output_openssl_errors();
goto out; goto out;
} }
@ -817,14 +874,22 @@ out:
return len; return len;
} }
int sign_hash_v2(const char *algo, const unsigned char *hash, int size, const char *keyfile, unsigned char *sig) /*
* @sig is assumed to be of (MAX_SIGNATURE_SIZE - 1) size
* Return: -1 signing error, >0 length of signature
*/
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 *hdr;
int len = -1; int len = -1;
RSA *key; EVP_PKEY *pkey;
char name[20]; char name[20];
unsigned char *buf; EVP_PKEY_CTX *ctx = NULL;
const struct RSA_ASN1_template *asn1; const EVP_MD *md;
size_t sigsize;
const char *st;
uint32_t keyid;
if (!hash) { if (!hash) {
log_err("sign_hash_v2: hash is null\n"); log_err("sign_hash_v2: hash is null\n");
@ -846,43 +911,56 @@ int sign_hash_v2(const char *algo, const unsigned char *hash, int size, const ch
return -1; return -1;
} }
log_info("hash: "); log_info("hash(%s): ", imaevm_params.hash_algo);
log_dump(hash, size); log_dump(hash, size);
key = read_priv_key(keyfile, params.keypass); pkey = read_priv_pkey(keyfile, imaevm_params.keypass);
if (!key) if (!pkey)
return -1; return -1;
hdr = (struct signature_v2_hdr *)sig; hdr = (struct signature_v2_hdr *)sig;
hdr->version = (uint8_t) DIGSIG_VERSION_2; hdr->version = (uint8_t) DIGSIG_VERSION_2;
hdr->hash_algo = get_hash_algo(algo); hdr->hash_algo = imaevm_get_hash_algo(algo);
if (hdr->hash_algo == -1) {
calc_keyid_v2(&hdr->keyid, name, key); log_err("sign_hash_v2: hash algo is unknown: %s\n", algo);
return -1;
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;
} }
calc_keyid_v2(&keyid, name, pkey);
hdr->keyid = keyid;
st = "EVP_PKEY_CTX_new";
if (!(ctx = EVP_PKEY_CTX_new(pkey, NULL)))
goto err;
st = "EVP_PKEY_sign_init";
if (!EVP_PKEY_sign_init(ctx))
goto err;
st = "EVP_get_digestbyname";
if (!(md = EVP_get_digestbyname(imaevm_params.hash_algo)))
goto err;
st = "EVP_PKEY_CTX_set_signature_md";
if (!EVP_PKEY_CTX_set_signature_md(ctx, md))
goto err;
st = "EVP_PKEY_sign";
sigsize = MAX_SIGNATURE_SIZE - sizeof(struct signature_v2_hdr) - 1;
if (!EVP_PKEY_sign(ctx, hdr->sig, &sigsize, hash, size))
goto err;
len = (int)sigsize;
/* we add bit length of the signature to make it gnupg compatible */ /* we add bit length of the signature to make it gnupg compatible */
hdr->sig_size = __cpu_to_be16(len); hdr->sig_size = __cpu_to_be16(len);
len += sizeof(*hdr); len += sizeof(*hdr);
log_info("evm/ima signature: %d bytes\n", len); log_info("evm/ima signature: %d bytes\n", len);
out:
if (buf) err:
free(buf); if (len == -1) {
RSA_free(key); log_err("sign_hash_v2: signing failed: (%s) in %s\n",
ERR_reason_error_string(ERR_peek_error()), st);
output_openssl_errors();
}
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(pkey);
return len; return len;
} }
@ -890,14 +968,16 @@ out:
int sign_hash(const char *hashalgo, const unsigned char *hash, int size, const char *keyfile, const char *keypass, unsigned char *sig) int sign_hash(const char *hashalgo, const unsigned char *hash, int size, const char *keyfile, const char *keypass, unsigned char *sig)
{ {
if (keypass) if (keypass)
params.keypass = keypass; imaevm_params.keypass = keypass;
return params.x509 ? sign_hash_v2(hashalgo, hash, size, keyfile, sig) : return imaevm_params.x509 ?
sign_hash_v2(hashalgo, hash, size, keyfile, sig) :
sign_hash_v1(hashalgo, hash, size, keyfile, sig); sign_hash_v1(hashalgo, hash, size, keyfile, sig);
} }
static void libinit() static void libinit()
{ {
OpenSSL_add_all_algorithms(); OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS |
OPENSSL_INIT_ADD_ALL_DIGESTS, NULL);
ERR_load_crypto_strings(); ERR_load_crypto_strings();
} }