在TPM2.0下使用IMA/EVM

事情的起因是在根据Integrity Measurement Architecture (IMA)上所叙述的教程,来启用Linux上的IMA/EVM模块。但是由于TPM2.0太新了,配置过程中涉及到TPM芯片的部分,指令无法正确执行。

特别说明,此处的测试均是以root用户来进行实验的,并未考虑普通用户使用时所涉及的功能效果以及权限问题。

执行环境

  • Ubuntu 20.04.1
  • vmware虚拟TPM2.0

如何在Vmware虚拟机中安装TPM芯片,参见教程Vmware Fusion TPM安装过程

Ubuntu上开启IMA/EVM

由于Ubuntu的内核中已经默认编译了IMA和EVM模块,因此不需要再手动重新编译内核。

IMA/EVM命令行参数

根据Ubuntu一个较为久远的IMA wiki中所描述的方式启用ima。首先打开/etc/default/grub文件,在里面找到GRUB_CMDLINE_LINUX参数。在这里添加Linux启动时的命令行参数,不同参数之间用空格分隔。(如果GRUB_CMDLINE_LINUX 内部已经有其他的参数,就在末尾添加,不要忘记空格)。

1
GRUB_CMDLINE_LINUX="ima_tcb ima_appraise_tcb ima_appraise=fix evm=fix"

这里介绍一下各个参数的含义(还有许多其他的参数,请参考文档Integrity Measurement Architecture (IMA)):

  • ima_policy

    指定内置策略。在Linux-4.13中默认值为无策略。此外该参数可以被指定多次,最终结果为多个参数的并集。

    可选参数

    • tcb - 测量所有运行的可执行文件,所有要执行的mmap文件(例如共享库),所有内核模块以及所有固件。此外,还将测量打开供root用户读取的文件。
    • appraise_tcb - 评估root拥有的所有文件。
    • secure_boot - 评估所有加载的模块,固件,kexec’d kernel和IMA策略。它还要求他们也具有IMA签名。通常在“安全启动”方案中将其与内核中的CONFIG_INTEGRITY_TRUSTED_KEYRING选项一起使用,并通过固件从OEM或通过垫片中的MOK(Machine Owner Key)获得公钥。
  • ima_tcb
    如果指定,则启用TCB策略,该策略可以满足Trusted Computing Base的需求。这意味着IMA将测量所有已执行的程序可执行程序的文件映射以及uid = 0所打开供读取的所有文件

  • ima_appraise_tcb

    ima_appraise_tcb用于启用ima文件评估功能。IMA评估扩展针对存储为扩展属性security.ima的“良好”值添加了本地完整性验证和度量的实施。验证security.ima的初始方法是基于散列的,它提供了文件数据的完整性;而基于数字签名的除了提供文件数据的完整性之外,还提供了真实性。

  • ima_appraise=fix

    在启用ima appraise功能时,与ima_appraise_tcb一同加入到命令行参数,会对文件系统进行标记(存储在拓展属性security.ima中)。标记结束后,可以删除该参数,仅保留ima_appraise_tcb参数。

    可选参数

    • off - 是关闭完整性评估验证的运行时参数。
    • enforce - 验证并加强运行时文件的完整性。 [默认参数]
    • fix - 对于非数字签名的文件,更新security.ima拓展属性以反映现有的文件哈希。
    • log - 与enforce类似,区别在于不会拒绝文件的访问,而是记录日志。
  • evm=fix

    为了标记使用扩展属性security.evm现有文件系统,已定义了新的引导参数evm = fix

  • ima_audit=0

    信息性审核日志记录

    可选参数

    • 0 - 正常完整性审核消息。[默认参数]
    • 1 - 启用额外信息完整性审核消息。

修改完启动命令行参数后,使用如下命令更新自动生成文件更新/boot/grub/grub.cfg。然后重启电脑。

1
sudo update-grub

EVM的使用还至少需要一个evm-key,因此在下文中继续EVM的设置。

EVM受信任的加密密钥

受信任的密钥要求具有受信任的平台模块(TPM)芯片以提供更高的安全性,而加密的密钥可以在任何系统上使用。即只有拥有TPM的平台上才能使用trusted方式来创建密钥。

(如果没有TPM芯片,则可以使用用户的方式创建,具体的Creating trusted and EVM encrypted keys参照中的教程。这里因为没有测试,就不赘述了。)

默认情况下,受信任和EVM快捷方式模块在/etc/keys中查找受信任和EVM加密的密钥。要创建并保存内核主密钥和EVM密钥。

1
2
3
4
5
6
7
8
9
10
$ su -c 'mkdir -p /etc/keys'

# To create and save the kernel master key (trusted type):
$ su -c 'modprobe trusted encrypted'
$ su -c 'keyctl add trusted kmk-trusted "new 32" @u'
$ su -c 'keyctl pipe `keyctl search @u trusted kmk-trusted` >/etc/keys/kmk-trusted.blob'

# Create the EVM encrypted key
$ su -c 'keyctl add encrypted evm-key "new trusted:kmk-trusted 32" @u'
$ su -c 'keyctl pipe `keyctl search @u encrypted evm-key` >/etc/keys/evm-trusted.blob'

但是在执行到命令keyctl add trusted kmk-trusted "new 32" @u时,可能会出现错误add_key: Invalid argument。尽管拥有TPM芯片,该错误依然会出现。解决方案可以参见Linux内核文档Trusted and Encrypted Keys

对于TPM1.2

默认情况下,可信密钥在SRK下密封,该密钥具有默认授权值(20个零)。可以在trouser的工具tpm_takeownership -u -z中进行设置。因此对于TPM1.2上述创建EVM可信密钥的执行不会出现问题。

对于TPM2.0

用户必须首先创建一个存储密钥并使其持久化,以便该密钥在重新启动后可用。可以使用以下命令来完成。

使用IBM TSS 2 stack(我的环境不适用这种方式,因此没有经过测试):

1
2
3
#> tsscreateprimary -hi o -st
Handle 80000000
#> tssevictcontrol -hi o -ho 80000000 -hp 81000001

或者使用Intel TSS 2 stack

1
2
3
#> tpm2_createprimary --hierarchy o -G rsa2048 -c key.ctxt
#> tpm2_evictcontrol -c key.ctxt 0x81000001
persistentHandle: 0x81000001

创建完之后将原先的可信密钥命令修改为keyctl add trusted kmk-trusted "new 32 keyhandle=0x81000001" @u ,即可。后续的命令照常执行。

在创建完evm-key之后,使用如下命令来启用evm。

1
echo "1" >/sys/kernel/security/evm

IMA/EVM测试

使用测试

Ubuntu中默认挂载了securityfs伪文件系统,在/sys/kernel/security中。如果系统没有默认挂载则使用如下命令手动挂载。

1
mount -t securityfs securityfs /sys/kernel/security

然后使用命令more /sys/kernel/security/ima/ascii_runtime_measurements查看ima模块存储的运行度量值。ima的度量日志格式可以参见附录A

然后随意创建一个文件并保存,如test2.txt。之后再使用getfattr工具查看其拓展属性(如果没有这条命令,则使用apt install attr安装)。

由于ima_appraise='fix'参数的作用是重新标记系统,所以此时修改test2.txt的内容则,ima和evm相应的值也会发生改变。

IMA评估

首先使用keyctl show查看用户会话中已有的密钥。如果”kmk-trusted”和”eve-key”不存在,则使用下列命令添加。

1
2
3
#> keyctl add trusted kmk-trusted "load `cat /etc/keys/kmk-trusted.blob` keyhandle=0x81000001" @u
#> keyctl add encrypted evm-key "load `cat /etc/keys/evm-trusted.blob`" @u
#> echo "1" >/sys/kernel/security/evm

创建一个文件text.txt,写入一些文字,然后查看其内存储的ima和evm值。

接着使用命令对所有文件进行标记。该方法执行耗时较长。

1
#> time find / -fstype ext4 -type f -uid 0 -exec dd if='{}' of=/dev/null count=0 status=none \;

由于ima_appraise='fix'参数的作用是重新标记系统,标记完之后修改为ima_appraise=enforce。则会使系统在使用存储的值之前对其进行验证。如果不匹配,则不会加载文件,对该文件的任何访问都将被拒绝,并显示“权限被拒绝”错误;如果启用了审核,则会生成审核事件。

因此修改/etc/default/grub文件,将ima_appraise设为enforce,同时启用审核。

1
GRUB_CMDLINE_LINUX="ima_tcb ima_appraise_tcb ima_appraise=enforce evm=fix ima_audit=0"

然后再次执行update-grub并重启系统。

然后修改text.txt中存储的ima的值为其他值。修改之后可以看到text.txt的访问被拒绝。同时在dmesg中记录了一条信息说明了错误的原因为”invalid-hash”。

然后再测试命令apt,得到如下结果:

测试工具验证(未完成)

由于我的测试环境使用的是TPM 2.0,出现了错误Error event too long PCR-00,导致该工具测试未完成。以后有时间再选用TPM 1.2进行测试。

IMA测试程序是Linux Test Project.的一部分。

首先安装校验工具:

1
2
3
4
5
#> wget -O ltp-ima-standalone-v2.tar.gz http://downloads.sf.net/project/linux-ima/linux-ima/ltp-ima-standalone-v2.tar.gz
#> tar -xvzf ltp-ima-standalone-v2.tar.gz
#> cd ima-tests
#> make
#> make install

如果make install,出现如下图所示的结果,则说明install在了错误的位置。该命令应该放在某个bin目录下。

因此修改ima-tests/Makefile中的DESTDIR的值为/usr/local/bin/,如下图所示。

然后重新执行make install。安装成功后则有了三个命令行工具。

IMA/EVM数字签名

使用命令分别为ima和evm生成公私钥对,并放入到/etc/keys/目录下。

1
2
3
4
5
6
# generate unencrypted private key
openssl genrsa -out privkey_evm.pem 1024
openssl rsa -pubout -in privkey_evm.pem -out pubkey_evm.pem

openssl genrsa -out privkey_ima.pem 1024
openssl rsa -pubout -in privkey_ima.pem -out pubkey_ima.pem

然后安装ima-evm-utils包,以获得工具evmctl。

1
apt install ima-evm-utils

接着使用evmctl工具将刚刚创建好的公钥导入到keyring中存储。如果不加参数--rsa,添加key会失败。

1
2
3
4
5
ima_id=`keyctl newring _ima @u`
evmctl import --rsa /etc/keys/pubkey_ima.pem $ima_id

evm_id=`keyctl newring _evm @u`
evmctl import --rsa /etc/keys/pubkey_evm.pem $evm_id

然后对着之前的文件test.txt进行签名。

1
2
# 计算文件hash存入security.ima,并且产生数字签名存入security.evm
evmctl sign --imahash test.txt
1
2
# 计算ima签名和evm签名
evmctl sign --imasig test.txt
1
2
# 仅计算ima签名
evmctl ima_sign test.txt

文章涉及部分命令解释

keyctl

该命令程序用于控制密钥管理工具使用各种子命令的各种方式。

  1. keyctl add trusted kmk-trusted "new 32 keyhandle=0x81000001" @u

    • 对于可信密钥keyctl add trusted name "new keylen [options]" ring则有这种固定格式。

    • 密钥类型为trusted,该类型仅有TPM芯片存在的情况下才能使用。

    • kmk-trusted是自定义的密钥描述,也即密钥的名字。

    • "new 32 keyhandle=0x81000001"代表生成的可信密钥长度为32字节(可信密钥支持32-128字节,上限为2048位SRK(RSA)密钥长度,并带有所有必要的结构/填充。)。keyhandle=0x81000001指向某一持久化密钥的句柄0x81000001。

    • @u特殊的密钥环标识符,表示用户特定密钥环。该密钥环在特定用户拥有的所有进程之间共享。它不会直接搜索,但通常是从会话密钥环链接到的。

    • 该命令的执行之后查看:

      打印该密钥的值:

  2. keyctl pipe <key>:将源数据输出到标准输出流。

  3. keyctl add encrypted evm-key "new trusted:kmk-trusted 32" @u

    • encrypted用于给密钥加密。加密密钥不依赖于TPM,并且速度更快,因为它们使用AES进行加密/解密。 新密钥由内核生成的随机数创建,并使用指定的“主”密钥进行加密/解密。 “主”密钥可以是受信任密钥或用户密钥类型。 加密密钥的主要缺点是,如果它们不根植于受信任的密钥中,则它们的安全性仅与加密它们的用户密钥一样安全。 因此,应该以一种尽可能安全的方式加载主用户密钥,最好在启动初期就加载。

    • 该用法格式keyctl add encrypted name "new key-type:master-key-name keylen" ring

    • 执行后查看密钥

      打印该密钥的值:

tpm2-tools内的命令

  1. tpm2_createprimary --hierarchy o -G rsa2048 -c key.ctxt

    • tpm2_createprimary用于创建四种不同授权层次的主对象(Owner, Platform, Endorsement, NULL)。这条命令中--hierarchy o对应的层次为Owner。

    • -G指定生成主密钥的算法

    • -c key.ctxt表示保存生成的对象主内容

    • 该命令执行结果如下:

      其key.ctxt内部分内容如下图:

  2. tpm2_evictcontrol -c key.ctxt 0x81000001

    • tpm2_evictcontrol用于创建或删除持久化对象。这条命令的作用就是将提供的临时对象存储到一个持久化的句柄0x81000001中。

    • 如果一个对象是持久的,则该对象驻留在TPM中的持久句柄地址上。

    • 使用tpm2_readpublic(用于读取公共区域的对象)读取句柄内的值

附录A:IMA模版

注:以下均内容均在内核版本v5.4.120下进行叙述,不同的内核版本细节上可能存在差异。

模版代表了IMA度量日志的输出格式,以一条来自ascii_runtime_measurements的记录为例:

1
10 972d62ff5b3a74e89952e0980b2099eed49bf8f0 ima-ng sha1:e9002ba6c5a98f5b7a33dc6bbf9ac1863873b713 /init
  • 当前条目关联的PCR寄存器:10
  • 当前模版的哈希值(默认SHA1):972d62ff5b3a74e89952e0980b2099eed49bf8f0
  • 当前模版名称:ima-ng
  • 模版相关数据:sha1:e9002ba6c5a98f5b7a33dc6bbf9ac1863873b713
  • 模版相关数据:/init

这其中前三者,也即PCR、模版哈希、模版名称,是必须的,而之后的模版相关数据则是根据不同模版规则来记录。除此之外,模版哈希的计算也会因为不同的模版而存在差异。

模版管理机制

原始ima模版是固定长度的,包含文件哈希(固定20字节)和路径名(最大长度不超过255字节)。为了克服这个限制,并且添加额外的元数据(如LSM标签),需要定义新的模版。但是每次定义新的模版都需要编写额外的生成和显示额外数据的代码,为避免代码显著增长,引入模版管理机制来统一处理。

ps:可以理解成采取多态的方式来处理不同模版的处理问题

模版包含两个核心的数据结构模版描述符,记录模版相关信息;以及模版字段,用于生成和显示数据。除此之外,还有一个模版条目结构,用于关联模版描述符和模版字段,并且实际记录模版字段的值。

这里可能有点绕,结合源码说明一下。struct ima_template_field是模版字段的结构体,这里面记录了字段ID、用于初始化的函数指针、以及用于输出的函数指针。

1
2
3
4
5
6
7
struct ima_template_field {
const char field_id[IMA_TEMPLATE_FIELD_ID_MAX_LEN];
int (*field_init)(struct ima_event_data *event_data,
struct ima_field_data *field_data);
void (*field_show)(struct seq_file *m, enum ima_show_type show,
struct ima_field_data *field_data);
};

也就是说在ima_template_field中不会实际存储字段的值,而是存储字段的类型以及对字段的操作。v5.4.120版本内核定义的字段ID有以下几类:

  • “d”: 事件摘要,如被度量的文件摘要,使用SHA1或MD5计算
  • “n”: 事件名称,如被度量的文件名,最大长度为255字节
  • “d-ng”: 事件摘要,使用任意的哈希算法,以[\<hash algo>:]digest格式进行输出,仅当算法不是SHA1或MD5时输出前缀
  • “d-modsig”: 未附加modsig的文件摘要
  • “n-ng”: 无长度限制的事件名
  • “sig”: 文件签名
  • “modsig”:追加的文件签名
  • “buf”: 用于生成没有大小限制的哈希的缓冲区数据

模版描述符则决定了格式化输出的类型有哪些,定义的模版描述符有:

  • “ima”:格式化”d|n”
  • “ima-ng”:格式化”d-ng|n-ng”
  • “Ima-sig”:格式化”d-ng|n-ng|sig”
  • “ima-buf”:格式化”d-ng|n-ng|buf”
  • “ima-modsig”:格式化”d-ng|n-ng|sig|d-modsig|modsig”

接下来视角转到描述符和条目结构体,ima_template_field在ima_template_desc中是以一个指针数组进行存储的,该数组的下标与ima_template_entry中的template_data数组下标是一一对应的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct ima_template_desc {
struct list_head list;
char *name;
char *fmt;
int num_fields;
const struct ima_template_field **fields;
};

struct ima_template_entry {
int pcr;
u8 digest[TPM_DIGEST_SIZE]; /* sha1 or md5 measurement hash */
struct ima_template_desc *template_desc; /* template descriptor */
u32 template_data_len;
struct ima_field_data template_data[0]; /* template related data */
};

本文虽然并不是以源码分析为主,但是提及上述三个结构体对接下来的模版哈希计算规则有帮助。

模版哈希

模版哈希是对模版条目内所具有的全部字段计算哈希。由函数ima_calc_field_array_hash_tfm(位于security/integrity/ima/ima_crypto.c)可以看出,计算方式为:

  • 如果模版名不是”ima”,则先update字段长度,再update字段的值
  • 如果模版名为”ima”,则不会将字段长度加入到摘要中;并且由于”ima”模版要求事件名(如文件名)最大长度不超过255字节,因此会对事件名进行截断再进行计算。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static int ima_calc_field_array_hash_tfm(struct ima_field_data *field_data,
struct ima_template_desc *td,
int num_fields,
struct ima_digest_data *hash,
struct crypto_shash *tfm)
{
SHASH_DESC_ON_STACK(shash, tfm);
int rc, i;

shash->tfm = tfm;

hash->length = crypto_shash_digestsize(tfm);

rc = crypto_shash_init(shash);
if (rc != 0)
return rc;

for (i = 0; i < num_fields; i++) {
u8 buffer[IMA_EVENT_NAME_LEN_MAX + 1] = { 0 };
u8 *data_to_hash = field_data[i].data;
u32 datalen = field_data[i].len;
u32 datalen_to_hash =
!ima_canonical_fmt ? datalen : cpu_to_le32(datalen);

if (strcmp(td->name, IMA_TEMPLATE_IMA_NAME) != 0) {
rc = crypto_shash_update(shash,
(const u8 *) &datalen_to_hash,
sizeof(datalen_to_hash));
if (rc)
break;
} else if (strcmp(td->fields[i]->field_id, "n") == 0) {
memcpy(buffer, data_to_hash, datalen);
data_to_hash = buffer;
datalen = IMA_EVENT_NAME_LEN_MAX + 1;
}
rc = crypto_shash_update(shash, data_to_hash, datalen);
if (rc)
break;
}

if (!rc)
rc = crypto_shash_final(shash, hash->digest);

return rc;
}

参考资料

  1. Integrity Measurement Architecture (IMA)

  2. keyctl(1) — Linux manual page

  3. Trusted and Encrypted Keys

  4. tpm2_createprimary - Man Page

  5. tpm2_evictcontrol - Man Page

  6. Encrypted keys for the eCryptfs filesystem

  7. evmctl - IMA/EVM signing utility

  8. tpm2_readpublic.1

  9. Integrity Measurement Architecture

  10. ima_policy

  11. IMA-templates

Author

Chaos Chen

Posted on

2020-11-25

Updated on

2023-06-30

Licensed under

Commentaires