kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

内核模块签名--详解

验证模块签名

kernel/module.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SYSCALL_DEFINE3(init_module, void __user *, umod,
		unsigned long, len, const char __user *, uargs)
{
	int err; 
	struct load_info info = { }; 

	err = may_init_module();
	if (err)
		return err; 

	pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",
	       umod, len, uargs);

	err = copy_module_from_user(umod, len, &info);
	if (err)
		return err; 

	return load_module(&info, uargs, 0);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/* Allocate and load the module: note that size of section 0 is always
   zero, and we rely on this for optional sections. */
static int load_module(struct load_info *info, const char __user *uargs,
		       int flags)
{
	struct module *mod;
	struct module_ext *mod_ext;
	long err;

	err = module_sig_check(info);
	if (err)
		goto free_copy;
	...
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
static int module_sig_check(struct load_info *info)
{
	int err = -ENOKEY;
	const unsigned long markerlen = sizeof(MODULE_SIG_STRING) - 1;
	const void *mod = info->hdr;

	# 模块最后是 MODULE_SIG_STRING 字符串
	if (info->len > markerlen &&
	    memcmp(mod + info->len - markerlen, MODULE_SIG_STRING, markerlen) == 0) {
		/* We truncate the module to discard the signature */
		info->len -= markerlen;
		err = mod_verify_sig(mod, &info->len); // 检验签名
	}

	if (!err) {
		info->sig_ok = true;
		return 0;
	}

	/* Not having a signature is only an error if we're strict. */
	if ((err == -ENOKEY && !sig_enforce) && (get_securelevel() <= 0))
		err = 0;

	return err;
}
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/*
 * Verify the signature on a module.
 */
int mod_verify_sig(const void *mod, unsigned long *_modlen)
{
	struct public_key_signature *pks;
	struct module_signature ms;
	struct key *key;
	const void *sig;
	size_t modlen = *_modlen, sig_len;
	int ret;

	pr_devel("==>%s(,%zu)\n", __func__, modlen);

	if (modlen <= sizeof(ms))
		return -EBADMSG;

	# 去除 MODULE_SIG_STRING 后,文件末尾是定义个字段长度的结构
	memcpy(&ms, mod + (modlen - sizeof(ms)), sizeof(ms));
	modlen -= sizeof(ms);

	# 签名长度
	sig_len = be32_to_cpu(ms.sig_len);
	if (sig_len >= modlen)
		return -EBADMSG;
	modlen -= sig_len;

	# 签名者长度,签名的标识ID长度,ID一般是一个20B的串
	if ((size_t)ms.signer_len + ms.key_id_len >= modlen)
		return -EBADMSG;
	modlen -= (size_t)ms.signer_len + ms.key_id_len;

	*_modlen = modlen;
	sig = mod + modlen;

	/* For the moment, only support RSA and X.509 identifiers */
	if (ms.algo != PKEY_ALGO_RSA ||
	    ms.id_type != PKEY_ID_X509)
		return -ENOPKG;

	if (ms.hash >= PKEY_HASH__LAST ||
	    !hash_algo_name[ms.hash])
		return -ENOPKG;

	# 查找 .system_keyring
	key = request_asymmetric_key(sig, ms.signer_len,
				     sig + ms.signer_len, ms.key_id_len);
	if (IS_ERR(key))
		return PTR_ERR(key);

	# 摘要
	pks = mod_make_digest(ms.hash, mod, modlen);
	if (IS_ERR(pks)) {
		ret = PTR_ERR(pks);
		goto error_put_key;
	}

	# hash算法的额外前缀
	ret = mod_extract_mpi_array(pks, sig + ms.signer_len + ms.key_id_len,
				    sig_len);
	if (ret < 0)
		goto error_free_pks;

	# 验证签名
	ret = verify_signature(key, pks);
	pr_devel("verify_signature() = %d\n", ret);

error_free_pks:
	mpi_free(pks->rsa.s);
	kfree(pks);
error_put_key:
	key_put(key);
	pr_devel("<==%s() = %d\n", __func__, ret);
	return ret;
}
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/*
 * Request an asymmetric key.
 */
static struct key *request_asymmetric_key(const char *signer, size_t signer_len,
					  const u8 *key_id, size_t key_id_len)
{
	key_ref_t key;
	size_t i;
	char *id, *q;

	pr_devel("==>%s(,%zu,,%zu)\n", __func__, signer_len, key_id_len);

	/* Construct an identifier. */
	id = kmalloc(signer_len + 2 + key_id_len * 2 + 1, GFP_KERNEL);
	if (!id)
		return ERR_PTR(-ENOKEY);

	memcpy(id, signer, signer_len);

	q = id + signer_len;
	*q++ = ':';
	*q++ = ' ';
	for (i = 0; i < key_id_len; i++) {
		*q++ = hex_asc[*key_id >> 4];
		*q++ = hex_asc[*key_id++ & 0x0f];
	}

	*q = 0;

	pr_debug("Look up: \"%s\"\n", id);

#ifdef CONFIG_SYSTEM_BLACKLIST_KEYRING
	key = keyring_search(make_key_ref(system_blacklist_keyring, 1),
				   &key_type_asymmetric, id);
	if (!IS_ERR(key)) {
		/* module is signed with a cert in the blacklist.  reject */
		pr_err("Module key '%s' is in blacklist\n", id);
		key_ref_put(key);
		kfree(id);
		return ERR_PTR(-EKEYREJECTED);
	}
#endif

	key = keyring_search(make_key_ref(system_trusted_keyring, 1),
			     &key_type_asymmetric, id);
	if (IS_ERR(key))
		pr_warn("Request for unknown module key '%s' err %ld\n",
			id, PTR_ERR(key));
	kfree(id);

	if (IS_ERR(key)) {
		switch (PTR_ERR(key)) {
			/* Hide some search errors */
		case -EACCES:
		case -ENOTDIR:
		case -EAGAIN:
			return ERR_PTR(-ENOKEY);
		default:
			return ERR_CAST(key);
		}
	}

	pr_devel("<==%s() = 0 [%x]\n", __func__, key_serial(key_ref_to_ptr(key)));
	return key_ref_to_ptr(key);
}

内核密匙注册

kernel/system_keyring.c

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/cred.h>
#include <linux/err.h>
#include <keys/asymmetric-type.h>
#include <keys/system_keyring.h>
#include "module-internal.h"

struct key *system_trusted_keyring;
EXPORT_SYMBOL_GPL(system_trusted_keyring);
#ifdef CONFIG_SYSTEM_BLACKLIST_KEYRING
struct key *system_blacklist_keyring;
#endif

extern __initconst const u8 system_certificate_list[];
extern __initconst const unsigned long system_certificate_list_size;

/*
 * Load the compiled-in keys
 */
static __init int system_trusted_keyring_init(void)
{
	pr_notice("Initialise system trusted keyring\n");

	system_trusted_keyring =
		keyring_alloc(".system_keyring",
			      KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
			      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
			      KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH),
			      KEY_ALLOC_NOT_IN_QUOTA, NULL);
	if (IS_ERR(system_trusted_keyring))
		panic("Can't allocate system trusted keyring\n");

	set_bit(KEY_FLAG_TRUSTED_ONLY, &system_trusted_keyring->flags);

#ifdef CONFIG_SYSTEM_BLACKLIST_KEYRING
	system_blacklist_keyring = keyring_alloc(".system_blacklist_keyring",
				    KUIDT_INIT(0), KGIDT_INIT(0),
				    current_cred(),
				    (KEY_POS_ALL & ~KEY_POS_SETATTR) |
				    KEY_USR_VIEW | KEY_USR_READ,
				    KEY_ALLOC_NOT_IN_QUOTA, NULL);
	if (IS_ERR(system_blacklist_keyring))
		panic("Can't allocate system blacklist keyring\n");

	set_bit(KEY_FLAG_TRUSTED_ONLY, &system_blacklist_keyring->flags);
#endif

	return 0;
}

/*
 * Must be initialised before we try and load the keys into the keyring.
 */
device_initcall(system_trusted_keyring_init);

/*
 * Load the compiled-in list of X.509 certificates.
 */
static __init int load_system_certificate_list(void)
{
	key_ref_t key;
	const u8 *p, *end;
	size_t plen;

	pr_notice("Loading compiled-in X.509 certificates\n");

	p = system_certificate_list;
	end = p + system_certificate_list_size;
	while (p < end) {
		/* Each cert begins with an ASN.1 SEQUENCE tag and must be more
		 * than 256 bytes in size.
		 */
		if (end - p < 4)
			goto dodgy_cert;
		if (p[0] != 0x30 &&
		    p[1] != 0x82)
			goto dodgy_cert;
		plen = (p[2] << 8) | p[3];
		plen += 4;
		if (plen > end - p)
			goto dodgy_cert;

		key = key_create_or_update(make_key_ref(system_trusted_keyring, 1),
					   "asymmetric",
					   NULL,
					   p,
					   plen,
					   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
					   KEY_USR_VIEW | KEY_USR_READ),
					   KEY_ALLOC_NOT_IN_QUOTA |
					   KEY_ALLOC_TRUSTED);
		if (IS_ERR(key)) {
			pr_err("Problem loading in-kernel X.509 certificate (%ld)\n",
			       PTR_ERR(key));
		} else {
			set_bit(KEY_FLAG_BUILTIN, &key_ref_to_ptr(key)->flags);
			pr_notice("Loaded X.509 cert '%s'\n",
				  key_ref_to_ptr(key)->description);
			key_ref_put(key);
		}
		p += plen;
	}

	return 0;

dodgy_cert:
	pr_err("Problem parsing in-kernel X.509 certificate list\n");
	return 0;
}
late_initcall(load_system_certificate_list);

内核信任密匙是在编译的时候收集到的。收集 *.x509

kernel/Makefile

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
46
47
48
49
50
51
52
obj-$(CONFIG_SYSTEM_TRUSTED_KEYRING) += system_keyring.o system_certificates.o
...
...

###############################################################################
#
# Roll all the X.509 certificates that we can find together and pull them into
# the kernel so that they get loaded into the system trusted keyring during
# boot.
#
# We look in the source root and the build root for all files whose name ends
# in ".x509".  Unfortunately, this will generate duplicate filenames, so we
# have make canonicalise the pathnames and then sort them to discard the
# duplicates.
#
###############################################################################
ifeq ($(CONFIG_SYSTEM_TRUSTED_KEYRING),y)
X509_CERTIFICATES-y := $(wildcard *.x509) $(wildcard $(srctree)/*.x509)
X509_CERTIFICATES-$(CONFIG_MODULE_SIG) += signing_key.x509
X509_CERTIFICATES := $(sort $(foreach CERT,$(X509_CERTIFICATES-y), \
			$(or $(realpath $(CERT)),$(CERT))))

X509_TOOL_CERTIFICATES := $(wildcard $(srctree)/tool_certs/*.pub)

ifeq ($(X509_CERTIFICATES),)
$(warning *** No X.509 certificates found ***)
endif

ifneq ($(wildcard $(obj)/.x509.list),)
ifneq ($(shell cat $(obj)/.x509.list),$(X509_CERTIFICATES))
$(info X.509 certificate list changed)
$(shell rm $(obj)/.x509.list)
endif
endif

ifneq ($(wildcard $(obj)/.tool_x509.list),)
ifneq ($(shell cat $(obj)/.tool_x509.list),$(X509_TOOL_CERTIFICATES))
$(info X.509 tool_certificate list changed)
$(shell rm $(obj)/.tool_x509.list)
endif
endif

kernel/system_certificates.o: $(obj)/x509_certificate_list $(obj)/x509_tool_certificate_list

quiet_cmd_x509certs  = CERTS   $@  
	cmd_x509certs  = cat $(X509_CERTIFICATES) /dev/null >$@ $(foreach X509,$(X509_CERTIFICATES),; echo "  - Including cert $(X509)")
quiet_cmd_tool_x509certs  = CERTS   $@  
	cmd_tool_x509certs  = cat $(X509_TOOL_CERTIFICATES) /dev/null >$@ $(foreach X509,$(X509_TOOL_CERTIFICATES),; echo "  - Including cert $(X509)")

targets += $(obj)/x509_certificate_list
$(obj)/x509_certificate_list: $(X509_CERTIFICATES) $(obj)/.x509.list
	$(call if_changed,x509certs)

kernel/system_certificates.S

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
#include <linux/export.h>
#include <linux/init.h>

	__INITRODATA

	.align 8
	.globl VMLINUX_SYMBOL(system_certificate_list)
VMLINUX_SYMBOL(system_certificate_list):
__cert_list_start:
#ifdef CONFIG_MODULE_SIG
#ifdef CONFIG_MODULE_SIG_FORCE
	.incbin "kernel/x509_certificate_list"
#endif
#endif
__cert_list_end:

	.align 8
	.globl VMLINUX_SYMBOL(system_certificate_list_size)
VMLINUX_SYMBOL(system_certificate_list_size):
#ifdef CONFIG_64BIT
	.quad __cert_list_end - __cert_list_start
#else
	.long __cert_list_end - __cert_list_start
#endif

	.align 8
	.globl VMLINUX_SYMBOL(tool_certificate_list)
VMLINUX_SYMBOL(tool_certificate_list):
__tool_cert_list_start:
#ifdef CONFIG_MODULE_SIG
#ifdef CONFIG_MODULE_SIG_FORCE
	.incbin "kernel/x509_tool_certificate_list"
#endif
#endif
__tool_cert_list_end:

	.align 8
	.globl VMLINUX_SYMBOL(tool_certificate_list_size)
VMLINUX_SYMBOL(tool_certificate_list_size):
#ifdef CONFIG_64BIT
	.quad __tool_cert_list_end - __tool_cert_list_start
#else
	.long __tool_cert_list_end - __tool_cert_list_start
#endif

往内核插入自己密匙

kernel/system_keyring.c copy出来独立模块,将自己公匙导入系统

内核模块签名--命令行

依据 scripts/sign-file, 命令行签名模块及验证签名

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
# 生成签名,密匙MOK_private.perm; 证书MOK.crt; DER格式证书MOK.der
openssl req -newkey rsa:4096 -nodes -keyout MOK_private.perm -new -x509 -sha512 -days 3650 -subj "/CN=my Machine Owner Key/" -out MOK.crt
openssl req -newkey rsa:4096 -nodes -keyout MOK_private.perm -new -x509 -sha512 -days 3650 -subj "/CN=test.com/" -out MOK.crt
openssl x509 -outform DER -in MOK.crt -out MOK.der

# pem 转 crt
openssl x509 -in MOK.pem -out MOK.crt

# 从密匙、证书提取公匙
openssl rsa -in MOK_private.perm -pubout -out MOK_pub.perm
openssl x509 -pubkey -in MOK.crt > MOK_pub.perm


# 从ko中提取摘要
openssl dgst -sha512 -binary test.ko.tmp > test.ko.sha512

# 依据 scripts/sign-file, 需要在摘要前加些东西再做签名
./a.out > test.ko.dgst
cat test.ko.sha512 >> test.ko.dgst

# 对摘要签名
openssl rsautl -sign -in test.ko.dgst -out test.ko.sig -inkey MOK_private.key

# 解密签名得到摘要
openssl rsautl -verify -inkey MOK.crt -certin -in test.ko.sig -o test.ko.verify1
openssl rsautl -verify -inkey MOK_pub.key -pubin -in test.ko.sig -o test.ko.verify2
diff test.ko.verify1 test.ko.dgst

# 直接用公匙验证签名
openssl dgst -sha512 -verify MOK_pub.key -signature test.ko.sig test.ko.tmp

a.c

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main()
{
	int i;
	char a[] = {    0x30, 0x51, 0x30, 0x0d, 0x06, 0x09,
			0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
			0x05, 0x00, 0x04, 0x40};

	for (i = 0; i < 19; i ++)
		printf("%c", a[i]);
	return 0;
}

scripts/sign-file

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
不同算法需要在摘要前加下面内容
315 #
316 # Digest the data
317 #
318 my $prologue;
319 if ($dgst eq "sha1") {
320     $prologue = pack("C*",
321                      0x30, 0x21, 0x30, 0x09, 0x06, 0x05,
322                      0x2B, 0x0E, 0x03, 0x02, 0x1A,
323                      0x05, 0x00, 0x04, 0x14);
324     $hash = 2;
325 } elsif ($dgst eq "sha224") {
326     $prologue = pack("C*",
327                      0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09,
328                      0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04,
329                      0x05, 0x00, 0x04, 0x1C);
330     $hash = 7;
331 } elsif ($dgst eq "sha256") {
332     $prologue = pack("C*",
333                      0x30, 0x31, 0x30, 0x0d, 0x06, 0x09,
334                      0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
335                      0x05, 0x00, 0x04, 0x20);
336     $hash = 4;
337 } elsif ($dgst eq "sha384") {
338     $prologue = pack("C*",
339                      0x30, 0x41, 0x30, 0x0d, 0x06, 0x09,
340                      0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02,
341                      0x05, 0x00, 0x04, 0x30);
342     $hash = 5;
343 } elsif ($dgst eq "sha512") {
344     $prologue = pack("C*",
345                      0x30, 0x51, 0x30, 0x0d, 0x06, 0x09,
346                      0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
347                      0x05, 0x00, 0x04, 0x40);
348     $hash = 6;
349 } else {
350     die "Unknown hash algorithm: $dgst\n";
351 }
352
353 my $signature;
354 if ($signature_file) {
355         $signature = read_file($signature_file);
356 } else {
357         #
358         # Generate the digest and read from openssl's stdout
359         #
360         my $digest;  # 先算摘要
361         $digest = readpipe("openssl dgst -$dgst -binary $module") || die "openssl dgst";
362 
363         #
364         # Generate the binary signature, which will be just the integer that
365         # comprises the signature with no metadata attached.
366         #
367         my $pid;     # 签名命令,签名的输入372行
368         $pid = open2(*read_from, *write_to,
369                      "openssl rsautl -sign -inkey $private_key -keyform PEM") ||
370             die "openssl rsautl";
371         binmode write_to; # 签名的输入是 $prologue . $digest
372         print write_to $prologue . $digest || die "pipe to openssl rsautl";
373         close(write_to) || die "pipe to openssl rsautl";
374 
375         binmode read_from;
376         read(read_from, $signature, 4096) || die "pipe from openssl rsautl";
377         close(read_from) || die "pipe from openssl rsautl";
378         waitpid($pid, 0) || die;
379         die "openssl rsautl died: $?" if ($? >> 8);
380 }
381 $signature = pack("n", length($signature)) . $signature,
382 

https://www.jianshu.com/p/215eee5dbb05

整篇文章经由对Signing Kernel Moudles For Security Boot实践整理而成。如果能看懂原版的话,建议看该网页

在我们安装一个自己编译的模块包后,需要modprobe xx 然而,可能出现required key not available这样的提示。

这是由于采用EFI的Linux系统限制只有通过签名的模块才能加载运行。如果你是安装自己编译的模块,就需要自己签名了。

1.需要安装依赖的工具:

1
2
3
4
5
sudo yum install openssl
sudo yum install kernel-devel
sudo yum install perl
sudo yum install mokutil
sudo yum install keyutils

2.对于System Key Rings的解释:

咱们的X.509 Keys放在哪儿呢?请看下表

1
2
3
4
Source of X.509 Keys     User Ability to Add Keys    Keys Loaded During Boot
UEFI Secure Boot "db"     Limited             .system_keyring
UEFI Secure Boot "dbx"        Limited             .system_keyring
Machine Owner Key (MOK) list  Yes             .system_keyring

密钥要经过系统验证,也就是说咱们的一对密钥中的公钥要加载进MOK中

3.检查自己是否是EFI

1
sudo keyctl list %:.system_keyring

你看到的就是MOK list

如果是EFI,你可以看到包含 EFI 字样的keyring。咱们在安装过程中,也要把自己的keyring也加到里面去。

4.生成自己的密钥对

生成密钥配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sudo cat << EOF > configuration_file.config
[ req ]
default_bits = 4096
distinguished_name = req_distinguished_name
prompt = no
string_mask = utf8only
x509_extensions = myexts

[ req_distinguished_name ]
O = <你的签名key的名字>
emailAddress = <你的E-mail>

[ myexts ]
basicConstraints=critical,CA:FALSE
keyUsage=digitalSignature
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid
EOF

你的名字和E-mail地址这些东西是为了标识你的签名密钥,毕竟是自己做的作品嘛。你还可以在 [req_distinguished_name] 部分添加更多信息,也可以删减。

生成密钥

1
2
3
4
sudo openssl req -x509 -new -nodes -utf8 -sha256 -days 36500 \
	-batch -config configuration_file.config -outform DER \
	-out public_key.der                   \  
	-keyout private_key.priv

5.登记你的公钥

公钥要登记在MOK list里

Centos7、RedHat EL7系系统,可以使用mokutil

1
sudo mokutil --import my_signing_key_pub.der

这时系统会要你为MOK登记设置一个密码

设置完密码后,重启:

1
sudo reboot

重启过程中会进入EFI的确认界面,输入刚刚设置的密码,一直确认就行

重启后,输入

1
sudo keyctl list %:.system_keyring

你会发现MOK list比以前多了一项,也就是你的签名

6.给你的模块签名

这里我结合我自己给wl模块签名的实例

这里 我的wl模块 来源于我安装了一个叫wl-kmod的包,这是无线网卡驱动,为了找到模块位置,我先输入:

1
rpm -ql kmod-wl

找到了wl.ko的位置在/lib/modules/3.10.0-514.10.2.el7.x86_64/extra/wl/wl.ko

如果能给安装包直接签名貌似更好,但是我是已经安装完才进行补救的

那么就是给wl.ko签名啦:

1
2
3
4
5
sudo perl /usr/src/kernels/$(uname -r)/scripts/sign-file \
	sha256 \
	/home/feyan/feyan_signing_key_pub.der\     #公钥文件(位置和名称视具体情况)
	/home/fayan/feyan_signing_key.priv\        #私钥文件(位置和名称视具体情况)
	/lib/modules/3.10.0-514.10.2.el7.x86_64/extra/wl/wl.ko   #模块文件

签名成功后,输入

1
sudo modprobe wl

载入模块没有问题,说明我的签名成功了

内核模块签名--密匙

https://www.ibm.com/developerworks/cn/linux/l-key-retention.html

Linux 密钥保留服务(Linux key retention service)是在 Linux 2.6 中引入的,它的主要意图是在 Linux 内核中缓存身份验证数据。远程文件系统和其他内核服务可以使用这个服务来管理密码学、身份验证标记、跨域用户映射和其他安全问题。它还使 Linux 内核能够快速访问所需的密钥,并可以用来将密钥操作(比如添加、更新和删除)委托给用户空间。

本文将概述 Linux 密钥保留服务,定义它的术语,帮助您快速掌握 Linux 密钥的使用方法。您将通过示例代码了解如何在内核模块中使用 Linux 密钥保留服务。在编写本文时使用的内核版本是 2.6.20。

什么是密钥?

密钥(key)是一组密码学数据、身份验证标记或某些相似的元素,它在内核中由 struct key 表示。在 Linux 内核源代码中,struct key 是在 include/linux/key.h 下定义的。

清单 1 给出 struct key 中一些重要的字段。注意,为了支持密钥,已经修改了 task_struct、user_struct 和 signal_struct。 清单 1. struct key 中的重要字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct key {
	atomic_t             usage;         /* number of references */
	key_serial_t         serial;        /* key serial number */
	struct key_type      *type;         /* type of key */
	time_t               expiry;        /* time at which key expires (or 0) */
	uid_t                uid;           /* UID */
	gid_t                gid;           /* GID */
	key_perm_t           perm;          /* access permissions */
	unsigned short       quotalen;      /* length added to quota */
	unsigned short       datalen;       /* payload data length
	char                 *description;
	union {
		unsigned long       value;
		void                *data;
		struct keyring_list *subscriptions;
	} payload;                          /* Actual security data */
	....
	....
};

密钥的属性

密钥具有以下属性:

序号(Serial number):一个惟一的 32 位非零正数。

类型(Type):Linux 密钥保留服务定义两个标准密钥类型:user 和 keyring。要添加新的密钥类型,必须由一个内核服务注册它。用户空间程序不允许创建新的密钥类型。密钥类型在内核中由 struct key_type 表示,这是在 include/linux/key.h 中定义的。key_type 结构的一些重要字段见清单 2。

清单 2. key_type 的重要字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct key_type {
	const char *name;
	size_t def_datalen;
     
	/* Operations that can be defined for a key_type */
	int (*instantiate)(struct key *key, const void *data, size_t datalen);
	int (*update)(struct key *key, const void *data, size_t datalen);
	int (*match)(const struct key *key, const void *desc);
	void (*revoke)(struct key *key);
	void (*destroy)(struct key *key);
	void (*describe)(const struct key *key, struct seq_file *p);
	long (*read)(const struct key *key, char __user *buffer, size_t buflen);
	....
	....
};

还可以将一组操作与一个密钥类型相关联。key_type 可以定义以下操作:

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
    instantiate 创建指定类型的一个新密钥。
    describe 输出描述这个密钥的文本。
    match 根据描述搜索密钥。
    destroy 清除与一个密钥相关的所有数据。
    request_key 搜索密钥。
    revoke 清除密钥数据并将密钥的状态改为 REVOKED。
    read 读取密钥数据。
    update 修改密钥。
  描述(Description):一个描述密钥的可输出字符串。这个属性还可以用来执行搜索操作。
  访问控制信息(Access control information):每个密钥有一个所有者 ID、一个 GID 和一个权限掩码,权限掩码表示如何响应用户级或内核级程序。权限掩码给四个可能的密钥访问者类型各分配 8 位:所有者、用户、组和其他。在这 8 位中,只定义了 6 位。可能的权限如下:
    View 允许权限持有者查看密钥属性。
    Read 允许权限持有者读取密钥并列出 keyring 的密钥。
    Write 允许权限持有者修改密钥或 keyring 的有效内容和修改链接的密钥。
    Search 允许权限持有者搜索 keyring 和寻找密钥。
    Link 允许权限持有者将特定的密钥或 keyring 链接到 keyring。
    Set Attribute 允许权限持有者设置密钥的 UID、GID 和权限掩码。 
过期时间(Expiry Time):密钥的生存期。密钥也可以是永久的。
有效内容(Payload):实际的安全内容。可以通过 struct key_type 定义的操作用数据对有效内容进行实例化,还可以读取数据或修改数据。对于内核来说,有效内容仅仅是一组数据。
状态(State):密钥可以处于以下状态:
    UNINSTANTIATED:已经创建了密钥,但是还没有附加任何数据。
    INSTANTIATED:密钥已经实例化并附加了数据;这是一个完整的 状态。
    NEGATIVE:这是一个临时状态,表示前面对用户空间的调用失败了。
    EXPIRED:表示密钥已经超过了预定义的生存期。
    REVOKED:一个用户空间操作将密钥转移到这个状态。
    DEAD:key_type 取消了注册。

密钥类型

有两种预定义的密钥类型:keyring 和 user。

keyring 包含一组到其他密钥或 keyring 的链接。有六种标准的 keyring:

1
2
3
4
5
6
线程特有的
进程特有的
会话特有的
用户特有的会话
用户默认的会话
组特有的(还未实现)
限额

对于一个用户可以拥有的密钥和 keyring 的数量有限制(密钥数量限额),对于在密钥描述和有效内容中使用的信息量也有限制(密钥大小限额)。进程特有的和线程特有的 keyring 不在用户限额的范围内。

只有前三个 keyring 被自动搜索,自动搜索会按照次序进行。第四种类型(用户特有的会话 keyring)不被直接搜索,但是,它通常会链接到一个会话特有的 keyring。登录进程(比如 PAM)将绑定到用户默认的会话 keyring,直到创建另一个会话为止。

用户密钥由用户空间程序操作。

三个新的系统调用

Linux 密钥保留服务提供三个新的系统调用,用来在用户空间中操作密钥。第一个是 add_key:

1
2
3
key_serial_t add_key(const char *type, const char *desc,
		     const void *payload, size_t plen,
		     key_serial_t ring);

add_key 系统调用用来创建类型为 type、长度为 plen 的密钥。密钥描述由 desc 定义,它的有效内容由 payload 指定。密钥链接到 keyring ring。密钥类型可以是 user 或 keyring。其他任何密钥类型必须已经通过内核服务向内核注册,然后才能使用。如果密钥是 keyring 类型的,有效内容就应该是 NULL,plen 应该是零。

下一个新的系统调用是 request_key:

1
2
3
key_serial_t request_key(const char *type, const char *desc,
			 const char *callout_info, 
			 key_serial_t dest_keyring);

request_key 系统调用搜索一个进程 keyring,寻找一个密钥。搜索密钥的基本算法见清单 3。 清单 3. request_key 算法

1
2
3
4
5
6
7
8
9
10
11
search_into_each_subscribed_keyrings {
	if (key is found) {
		return(found key);
	} else {
		if (callout_info is NULL) {
			return(ERROR);
		} else {
			Execute /sbin/request-key and pass callout_info as argument;
		}
	}
}

关于 request_key 算法的工作原理的详细信息,请参考 Documentation/keys-request-key.txt(参见 参考资料 中的链接)。

最后,系统调用 keyctl 提供许多用来管理密钥的函数。可以根据传递给 keyctl 的第一个参数在密钥上执行各种操作。下面列出 keyctl 的一部分操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
KEYCTL_DESCRIBE 描述一个密钥。
KEYCTL_READ 从一个密钥读取有效内容数据。
KEYCTL_UPDATE 更新指定的密钥。
KEYCTL_LINK 将一个密钥链接到一个 keyring。
KEYCTL_UNLINK 将密钥或 keyring 与另一个 keyring 的链接取消。
KEYCTL_JOIN_SESSION_KEYRING 将一个会话 keyring 替换为新的会话 keyring。
KEYCTL_REVOKE 取消一个密钥。
KEYCTL_CHOWN 修改密钥的所有者。
KEYCTL_SETPERM 修改密钥的权限掩码。
KEYCTL_CLEAR 清除一个 keyring。
KEYCTL_SEARCH 在一个 keyring 树中搜索密钥。
KEYCTL_INSTANTIATE 对部分构造好的密钥进行实例化。
KEYCTL_NEGATE 取消对部分构造好的密钥的实例化。

关于 keyctl 的原型和 keyctl 可以执行的其他操作的更多信息,请参考 Linux 手册页。

管理密钥的内核 API

下面是几个用来管理密钥的最重要的 Linux 内核 API。要想了解更全面的信息,请下载并参考 Linux 密钥实现源代码文件(参见下面的 下载)。

register_key_type 用来定义新的密钥类型。

如果已经存在名称相同的密钥类型,那么 int register_key_type(struct key_type *type) 返回 EEXIT。

unregister_key_type 用来取消密钥类型的注册:

1
void unregister_key_type(struct key_type *type);

key_put 发布一个密钥:

1
void key_put(struct key *key);

request_key 搜索与给定的描述匹配的密钥:

1
2
3
struct key *request_key(const struct key_type *type,
                        const char *description,
                        const char *callout_string);

key_alloc 分配指定类型的密钥:

1
2
3
struct key *key_alloc(struct key_type *type, const char *desc, 
                      uid_t uid, gid_t gid, struct task_struct *ctx,
                      key_perm_t perm, unsigned long flags);

key_instantiate_and_link 对密钥进行实例化并将它链接到目标 keyring:

1
2
3
int key_instantiate_and_link(struct key *key,  const void *data,
                             size_t datalen, struct key *keyring,
                             struct key *instkey);

启用密钥服务

因为 Linux 密钥保留服务仍然非常新,在默认情况下 Linux 内核中关闭了这个服务。要想启用密钥服务,必须使用 CONFIG_KEYS=y 选项对内核进行配置。可以在内核编译的 make *config 步骤中 Security options 下面找到这个选项。

清单 4 给出在 Linux 内核中启用密钥服务的配置。 清单 4. 在 Linux 内核中启用密钥服务

1
2
3
4
5
6
7
8
9
".config" file ...
#
# Security options
#
CONFIG_KEYS=y
CONFIG_KEYS_DEBUG_PROC_KEYS=y
CONFIG_SECURITY=y
CONFIG_SECURITY_NETWORK=y
CONFIG_SECURITY_CAPABILITIES=y

密钥的源代码被组织在目录 linux-2.6.x/security/keys 中。

接下来,需要 下载并安装 keyutils 包。keyutils 包含 keyctl 命令,可以使用这个命令在密钥上执行各种操作。前面已经列出了 keyctl 的一部分操作。更多信息参见 Linux 手册页。

创建新的密钥类型

学习 Linux 密钥保留服务最容易的方式就是进行实践。下面的示例使用 Linux 密钥保留服务创建一个新类型的密钥。如果还没有 下载示例程序, 现在就执行这个步骤。执行 make 来构建内核模块和用户级程序的二进制代码。这些代码已经在 Linux 内核版本 2.6.20 上测试过了。

示例程序有两个组件:一个内核模块和一个用户空间程序。这个内核模块注册一个新的密钥类型。这个用户空间程序在预定义的 proc-entries 上执行 ioctl,这会导致对内核模块的调用。这个调用会产生一个新的密钥。然后,一个 “bash” shell 返回给用户,它带有新的会话 keyring 和链接到这个 keyring 的新类型的密钥。

因为这个用户空间程序将执行 ioctl,内核模块必须注册 proc_ioctl() 函数来处理 ioctl 请求。所有 ioctl 通信都使用 /proc 接口来进行。清单 5 给出在内核模块中声明的一个新的密钥类型。 清单 5. 声明新的密钥类型

1
2
3
4
5
6
7
truct key_type new_key_type = {
	.name = "mykey",
	.instantiate = instantiate_key,
	.describe = key_desc,
	.destroy = key_destroy,
	.match = key_match,
;

然后,模块在它的 init 函数中调用 register_key_type() 来注册这个新密钥类型(名为 mykey)。当内核模块收到 ioctl 请求时,它首先调用 key_alloc() 来分配一个新的密钥,从而创建一个会话 keyring。在成功调用 key_alloc() 之后,调用 key_instantiate_and_link() 对密钥进行实例化。在创建并实例化会话 keyring 之后,为用户的会话创建密钥。同样,依次调用 key_alloc() 和 key_instantiate_and_link()。成功完成这些调用之后,用户空间会话就有了一个新密钥。

示例程序中演示了所有这些步骤。

使用模块

创建了新的密钥类型之后,我们来试用一下这个内核模块。模块中的一个基本操作是查看一个进程与哪些 keyring 相关联,以及这些 keyring 包含哪些密钥和其他 keyring。调用 keyctl show 就可以在树结构中显示密钥。清单 6 显示在运行程序之前密钥的状态。 清单 6. 查看进程的 keyring

1
2
3
4
root@phoenix set.5]# keyctl show
ession Keyring
      -3 --alswrv      0     0  keyring: _ses.1976
       2 --alswrv      0     0   \_ keyring: _uid.0

清单 7 显示插入模块或者卸载模块或用户级程序的命令的输出。这些消息会放在一个系统日志文件中(通常是 /var/log/messages)。 清单 7. 插入内核模块

1
2
3
root@phoenix set.5]# insmod ./kernel.land/newkey.ko
oading the module ...
egistered "learning_key"

接下来,执行用户级程序。 清单 8. 执行用户级程序

1
2
3
4
5
6
7
8
9
root@phoenix set.5]# ./user.land/session
  
n /var/log/message, you will see similar output
nstalling session keyring:
eyring allocated successfully.
eyring instantiated and linked successfully.
ew session keyring installed successfully.
ey of new type allocated successfully.
ew key type linked to current session.

再看一下密钥的状态,见清单 9。 清单 9. 运行用户级程序之后的密钥状态

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
root@phoenix set.5]# keyctl show
ession Keyring
      -3 --alswrv      0     0  keyring: session.2621
39044642 --alswrv      0     0   \_ mykey: New key type
  
root@phoenix set.5]# cat /proc/keys
0000001 I-----     1 perm 1f3f0000     0     0 keyring   _uid_ses.0: 1/4
0000002 I-----     5 perm 1f3f0000     0     0 keyring   _uid.0: empty
253c622 I--Q--     1 perm 3f3f0000     0     0 mykey   New key type: 0
1a490da I--Q--     2 perm 3f3f0000     0     0 keyring   session.2621: 1/4
3670439 I--Q--     2 perm 1f3f0000     0     0 keyring   _ses.1977: 1/4
59d39b8 I--Q--     5 perm 1f3f0000     0     0 keyring   _ses.1976: 1/4
a14f259 I--Q--     3 perm 1f3f0000     0     0 keyring   _ses.1978: 1/4
root@phoenix set.5]# cat /proc/key-users
   0:     8 7/7 5/100 136/10000
  43:     2 2/2 2/100 56/10000
  48:     2 2/2 2/100 56/10000
  81:     2 2/2 2/100 56/10000
  786:     4 4/4 4/100 113/10000

"keyctl describe <Key>" command gives the description of key.
  
[root@phoenix set.5]# keyctl describe -3
       -3: alswrvalswrv------------     0     0 keyring: session.2621
[root@phoenix set.5]# keyctl describe 39044642
 39044642: alswrvalswrv------------     0     0 mykey: New key type
[avinesh@phoenix set.5]$ keyctl search -3 mykey "New key type"
39044642
[root@phoenix set.5]# exit
exit
Now back to our previous state  
[root@phoenix set.5]# keyctl show
Session Keyring
       -3 --alswrv      0     0  keyring: _ses.1976
        2 --alswrv      0     0   \_ keyring: _uid.0
[root@phoenix set.5]# rmmod ./kernel.land/newkey.ko 
Unloading the module.
Unregistered "learning_key"

与密钥相关的 proc 文件

/proc 中添加了两个文件来管理密钥:/proc/keys 和 /proc/key-users。我们来仔细看看这些文件。

/proc/keys

如果一个进程希望了解它可以查看哪些密钥,它可以通过读取 /proc/keys 获得这些信息。在配置内核时,必须启用这个文件,因为它允许任何用户列出密钥数据库。 清单 10. /proc/keys 文件

1
2
3
4
5
6
7
[root@phoenix set.5]# cat /proc/keys
00000001 I-----  1     perm    1f3f0000      0    0    keyring    _uid_ses.0 : 1/4
00000002 I-----  5     perm    1f3f0000      0    0    keyring    _uid.0     : empty
13670439 I--Q--  2     perm    1f3f0000      0    0    keyring    _ses.1977  : 1/4
159d39b8 I--Q--  6     perm    1f3f0000      0    0    keyring    _ses.1976  : 1/4
3a14f259 I--Q--  3     perm    1f3f0000      0    0    keyring    _ses.1978  : 1/4
[Serial][Flags][Usage][Expiry][Permissions][UID][GID][TypeName][Description] :[Summary]

*Source: linux_kernel_source/security/keys/proc.c:proc_keys_show()

在以上文件中看到的大多数字段来自 include/linux/key.h 中定义的 struct key。可能的标志值见清单 11。 清单 11. struct key 字段可能的标志值

1
2
3
4
5
6
I        Instantiated
R        Revoked
D        Dead
Q        Contributes to user's quota
U        Under construction by callback to user-space
N        Negative key
/proc/key-users

清单 12 显示 /proc/key-users 文件。 清单 12. /proc/key-users 文件

1
2
3
4
5
6
[root@phoenix set.5]# cat /proc/key-users
    0:     6 5/5 3/100 90/10000
   43:     2 2/2 2/100 56/10000
   48:     2 2/2 2/100 56/10000
   81:     2 2/2 2/100 56/10000
  786:     4 4/4 4/100 113/10000

清单 13 给出每个字段的含义。 清单 13. /proc/key-users 文件的字段

1
2
3
4
5
<UID>            User ID
<usage>          Usage count
<inst>/<keys>    Total number of keys and number instantiated
<keys>/<max>     Key count quota
<bytes><max>     Key size quota

*Source: linux_kernel_source/security/keys/proc.c:proc_key_users_show()

大多数字段是 security/keys/internal.h 中定义的 struct key_user 的字段。

结束语

Linux 密钥保留服务是一种新的机制,其用途是保存与安全相关的信息,让 Linux 内核可以快速地访问这些信息。这个服务仍然处于初级阶段,刚刚开始获得认可。OpenAFS 使用 Linux 密钥保留服务来实现进程身份验证组(PAG),NFSv4 和 MIT Kerberos 也使用它。Linux 密钥保留服务仍然在进行开发,以后可能会修改或改进。

下载资源

使用 Linux 密钥保留服务的示例应用程序 (key.retention.services.zip | 4KB)

相关主题