kk Blog —— 通用基础

date [-d @int|str] [+%s|"+%F %T"]

内核模块签名--命令行

依据 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
# 生成签名,密匙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 x509 -outform DER -in MOK.crt -out MOK.der

# 从密匙、证书提取公匙
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登记设置一个密码

设置完密码后,重启:

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   #模块文件

签名成功后,输入

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)

相关主题

openssl常用命令,签名、非对称加解密

http://blog.csdn.net/modianwutong/article/details/43059435

https://www.cnblogs.com/gordon0918/p/5363466.html

https://stackoverflow.com/questions/5140425/openssl-command-line-to-verify-the-signature


查看网站证书

1
openssl s_client -showcerts -connect www.baidu.com:443

证书

证书是一个经证书授权中心签过名的包含公钥及公钥拥有者信息的文件。证书授权中心(CA)对证书签名的过程即为证书的颁发过程。证书里面的公钥只属于某一个实体(网站,个人等),它的作用是防止一个实体伪装成另外一个实体。

证书可以保证非对称加密算法的合理性,假设A和B的通话过程如下:

1
2
3
4
5
6
 A -------> Hello (plain text) ---------> B
 A <------- Hello (plain text) ---------- B
 A <------ B Send Certificate to A --- B
 A ------- cipher text ------------------> B
 A <------ cipher text ------------------- B
  … …

A在接受了B发过来的证书以后,A,B就可以使用密文进行通信了。

如果C想伪装成B,应该怎么做呢?我们想象下面的通话过程:

1
2
3
 A-------> Hello (plain text) ---------> C
 A <------ Hello (plain text) ----------- C
 A <------ C Send Certificate to A --- C

此时A没有怀疑C的身份,理所当然的接受了C的证书,并继续进行下面的通信

1
2
3
 A------- cipher text ------------------> C
 A <------ cipher text ------------------- C
  … …

这样的情况下A是没有办法发现C是假的,A的用户名,密码,甚至卡号等重要信息都有可能被C截获。如果A在通话过程中要求取得B的证书,并验证证书里面的名字,如果发现名字与B不符合,就可以发现对方不是B。验证B的名字通过后,再继续通信过程。

那么,如果证书是假的怎么办?或者证书被修改过怎么办?此时就要用到签名信息了。数字证书除了包含证书拥有者的名字和公钥外,还应包含颁发证书的机构名称,证书序列号和其它一些可选信息。最重要的是,它包含了证书颁发机构(Certification Authority,简称CA)的签名信息。

通过检查证书里面CA的签名信息,就知道这个证书的确是由该CA签发的,然后你就可以检查证书里面的证书拥有者的名字,检查通过后,就可以提取公钥,继续通信了。这样做的基础是,你信任该CA,认为该CA没有颁发错误的证书。

CA是第三方机构,被你信任,由它保证证书的确发给了应该得到证书的人。这里需要解释一下,CA也是一个实体,它也有自己的公钥和私钥,否则怎么做数字签名?它也有自己的证书,你可以去它的站点下载它的证书,来验证签名。

CA也是分级别的,最高级别的CA叫RootCA,低一级别的CA的证书由它来颁发和签名。这样我们信任RootCA,那些由RootCA签名过的证书的CA就可以来颁发证书给实体或其它CA了。那RootCA谁给签名呢?他们自己给自己签名,叫自签名。

现在常用的证书都是采用X.509格式的,这是一个国际标准的证书格式。任何遵循该标准的应用程序都可以读,写X509格式的证书。

下面是一个证书的例子:

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
# openssl x509 -text -in test.crt
Certificate:
   Data:
       Version: 3 (0x2)
       Serial Number: 17209717939030827578 (0xeed53348d899a23a)
   Signature Algorithm: sha1WithRSAEncryption                             // 签名算法
       Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd            // 证书颁发者信息
       Validity
           Not Before: Jan 14 07:01:20 2015 GMT                           // 证书的有效期
           Not After : Feb 13 07:01:20 2015 GMT
       Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd           // 证书拥有者信息
       Subject Public Key Info:                                           //公钥信息
           Public Key Algorithm: rsaEncryption
                Public-Key: (512 bit)
                Modulus:
                   00:c5:63:8c:c8:32:b1:2e:15:58:a6:cd:22:f4:40:
                   ef:53:8e:e7:fa:4e:fd:d5:d9:fe:69:a2:c2:5a:fc:
                   20:4b:da:c9:17:49:35:4e:67:92:82:ec:4e:a7:a7:
                   1a:66:3a:c5:36:2e:74:77:30:7a:dd:65:5f:03:9a:
                   9b:2e:d0:b1:43
                Exponent: 65537 (0x10001)
       X509v3 extensions:                                                 //x509扩展信息
           X509v3 Subject Key Identifier:
               0E:DB:FE:E6:CF:FE:A6:C8:6D:38:06:A5:22:34:DA:82:A9:BE:42:B8
           X509v3 Authority Key Identifier:
               keyid:0E:DB:FE:E6:CF:FE:A6:C8:6D:38:06:A5:22:34:DA:82:A9:BE:42:B8
           X509v3 Basic Constraints:
               CA:TRUE
   Signature Algorithm: sha1WithRSAEncryption
        32:43:0c:e8:32:6f:30:10:c9:0f:a3:36:24:7c:a5:dc:da:8c:            // CA的签名
        c4:90:69:90:de:b1:1b:19:8e:b1:a5:35:ce:2e:7a:05:69:94:
        46:72:37:c2:2c:38:57:4a:0c:89:bc:90:95:03:af:f2:6f:a0:
        3f:13:5f:f0:90:a7:2c:bf:75:ee

算法

加密算法分为两种:对称加密算法和非对称加密算法;

对称加密算法:即信息的发送方和接收方使用同一个密钥去加密和解密数据。它的最大优势是加/解密速度快,适合于对大数据量进行加密,但密钥管理困难。AES,DES等都是常用的对称加密算法;
非对称加密算法:它需要使用不同的密钥来分别完成加密和解密操作,一个公开发布,即公开密钥,另一个由用户自己秘密保存,即私用密钥。信息发送者用公开密钥去加密,而信息接收者则用私用密钥去解密。公钥机制灵活,但加密和解密速度却比对称密钥加密慢得多。RSA,DSA等是常用的非对称加密算法;

所以在实际的应用中,人们通常将两者结合在一起使用,例如,对称密钥加密系统用于存储大量数据信息,而公开密钥加密系统则用于加密密钥。

另外还有一种我们需要知道的加密算法,叫做摘要算法,英文名是messagedigest,用来把任何长度的明文以一定规则变成固定长度的一串字符。那么我们在对文件做签名的时候,通常都是先使用摘要算法,获得固定长度的一串字符,然后对这串字符进行签名。

Base64不是加密算法,它是编码方式,用来在ASCII码和二进制码之间进行转换。

RSA

RSA是目前比较流行的非对称加密算法,所谓非对称,就是指该算法需要一对密钥,使用其中一个加密,则需要用另一个才能解密。下面简单介绍一下它的原理:

RSA的算法涉及三个参数,n、e1、e2。

其中,n是两个大质数p、q的积,n的二进制表示所占用的位数,就是所谓的密钥长度。

e1和e2是一对相关的值,e1可以任意取,但要求e1与(p-1)(q-1)互质;再选择e2,要求(e2e1) mod((p-1)*(q-1))=1。

(n,e1), (n,e2)就是密钥对。其中(n,e1)为公钥,(n,e2)为私钥。

RSA加解密的算法完全相同,设A为明文,B为密文,则:A=Be2 mod n;B=Ae1 mod n;(公钥加密体制中,一般用公钥加密,私钥解密)

e1和e2可以互换使用,即:

A=Be1mod n;B=Ae2 mod n;

关于RSA加密算法的详细介绍可以参考百度百科;

协议

SSL是Secure Sockets Layer(安全套接层协议)的缩写,可以在Internet上提供秘密性传输。Netscape公司在推出第一个Web浏览器的同时,提出了SSL协议标准。其目标是保证两个应用间通信的保密性和可靠性,可在服务器端和用户端同时实现支持。已经成为Internet上保密通讯的工业标准。

OpenSSL是一个强大的安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用。

OpenSSL整个软件包大概可以分成三个主要的功能部分:SSL协议库、应用程序以及密码算法库。OpenSSL的目录结构自然也是围绕这三个功能部分进行规划的。

Openssl目前最新的稳定版本是1.0.2,可以在openssl的官网下载openssl-1.0.2的源代码。

Android中已经集成了openssl,源码目录在:external/openssl

工具

RSA是目前最有影响力的公钥加密算法,它能抵抗到目前为止已知的绝大多数密码攻击。下面我们要介绍的工具将会主要涉及到秘钥的产生、管理,证书请求及证书的产生,数据加密、解密,算法签名及身份验证等;

genrsa

生成RSA私有密钥,用法如下:

1
2
3
4
5
6
7
8
9
openssl genrsa [-outfilename] [-passout arg] [-aes128] [-aes192] [-aes256] [-des] [-des3] [-idea][-f4] [-3]
[-rand file(s)] [-engine id] [numbits]

Options:
  -outfilename:将生成的私钥输出到指定文件,默认为标准输出;
  -passoutarg:如果对生成的密钥使用加密算法的话,可以使用”-passout”选项指定密码来源,这里的arg可以是”pass:password”,”file:pathname”,”stdin”等;
  -des|-des3|-idea:采用什么加密算法来加密生成的密钥,一般需要输入保护密码;
  -f4|3:生成密钥过程中使用的公共组件,65537或3,默认使用65537;
  numbits:密钥长度,必须是genrsa命令的最后一个参数,默认值是512;

注意:genrsa生成的私钥默认是PEM编码格式;

Example:

1)生成私钥:

1
openssl genrsa -out private.pem -3 -2048

rsa

RSA密钥管理工具,用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
openssl rsa [-informPEM|NET|DER] [-outform PEM|NET|DER] [-in filename] [-passin arg] [-outfilename] [-passout arg] [-sgckey] [-aes128] [-aes192] [-aes256] [-des] [-des3][-idea] [-text] [-noout] [-modulus] [-check] [-pubin] [-pubout]

Options:
  -informPEM|NET|DER:指定输入格式,可以是PEM,NET或DER格式;
  -outformPEM|NET|DER:指定输出格式,可以是PEM,NET或DER格式;
  -infilename:指定输入文件,如果是加密过的密钥,会要求你输入密码;
  -outfilename:指定输出文件名称;
  -passinarg:输入密钥的密码来源;
  -passoutarg:输出密钥如果加密的话,该参数用于指定密码的来源;
  -text:以只读方式输出密钥及组件信息;
  -noout:不打印密钥文件;
  -modulus:显示密钥的模数;
  -pubin:默认从输入文件中读取私钥,如果该选项打开的话,就可以从输入文件中读取公钥;
  -pubout:默认输出私钥,使用该选项的话,将会输出公钥,如果pubin打开的话,pubout也会被自动打开;另外使用该参数的话,可以由私钥导出公钥;
  -check:检查RSA密钥是否被破坏;

Example:

1)去掉私钥的保护密码

1
openssl rsa -in private.pem -out private_out.pem

2)用DES3算法加密密钥,需要输入保护密码

1
openssl rsa -in private.pem -des3 -out private_out.pem

3)把私钥文件从PEM格式转换为DER格式

1
openssl rsa -in private.pem -outform DER -out private.der

4)打印私钥组件信息

1
openssl rsa -in private.pem -text -noout

5)导出公钥文件

1
openssl rsa -in private.pem -out public.pem -pubout

6)检查密钥文件的完整性

1
openssl rsa -in private.pem -check -noout

rsautil

无论是使用公钥加密还是私钥加密,RSA每次能够加密的数据长度不能超过RSA密钥长度,并且根据具体的补齐方式不同输入的加密数据最大长度也不一样,而输出长度则总是跟RSA密钥长度相等。RSA不同的补齐方法对应的输入输入长度如下表

1
2
3
4
5
数据补齐方式       输入数据长度      输出数据长度  参数字符串
PKCS#1 v1.5       少于(密钥长度-11)字节   同密钥长度 -pkcs
PKCS#1 OAEP       少于(密钥长度-11)字节   同密钥长度 -oaep
PKCS#1 for SSLv23 少于(密钥长度-11)字节   同密钥长度 -ssl
不使用补齐       同密钥长度     同密钥长度 -raw

RSA工具指令,包含RSA算法签名,身份验证,加密/解密数据等功能,用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
openssl rsautl [-in file][-out file] [-inkey file] [-pubin] [-certin] [-sign] [-verify] [-encrypt][-decrypt] [-pkcs]
[-ssl] [-raw] [-hexdump] [-asn1parse]

Options:
  -infile:指定输入文件;
  -outfile:指定输出文件;
  -inkeyfile:加密/解密数据时使用的密钥文件,缺省使用RSA私钥;
  -pubin:如果设置该选项,则使用RSA公钥加密/解密数据;
  -certin:指定使用证书文件加密/解密,证书中包含RSA公钥;
  -sign:数据签名,需要指定RSA私钥;
  -verify:数据验证;
  -encrypt:数据加密;
  -decrypt:数据解密;
  -pkcs|-ssl|-raw:指定数据填充模式,分别代表:PKCS#1v1.5(缺省值),SSL v2填充模式或无填充。如果要签名,只能使用pkcs或raw选项;
  -hexdump:用十六进制输出数据;
  -asn1parse:按照ASN.1结构分析输出数据,详细可以查看asn1parse命令,和”-verify”选项一起使用威力强大;

Example:

1)公钥加密

1
openssl rsautl -encrypt -in test.txt -out test.enc -inkey public.pem -pubin

注意:如果使用证书加密的话,需要使用-certin选项,-inkey指定证书文件;

2)私钥解密

1
openssl rsautl -decrypt -in test.enc -out test.dec -inkey private.pem

3)私钥签名

1
openssl rsautl -sign -in test.txt -out test.sig -inkey private.pem

4)公钥验证

1
openssl rsautl -verify -in test.sig -out test.vfy -inkey public.pem -pubin

注意:如果使用证书验证签名,需要使用-certin选项,-inkey指定证书文件;

req

创建和处理证书请求的工具,它还能建立自签名的证书,做RootCA用。用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
openssl req [-inform PEM|DER][-outform PEM|DER] [-in filename] [-passin arg] [-out filename] [-passout arg][-text] [-pubkey] [-noout] [-verify] [-modulus] [-new] [-rand file(s)] [-newkeyrsa:bits] [-newkey alg:file] [-nodes] [-key filename] [-keyform PEM|DER][-keyout filename] [-keygen_engine id] [-[digest]] [-config filename] [-subjarg] [-multivalue-rdn] [-x509] [-days n] [-set_serial n] [-asn1-kludge][-no-asn1-kludge] [-newhdr] [-extensions section] [-reqexts section] [-utf8][-nameopt] [-reqopt] [-subject] [-subj arg] [-batch] [-verbose]


在创建证书请求(CSR)的过程中会要求用户输入一些必要的信息,包括位置、组织、邮箱等信息,可以把它们放在配置文件里,也可以放在命令行参数里;

Options:
  -text:以可读方式打印将CSR文件里的内容;
  -noout:不要打印CSR文件的编码信息;
  -pubkey:解析CSR文件里的公钥信息,并打印出来;
  -modulus:解析CSR文件里的公钥模数,并打印出来;
  -verify:检验请求文件的签名信息;
  -new:创建新的CSR,它会要求用户输入一些必要信息。如果没有指定-key参数,将会生成新的私钥;
  -newkeyarg:创建新的CSR和新的私钥,参数指示私钥算法等信息(类似rsa:1024);
  -keyfilename:指定私钥文件,该选项支持pkcs8格式的私钥;
  -keyoutfilename:如果产生了新的私钥,则把私钥保存到指定文件;
  -[digest]:指定证书的摘要算法,可以是sha1|md5等,默认使用哈希算法;
  -configfilename:指定config文件,配置文件可以包含创建CSR所需的各种信息;
  -x509:产生自签名的证书,而不是证书请求;
  -days:证书的有效期,缺省是30天;
  -subject:显示CSR的位置,组织等信息;
  -subjarg:设置CSR的位置,组织等信息,格式:/C=AU/ST=Some-State/type0=value0

关于配置文件的格式,可以参考openssl的在线帮助文档;

Example:

1)生成一个私钥,并使用该私钥产生证书请求

1
2
3
openssl genrsa -out private.pem 1024

openssl req -new -key private.pem -out req.pem

如果加上-sha256,则使用sha256摘要算法;

2)生成一个私钥,并使用该私钥产生证书请求,另一个方法

1
openssl req -newkey rsa:2048 -keyout private.pem -out req.pem

3)检测并验证证书请求

1
openssl req -verify -in req.pem -text -noout

4)制作自签名的根证书,使用已有私钥

1
openssl req -new -x509 -key private.pem -sha256 -out rootca.crt

5)制作自签名的根证书,创建新的私钥

1
openssl req -newkey rsa:2048 -keyout private.pem -x509 -sha256 -out rootca.crt

x509

证书管理工具,可以用来显示证书的内容,证书格式转换,为证书请求签名等。用法如下:

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
openssl x509 [-inform DER|PEM|NET][-outform DER|PEM|NET] [-keyform DER|PEM]

[-CAform DER|PEM] [-CAkeyform DER|PEM][-in filename] [-out filename] [-serial] [-hash] [-subject_hash] [-issuer_hash][-ocspid] [-subject] [-issuer] [-nameopt option] [-email] [-ocsp_uri][-startdate] [-enddate] [-purpose] [-dates] [-checkend num] [-modulus][-pubkey] [-fingerprint] [-alias] [-noout] [-trustout] [-clrtrust] [-clrreject][-addtrust arg] [-addreject arg] [-setalias arg] [-days arg] [-set_serial n][-signkey filename] [-passin arg] [-x509toreq] [-req] [-CA filename] [-CAkeyfilename] [-CAcreateserial] [-CAserial filename] [-force_pubkey key] [-text][-certopt option] [-C] [-md2|-md5|-sha1|-mdc2] [-clrext] [-extfile filename][-extensions section] [-engine id]

Options:

由于x509的选项比较多,下面我们来分类介绍:

1)输入/输出相关选项
  -inform|-outformDER|PEM|NET:指定输入/输出文件的编码格式;
  -in|-outfilename:指定输入/输出文件;
  -md2|-md5|-sha1|-mdc2:指定摘要算法,默认使用哈希算法;

2)显示选项
  -text:以可读方式打印证书内容;
  -pubkey:解析证书里面的公钥信息,并打印出来;
  -modulus:解析证书里面的公钥模数信息,并打印出来;
  -serial:显示证书的序列号;
  -subject:显示证书拥有者的信息;
  -issuer:显示证书颁发者的名字;
  -email:显示邮箱信息;
  -startdate|-enddate |-dates:显示证书有效期,起始和结束时间;
  -fingerprint:显示DER格式证书的DER版本信息;
  -C:以C语言源文件方式,输出证书;

3)信任相关的选项
  -purpose:显示证书用途;

4)签名相关选项
  -signkeyfilename:使用给定的私钥,对输入文件进行签名;如果输入文件是证书,那么它的颁发者就会被设置成其拥有者,其它项也会被设置成符合自签名特征的证书项;如果输入文件是证书请求,那么就会产生一个自签名的证书;
  -days:证书有效期;
  -x509toreq:把证书转换成证书请求,需要-signkey选项指定私钥;
  -req:默认输入文件是一个证书,使用该选项的话,指示输入文件是一个证书请求;
  -CAfilename:指定签名用的CA证书文件,该选项通常和-req一起用,对证书请求实施自签名;
  -CAkeyfilename:指定CA的私钥文件,对证书进行签名;

Examples:

1)显示证书内容

1
openssl x509 -in cert.pem –noout -text

2)显示证书序列号

1
openssl x509 -in cert.pem -noout -serial

3)显示证书的subject信息

1
openssl x509 -in cert.pem -noout -subject

4)显示证书的fingerprint信息

1
openssl x509 -in cert.pem -noout -fingerprint

5)证书格式转换

1
2
3
openssl x509 -in cert.pem -inform PEM -out cert.der -outform DER

openssl x509 -in cert.der -inform DER -out cert.pem -outform PEM

6)把证书转换为证书请求

1
openssl x509 -x509toreq -in cert.pem -out cert.csr -signkey private.pem

7)把证书请求转换为证书

1
openssl x509 -req -in careq.pem -extfile openssl.cnf -extensions v3_ca -signkey key.pem -out cacert.pem

8)从证书里面提取公钥

1
openssl x509 -pubkey -in cert.pem >pub.pem

9)以C语言源文件方式输出证书,PEM格式转换为DER格式

1
openssl x509 -in cert.pem -out cert.der -outform DER -C > cert.c

asn1parse

asn1parse是一个诊断工具,可以解析ASN.1结构的密钥,证书等,用法如下:

1
2
3
4
5
6
openssl asn1parse [-informPEM|DER] [-in filename] [-out filename] [-noout] [-offset number]
[-length number] [-i] [-oid filename][-dump] [-dlimit num] [-strparse offset] [-genstr string]
[-genconf file] [-strictpem]

Options:
  -i:以缩进方式显示ASN.1结构

Example:

1)解析文件:

1
openssl asn1parse -in file.pem

2)解析DER格式的文件

1
openssl asn1parse -inform file.der -infile.der

dgst

摘要算法,用法如下:

1
2
3
4
5
6
7
8
9
10
11
openssl dgst[-sha|-sha1|-mdc2|-ripemd160|-sha224|-sha256|-sha384|-sha512|-md2|-md4|-md5|-dss1][-c] [-d] [-hex] [-binary] [-r] [file...]

所谓摘要算法,就是把任何长度的明文以一定规则变成固定长度的一串字符。

Options:
  -sha|-sha1 |-sha256 |-md5:算法名称;
  -c:打印出哈希结果的时候用冒号来分隔开;
  -d:详细打印出调试信息;
  -hex:以十六进制输出哈希结果,默认值;
  -binary:以二进制输出哈希结果;
  file:要哈希的文件;

Example:

1)使用sha256摘要算法,并以二进制方式输出

1
openssl dgst -sha256 -binary file.txt

PEM 和 DER 格式

PEM和DER是两种不同的编码格式,PEM使用ASCII(base64)编码方式,PEM格式文件的第一行和最后一行指明文件内容,DER采用二进制编码格式;使用openssl工具生成的密钥或证书,默认使用PEM编码格式;

实例

Image签名

高通平台支持bootimg签名和校验功能,下面我们先来看看它的签名脚本:

1
2
3
4
5
6
7
8
9
10
define build-boot-image
 mv -f $(1) $(1).nonsecure
 openssl dgst -$(TARGET_SHA_TYPE) -binary $(1).nonsecure >$(1).$(TARGET_SHA_TYPE)
 openssl rsautl -sign -in $(1).$(TARGET_SHA_TYPE) -inkey$(PRODUCT_PRIVATE_KEY) -out $(1).sig
 dd if=/dev/zero of=$(1).sig.padded bs=$(BOARD_KERNEL_PAGESIZE) count=1
 dd if=$(1).sig of=$(1).sig.padded conv=notrunc
 cat $(1).nonsecure $(1).sig.padded > $(1).secure
 rm -rf $(1).$(TARGET_SHA_TYPE) $(1).sig $(1).sig.padded
 mv -f $(1).secure $(1)
endef

这里定义了一个build-boot-image宏,使用方法如下:

1
2
3
4
$(INSTALLED_BOOTIMAGE_TARGET) :=$(PRODUCT_OUT)/boot.img
INSTALLED_SEC_BOOTIMAGE_TARGET :=$(PRODUCT_OUT)/boot.img.secure
$(INSTALLED_SEC_BOOTIMAGE_TARGET):$(INSTALLED_BOOTIMAGE_TARGET)
   $(hide) $(callbuild-boot-image,$(INSTALLED_BOOTIMAGE_TARGET))

上面就是对bootimg实施签名操作的脚本,build-boot-image宏做了什么事呢?

1)对bootimg实施摘要算法

1
openssl dgst -sha256 -binary boot.img.nonsecure > boot.img.sha256

2)制作签名

1
openssl rsautl -sign -inboot.img.sha256 -inkey qcom.key -out boot.img.sig

3)填充签名

1
2
dd if=/dev/zero of=boot.img.sig.paddedbs=2048 count=1
dd if=boot.img.sig of=boot.img.sig.paddedconv=notrunc

生成好的签名已经保存到boot.img.sig.padded,文件大小是2048字节,用0填充;

4)把签名打到image上,并删除中间文件

1
2
3
4
cat boot.img.nonsecure boot.img.sig.padded > boot.img.secure
rm -rf $boot.img.sha256 boot.img.sigboot.img.sig.padded

mv -f boot.img.secure boot.img

Image签名完成,签名信息保存在image的最后2048字节,不足的都用0来填充;

验证签名又是怎样的过程呢?前面用到了qcom.key,这个是高通提供的私钥,那么要验证签名,我们就需要用这个私钥制作的证书。验证过程是在LK里面完成的,所以我们还需要把证书转换为C源文件方式,方法很简单,用下面几条命令就可以了:

1
2
3
openssl req -new -x509 -key qcom.key -days11324 -sha256 -out qcom.crt

openssl x509 -inform PEM -in qcom.crt-outform DER -out qcom.der > cert.c

好了,cert.c就是以C源文件方式存储的证书,替换到LK里面相应的数组就可以了;

制作testkey

testkey是用来给apk签名的,保存在build/target/product/security目录下,testkey.pk8是私钥,

testkey.x509.pem是证书,那么这个key和证书是如何生成的呢?README写的很清楚:

1
2
development/tools/make_key testkey '/C=US/ST=California/L=MountainView/O=Android/OU=Android/
 CN=Android/emailAddress=android@android.com'

development/tools/make_key是制作testkey的脚本文件,我们看几条比较重要的命令吧:

1)生成私钥

1
openssl genrsa -f4 2048 | tee ${one}> ${two}

2)制作证书

1
openssl req -new -x509 ${hash} -key${two} -out $1.x509.pem -days 10000 -subj "$2"

3)制作pk8格式私钥

1
openssl pkcs8 -in ${one} -topk8-outform DER -out $1.pk8 –nocrypt

Help

OpenSSL提供了非常丰富的帮助信息,如果对哪个命令或参数不了解,可以很方便的使用帮助来查看命令的详细用法; 命令列表

使用下面命令查看openssl支持的命令和算法列表:

1
2
3
4
5
6
7
8
openssl [ list-standard-commands | list-message-digest-commands | list-cipher-commands | 
          list-cipher-algorithms | list-message-digest-algorithms | list-public-key-algorithms]
list-standard-commands:标准命令列表
list-message-digest-commands:摘要命令列表
list-cipher-commands:加密命令列表
list-cipher-algorithms:加密算法列表
list-message-digest-algorithms:摘要算法列表
list-public-key-algorithms:公钥加密算法列表

获取帮助

虽然openssl并不支持”-h”参数,使用”-h”参数,会出现类似” unknown option -h”的错误提示,但是openssl的帮助系统还是很nice的,在使用错误参数的情况下,会把命令的详细用法及参数解释列出来,因此,还是可以用”openssl command -h”的方式来获取帮助信息。

我们还可以使用类似”man genrsa”的方法,来查看openssl的帮助文档;

最后,我们还可以访问openssl的主页,里面有详细的帮助文档;

参考资料

openssl帮助文档:http://www.openssl.org/docs/

openssl命令详解.pdf

百度百科:openssl,RSA,X509;

使用openssl命令剖析RSA私钥文件格式:http://blog.csdn.net/lucien_cc/article/details/18407451

常用证书格式切换:http://blog.chinaunix.net/uid-20553497-id-2353867.html

内存屏障

https://www.cnblogs.com/icanth/archive/2012/06/10/2544300.html

内存屏障(Memory Barriers)

一方面,CPU由于采用指令流水线和超流水线技术,可能导致CPU虽然顺序取指令、但有可能会出现“乱序”执行的情况,当然,对于” a++;b = f(a);c = f”等存在依赖关系的指令,CPU则会在“b= f(a)”执行阶段之前被阻塞;另一方面,编译器也有可能将依赖关系很近“人为地”拉开距离以防止阻塞情况的发生,从而导致编译器乱序,如“a++ ;c = f;b = f(a)”。

一个CPU对指令顺序提供如下保证:

(1) On any given CPU, dependent memory accesses will be issued in order, with respect to itself.如Q = P; D = *Q;将保证其顺序执行

(2) Overlapping loads and stores within a particular CPU will appear to be ordered within that CPU.重叠的Load和Store操作将保证顺序执行(目标地址相同的Load、Store),如:a = X; X = b;

(3) It must_not be assumed that independent loads and stores will be issued in the order given.

(4) It must be assumed that overlapping memory accesses may be merged or discarded.如A = X; Y = A; => STORE A = X; Y = LOAD A; / or STORE *A = Y = X;

由此可见,无关的内存操作会被按随机顺序有效的得到执行,但是在CPU与CPU交互时或CPU与IO设备交互时, 这可能会成为问题. 我们需要一些手段来干预编译器和CPU, 使其限制指令顺序。内存屏障就是这样的干预手段. 他们能保证处于内存屏障两边的内存操作满足部分有序.(译注: 这里"部分有序"的意思是, 内存屏障之前的操作都会先于屏障之后的操作, 但是如果几个操作出现在屏障的同一边, 则不保证它们的顺序.)

(1) 写(STORE)内存屏障。在写屏障之前的STORE操作将先于所有在写屏障之后的STORE操作。

(2) 数据依赖屏障。两条Load指令,第二条Load指令依赖于第一条Load指令的结果,则数据依赖屏障保障第二条指令的目标地址将被更新。

(3) 读(LOAD)内存屏障。读屏障包含数据依赖屏障的功能, 并且保证所有出现在屏障之前的LOAD操作都将先于所有出现在屏障之后的LOAD操作被系统中的其他组件所感知.

(4) 通用内存屏障. 通用内存屏障保证所有出现在屏障之前的LOAD和STORE操作都将先于所有出现在屏障之后的LOAD和STORE操作被系统中的其他组件所感知.

(5) LOCK操作.它的作用相当于一个单向渗透屏障.它保证所有出现在LOCK之后的内存操作都将在LOCK操作被系统中的其他组件所感知之后才能发生. 出现在LOCK之前的内存操作可能在LOCK完成之后才发生.LOCK操作总是跟UNLOCK操作配对出现.

(6) UNLOCK操作。它保证所有出现在UNLOCK之前的内存操作都将在UNLOCK操作被系统中的其他组件所感知之前发生.

LINUX对于x86而言,在为UP体系统架构下,调用barrier()进行通用内存屏障。在SMP体系架构下,若为64位CPU或支持mfence、lfence、sfence指令的32位CPU,则smp_mb()、smp_rmb()、smp_smb()对应通用内存屏障、写屏障和读屏障;而不支持mfence、lfence、sfence指令的32位CPU则smp_mb()、smp_rmb()、smp_smb()对应LOCK操作。源码请参见《内存屏障源码分析》一节。

内存屏障源码分析

/include/asm-generic/system.h:

1
2
3
4
5
6
7
8
9
053 #ifdef CONFIG_SMP
054 #define smp_mb()        mb()
055 #define smp_rmb()       rmb()
056 #define smp_wmb()       wmb()
057 #else
058 #define smp_mb()        barrier()
059 #define smp_rmb()       barrier()
060 #define smp_wmb()       barrier()
061 #endif

在x86 UP体系架构中,smp_mb、smp_rmb、smp_wmb被翻译成barrier:

1
012 #define barrier() __asm__ __volatile__("": : :"memory")

__volatile告诉编译器此条语句不进行任何优化,"“: : :"memory” 内存单元已被修改、需要重新读入。

在x86 SMP体系架构中,smp_mb、smp_rmb、smp_wmb如下定义:

/arch/x86/include/asm/system.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
352 /*
353  * Force strict CPU ordering.
354  * And yes, this is required on UP too when we're talking
355  * to devices.
356  */
357 #ifdef CONFIG_X86_32
358 /*
359  * Some non-Intel clones support out of order store. wmb() ceases to be a
360  * nop for these.
361  */
362 #define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
363 #define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", X86_FEATURE_XMM2)
364 #define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM)
365 #else
366 #define mb()    asm volatile("mfence":::"memory")
367 #define rmb()   asm volatile("lfence":::"memory")
368 #define wmb()   asm volatile("sfence" ::: "memory")
369 #endif

362~364行针对x86的32位CPU,366~368行针对x86的64位CPU。

在x86的64位CPU中,mb()宏实际为:

1
asm volatile("sfence" ::: "memory")。

volatile告诉编译器严禁在此处汇编语句与其它语句重组优化,memory强制编译器假设RAM所有内存单元均被汇编指令修改,"sfence" ::: 表示在此插入一条串行化汇编指令sfence。

mfence:串行化发生在mfence指令之前的读写操作

lfence:串行化发生在mfence指令之前的读操作、但不影响写操作

sfence:串行化发生在mfence指令之前的写操作、但不影响读操作

在x86的32位CPU中,mb()宏实际为:

1
mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)

由于x86的32位CPU有可能不提供mfence、lfence、sfence三条汇编指令的支持,故在不支持mfence的指令中使用:"lock; addl $0,0(%%esp)“, "mfence"。lock表示将“addl $0,0(%%esp)”语句作为内存屏障。

关于lock的实现:cpu上有一根pin #HLOCK连到北桥,lock前缀会在执行这条指令前先去拉这根pin,持续到这个指令结束时放开#HLOCK pin,在这期间,北桥会屏蔽掉一切外设以及AGP的内存操作。也就保证了这条指令的atomic。

参考资料

《memroy-barries.txt》,/Documentation/memory-barriers.txt

《LINUX内核内存屏障》,http://blog.csdn.net/ljl1603/article/details/6793982

SYN-ACK 重传

fastopen synack 重传

1
2
3
4
5
6
7
8
int tcp_conn_request(struct request_sock_ops *rsk_ops,
			const struct tcp_request_sock_ops *af_ops,
			struct sock *sk, struct sk_buff *skb)
{
	...
	fastopen = !want_cookie &&
			tcp_try_fastopen(sk, skb, req, &foc, dst);
	...
1
2
3
4
5
6
7
8
bool tcp_try_fastopen(struct sock *sk, struct sk_buff *skb,
			struct request_sock *req,
			struct tcp_fastopen_cookie *foc,
			struct dst_entry *dst)
{
	...
	if (tcp_fastopen_create_child(sk, skb, dst, req)) {
	...
1
2
3
4
5
6
7
8
9
10
11
12
static bool tcp_fastopen_create_child(struct sock *sk,
				struct sk_buff *skb,
				struct dst_entry *dst,
				struct request_sock *req)
{
	...
	child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);
	...
	inet_csk_reset_xmit_timer(child, ICSK_TIME_RETRANS,
				TCP_TIMEOUT_INIT, TCP_RTO_MAX);
	...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void tcp_retransmit_timer(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);

	if (tp->fastopen_rsk) {
		WARN_ON_ONCE(sk->sk_state != TCP_SYN_RECV &&
			sk->sk_state != TCP_FIN_WAIT1);
		tcp_fastopen_synack_timer(sk);
		/* Before we receive ACK to our SYN-ACK don't retransmit
		 * anything else (e.g., data or FIN segments).
		 */
		return;
	}
	...
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
/*
 *      Timer for Fast Open socket to retransmit SYNACK. Note that the
 *      sk here is the child socket, not the parent (listener) socket.
 */
static void tcp_fastopen_synack_timer(struct sock *sk)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	int max_retries = icsk->icsk_syn_retries ? :
		sysctl_tcp_synack_retries + 1; /* add one more retry for fastopen */
	struct request_sock *req;

	req = tcp_sk(sk)->fastopen_rsk;
	req->rsk_ops->syn_ack_timeout(sk, req);

	if (req->num_timeout >= max_retries) {
		tcp_write_err(sk);
		return;
	}
	/* XXX (TFO) - Unlike regular SYN-ACK retransmit, we ignore error
	 * returned from rtx_syn_ack() to make it more persistent like
	 * regular retransmit because if the child socket has been accepted
	 * it's not good to give up too easily.
	 */
	inet_rtx_syn_ack(sk, req);
	req->num_timeout++;
	inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
		TCP_TIMEOUT_INIT << req->num_timeout, TCP_RTO_MAX);
}

http://blog.csdn.net/u011130578/article/details/44954891

1 Why

TCP服务器在收到SYN请求后发送SYN|ACK响应,然后等待对端的ACK到来以完成三次握手。如果没有收到ACK,TCP应该重传SYN|ACK,这个功能由SYN-ACK定时器完成。由于SYN|ACK发送后并没有放入发送队列中,故重传时必须重新构建SYN|ACK报文。

2 When

TCP在发送SYN|ACK响应后设置SYN-ACK定时器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1465 int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
1466 {
...
1598     skb_synack = tcp_make_synack(sk, dst, req,
1599         fastopen_cookie_present(&valid_foc) ? &valid_foc : NULL); //构建SYN|ACK
1600
1601     if (skb_synack) {
1602         __tcp_v4_send_check(skb_synack, ireq->loc_addr, ireq->rmt_addr);
1603         skb_set_queue_mapping(skb_synack, skb_get_queue_mapping(skb));
1604     } else
1605         goto drop_and_free;
1606
1607     if (likely(!do_fastopen)) {
1608         int err;
1609         err = ip_build_and_send_pkt(skb_synack, sk, ireq->loc_addr,
1610              ireq->rmt_addr, ireq->opt); //发送SYN|ACK
...
1617         /* Add the request_sock to the SYN table */
1618         inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT); //将requese sock加入到SYN表中,并设置SYN-ACK定时器
...

inet_csk_reqsk_queue_hash_add函数:

1
2
3
4
5
6
7
8
9
10
11
521 void inet_csk_reqsk_queue_hash_add(struct sock *sk, struct request_sock *req,
522                    unsigned long timeout)
523 {
524     struct inet_connection_sock *icsk = inet_csk(sk);
525     struct listen_sock *lopt = icsk->icsk_accept_queue.listen_opt;
526     const u32 h = inet_synq_hash(inet_rsk(req)->rmt_addr, inet_rsk(req)->rmt_port,
527                      lopt->hash_rnd, lopt->nr_table_entries);
528
529     reqsk_queue_hash_req(&icsk->icsk_accept_queue, h, req, timeout); //将request_sock放入syn_table中并记录超时时间
530     inet_csk_reqsk_queue_added(sk, timeout); //设置SYN-ACK定时器
531 }

reqsk_queue_hash_req函数会记录request_sock的超时时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
262 static inline void reqsk_queue_hash_req(struct request_sock_queue *queue,
263                     u32 hash, struct request_sock *req,
264                     unsigned long timeout)
265 {
266     struct listen_sock *lopt = queue->listen_opt;
267
268     req->expires = jiffies + timeout; //超时时间
269     req->num_retrans = 0;
270     req->num_timeout = 0;
271     req->sk = NULL;
272     req->dl_next = lopt->syn_table[hash];
273
274     write_lock(&queue->syn_wait_lock);
275     lopt->syn_table[hash] = req;
276     write_unlock(&queue->syn_wait_lock);
277 }

inet_csk_reqsk_queue_added函数为整个syn_table设置一个SYN-ACK定时器:

1
2
3
4
5
6
280 static inline void inet_csk_reqsk_queue_added(struct sock *sk,
281                           const unsigned long timeout)
282 {
283     if (reqsk_queue_added(&inet_csk(sk)->icsk_accept_queue) == 0) //如果添加request sock之前syn_table为空
284         inet_csk_reset_keepalive_timer(sk, timeout);//设置SYN-ACK定时器
285 }

inet_csk_reset_keepalive_timer函数真正设置定时器:

1
2
3
4
404 void inet_csk_reset_keepalive_timer(struct sock *sk, unsigned long len)
405 {
406     sk_reset_timer(sk, &sk->sk_timer, jiffies + len);
407 }

SYN-ACK定时器的超时时间为TCP_TIMEOUT_INIT(1秒)。

3 What

SYN-ACK定时器的结构为sk->sk_timer,其超时函数为tcp_keepalive_timer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
558 static void tcp_keepalive_timer (unsigned long data)
559 {
560     struct sock *sk = (struct sock *) data;
561     struct inet_connection_sock *icsk = inet_csk(sk);
562     struct tcp_sock *tp = tcp_sk(sk);
563     u32 elapsed;
564
565     /* Only process if socket is not in use. */
566     bh_lock_sock(sk);
567     if (sock_owned_by_user(sk)) {
568         /* Try again later. */
569         inet_csk_reset_keepalive_timer (sk, HZ/20);
570         goto out;
571     }
572
573     if (sk->sk_state == TCP_LISTEN) { //如果是SYN-ACK定时器超时则判断为真
574         tcp_synack_timer(sk); //SYN-ACK定时器超时函数
575         goto out;
576     }
...

tcp_synack_timer函数:

1
2
3
4
5
534 static void tcp_synack_timer(struct sock *sk)
535 {
536     inet_csk_reqsk_queue_prune(sk, TCP_SYNQ_INTERVAL,
537                    TCP_TIMEOUT_INIT, TCP_RTO_MAX);
538 }

inet_csk_reqsk_queue_prune函数:

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
570 void inet_csk_reqsk_queue_prune(struct sock *parent,
571                 const unsigned long interval,
572                 const unsigned long timeout,
573                 const unsigned long max_rto)
574 {
575     struct inet_connection_sock *icsk = inet_csk(parent);
576     struct request_sock_queue *queue = &icsk->icsk_accept_queue;
577     struct listen_sock *lopt = queue->listen_opt;
578     int max_retries = icsk->icsk_syn_retries ? : sysctl_tcp_synack_retries;
579     int thresh = max_retries;
580     unsigned long now = jiffies;
581     struct request_sock **reqp, *req;
582     int i, budget;
583
584     if (lopt == NULL || lopt->qlen == 0)
585         return;
...
604     if (lopt->qlen>>(lopt->max_qlen_log-1)) {
605         int young = (lopt->qlen_young<<1);
606
607         while (thresh > 2) {
608             if (lopt->qlen < young)
609                 break;
610             thresh--;
611             young <<= 1;
612         }
613     }
614
615     if (queue->rskq_defer_accept)    //需要等待数据到来再唤醒应用进程
616         max_retries = queue->rskq_defer_accept;
617
618     budget = 2 * (lopt->nr_table_entries / (timeout / interval));
619     i = lopt->clock_hand;
620
621     do {  //遍历SYN table
622         reqp=&lopt->syn_table[i];
623         while ((req = *reqp) != NULL) {
624             if (time_after_eq(now, req->expires)) { //超时
625                 int expire = 0, resend = 0;
626
627                 syn_ack_recalc(req, thresh, max_retries,
628                            queue->rskq_defer_accept,
629                            &expire, &resend);     //计算request sock是否过期以及是否需要重发SYN|ACK
630                 req->rsk_ops->syn_ack_timeout(parent, req); //调用tcp_syn_ack_timeout更新信息数据库
631                 if (!expire &&  //request socket没有超时
632                     (!resend ||
633                      !inet_rtx_syn_ack(parent, req) ||  //重传SYN-ACK
634                      inet_rsk(req)->acked)) {
635                     unsigned long timeo;
636
637                     if (req->num_timeout++ == 0)
638                         lopt->qlen_young--;
639                     timeo = min(timeout << req->num_timeout,
640                             max_rto);
641                     req->expires = now + timeo;  //更新request_sock超时时间
642                     reqp = &req->dl_next;
643                     continue;
644                 }
645
646                 /* Drop this request */
647                 inet_csk_reqsk_queue_unlink(parent, req, reqp); 
648                 reqsk_queue_removed(queue, req);
649                 reqsk_free(req);
650                 continue;
651             }
652             reqp = &req->dl_next;
653         }
654
655         i = (i + 1) & (lopt->nr_table_entries - 1);
656
657     } while (--budget > 0);
658
659     lopt->clock_hand = i;
660
661     if (lopt->qlen) //syn_table中还有成员
662         inet_csk_reset_keepalive_timer(parent, interval); //继续设置定时器,超时
663 }

604-611:当syn_table中剩余空间比较小时,需要减小最大重试次数,以便使旧的request_sock能够更快消亡,从而新的request_sock能够更多的被接受

647-649:将超时的request_sock移出syn_table并释放,即丢弃其对应的连接

631-642:全部满足下列条件就不删除request_sock而只是更新超时时间:
(1)request_sock没有超时
(2)下列3个条件之一成立
1)不需要重传SYN|ACK
2)重传SYN|ACK成功
3)应用进程使用TCP_DEFER_ACCEPT socket选项意图使数据到来时listen socket再唤醒进程,当ACK到来但没有数据时

syn_ack_recalc函数来确定request_sock是否超时以及是否需要重传SYN|ACK:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
539 static inline void syn_ack_recalc(struct request_sock *req, const int thresh,
540                   const int max_retries,
541                   const u8 rskq_defer_accept,
542                   int *expire, int *resend)
543 {
544     if (!rskq_defer_accept) {    //不需要等待数据到来再调用accept系统调用
545         *expire = req->num_timeout >= thresh;    //超时次数达到限制则超时
546         *resend = 1;    //重传SYN|ACK
547         return;
548     }
549     *expire = req->num_timeout >= thresh &&    //超时次数达到限制
550           (!inet_rsk(req)->acked || req->num_timeout >= max_retries);    //ACK没有到来或超时次数达到最高上限
551     /*
552      * Do not resend while waiting for data after ACK,
553      * start to resend on end of deferring period to give
554      * last chance for data or ACK to create established socket.
555      */
556     *resend = !inet_rsk(req)->acked || //ACK没有到来
557           req->num_timeout >= rskq_defer_accept - 1;    //超时次数超过或即将达到应用进程的限制,赶快重传SYN|ACK以便给对端最后一个机会建立连接
558 }

综上,SYN|ACK定时器超时时重传SYN|ACK的条件是下列条件全部成立:
(1)request_sock超时
(2)request_sock的超时次数达到限制
(3)下列条件之一成立:
1)应用进程没有使用TCP_DEFER_ACCEPT socket选项来延迟accept request_sock的时间
2)应用进程使用TCP_DEFER_ACCEPT socket选项设置了超时次数限制,但ACK没有到来或,超时次数达到最高限制且超时次数超过或即将达到应用进程的限制

SYN|ACK的重传是由inet_rtx_syn_ack函数完成的:

1
2
3
4
5
6
7
8
560 int inet_rtx_syn_ack(struct sock *parent, struct request_sock *req)
561 {
562     int err = req->rsk_ops->rtx_syn_ack(parent, req);    //指向tcp_v4_rtx_synack或tcp_v6_rtx_synack
563
564     if (!err)
565         req->num_retrans++;
566     return err;
567 }

tcp_v4_rtx_synack函数:

1
2
3
4
5
6
7
8
870 static int tcp_v4_rtx_synack(struct sock *sk, struct request_sock *req)
871 {
872     int res = tcp_v4_send_synack(sk, NULL, req, 0, false); //构建并发送SYN-ACK
873
874     if (!res)
875         TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_RETRANSSEGS);
876     return res;
877 }