文章目录
1. 序言
在介绍第②包quick_inI1_outR1()
函数之前,先说明下处理流程中的主要的功能:
- 协商第二阶段的SA算法信息,包括AH协议、ESP协议、封装模式等重要参数。
- 密钥材料交换,包括Nonce、KE(可选)。
- 使用ID载荷来协商两端的保护子网范围。
- 建立IPSec SA结构。
- 报文的认证和加密。
从上述作用可以看出,quick_inI1_outR1()
及后续函数几乎实现了第一阶段的所有基本交换(第一阶段里的重要载荷在此流程中基本都有实现),因此第二包处理流程算是IKEv1协商流程里最为复杂的流程了。这里只是做一个简单的笔记说明核心流程,无法涉及到完整的交换流程。此外响应端通过此次交换后会建立一个inbound sa,这部分流程尚未看明白处理逻辑(可能在于涉及到内核路由表等内容,目前还没有get到)。因此如果需要深入了解此流程,请参考源码实现。
2. quick_inI1_outR1()流程图
刚才已经说明第二个报文的处理流程比较复杂,实现的功能也较其他接口复杂了很多,从流程图上便可以看出:
3. 快速模式消息②数据包格式
下表中的报文格式有部分字段应该为变长类型,但是并未标出,这一点请注意。
4. 源码分析
4.1 quick_inI1_outR1()
quick_inI1_outR1()
接口的作用包括:
-
检验报文的完整性
- HASH载荷(杂凑载荷)既可以用来检验报文的完整性,也可以用来实现源认证功能,两者实际上是一致的。它计算范围是除了ISAKMP头部以外的完整报文进行杂凑运算。计算方式为:
H A S H = P R F ( S K E Y I D − a , M s g I D ∣ N i ∣ S A ∣ N r [ ∣ I D i ∣ I D r ] ) HASH = PRF(SKEYID-a, MsgID | Ni | SA | Nr [ | IDi | IDr ] ) HASH=PRF(SKEYID−a,MsgID∣Ni∣SA∣Nr[∣IDi∣IDr])
还需要注意的是快速模式的三个报文的HASH载荷的运算模式并不相同。
- HASH载荷(杂凑载荷)既可以用来检验报文的完整性,也可以用来实现源认证功能,两者实际上是一致的。它计算范围是除了ISAKMP头部以外的完整报文进行杂凑运算。计算方式为:
-
解析报文中的ID载荷
- 快速模式中,身份标识ID载荷缺省定义为ISAKMP双方的协商地址。如果双方需要指定身份ID载荷,则需要按照一定的顺序进行传输:IDi + IDr。还需要注意的是协商隧道时配置的保护子网(感兴趣流)是通过ID载荷来传输并完成协商的。ID载荷可以传输IPv4和IPv6的主机地址、子网地址、地址范围。因此使用ID载荷来协商感兴趣流完全满足需求。使用
emit_subnet_id()
来将保护子网填充到ID载荷,使用decode_net_id()
将ID载荷解析为保护子网地址.
- 快速模式中,身份标识ID载荷缺省定义为ISAKMP双方的协商地址。如果双方需要指定身份ID载荷,则需要按照一定的顺序进行传输:IDi + IDr。还需要注意的是协商隧道时配置的保护子网(感兴趣流)是通过ID载荷来传输并完成协商的。ID载荷可以传输IPv4和IPv6的主机地址、子网地址、地址范围。因此使用ID载荷来协商感兴趣流完全满足需求。使用
-
保存IV值,并调用后续处理
quick_inI1_outR1()
函数并没有协商保护子网信息,而是在后续接口中进行的协商。(NAT-T相关略)
stf_status
quick_inI1_outR1(struct msg_digest *md)
{
const struct state *const p1st = md->st;
struct connection *c = p1st->st_connection;
struct payload_digest *const id_pd = md->chain[ISAKMP_NEXT_ID];
struct verify_oppo_bundle b;
/* HASH(1) in *//*使用第一阶段的算法、计算并检验报文的hash载荷*/
CHECK_QUICK_HASH(md
, quick_mode_hash12(hash_val, hash_pbs->roof, md->message_pbs.roof
, p1st, &md->hdr.isa_msgid, FALSE)
, "HASH(1)", "Quick I1");
/* [ IDci, IDcr ] in
* We do this now (probably out of physical order) because
* we wish to select the correct connection before we consult
* it for policy.
*/
if (id_pd != NULL)/*如果ID载荷存在*/
{
struct payload_digest *IDci = id_pd->next;
/* ??? we are assuming IPSEC_DOI */
/* IDci (initiator is peer) */
if (!decode_net_id(&id_pd->payload.ipsec_id, &id_pd->pbs
, &b.his.net, "peer client"))/*获取到对端的网段*/
return STF_FAIL + INVALID_ID_INFORMATION;
/* Hack for MS 818043 NAT-T Update */
if (id_pd->payload.ipsec_id.isaiid_idtype == ID_FQDN) {
/*将单个地址转换为子网地址*/
loglog(RC_LOG_SERIOUS, "Applying workaround for MS-818043 NAT-T bug");
memset(&b.his.net, 0, sizeof(ip_subnet));
happy(addrtosubnet(&c->spd.that.host_addr, &b.his.net));
}
/* End Hack for MS 818043 NAT-T Update */
b.his.proto = id_pd->payload.ipsec_id.isaiid_protoid;
b.his.port = id_pd->payload.ipsec_id.isaiid_port;
b.his.net.addr.u.v4.sin_port = htons(b.his.port);
/* IDcr (we are responder) */
if (!decode_net_id(&IDci->payload.ipsec_id, &IDci->pbs
, &b.my.net, "our client"))
return STF_FAIL + INVALID_ID_INFORMATION;
b.my.proto = IDci->payload.ipsec_id.isaiid_protoid;
b.my.port = IDci->payload.ipsec_id.isaiid_port;
b.my.net.addr.u.v4.sin_port = htons(b.my.port);
#ifdef NAT_TRAVERSAL
/*
* 略
*/
#endif
}
else
{
/*载荷中不存在ID载荷,如果两端的地址类型不一致的化则返回错误
*
*如果不存在ID载荷,则使用协商地址作为保护子网
*/
/* implicit IDci and IDcr: peer and self */
if (!sameaddrtype(&c->spd.this.host_addr, &c->spd.that.host_addr))
return STF_FAIL;
/*默认使用IP地址当作ID*/
happy(addrtosubnet(&c->spd.this.host_addr, &b.my.net));
happy(addrtosubnet(&c->spd.that.host_addr, &b.his.net));
b.his.proto = b.my.proto = 0;
b.his.port = b.my.port = 0;
}
b.step = vos_start;
b.md = md;
b.new_iv_len = p1st->st_new_iv_len;
save_new_iv(p1st, b.new_iv);
/*
* FIXME - DAVIDM
* "b" is on the stack, for OPPO tunnels this will be bad, in
* quick_inI1_outR1_start_query it saves a pointer to it before
* a crypto (async op).
*/
return quick_inI1_outR1_authtail(&b, NULL);
}
4.2 quick_inI1_outR1_authtail()
quick_inI1_outR1_authtail()
函数作用包括如下几个:
-
根据子网信息查询连接
这部分代码没有看懂。
。按常理来说,接收此报文时已经确定了连接和状态信息,直接比较连接上的保护子网信息和SA载荷中的保护子网信息,确定是否匹配即可。但是openswan源码中的逻辑负责了很多,没有看明白这部分代码,先留一个疑问吧。
-
根据连接创建新的状态
-
解析IPSec SA建议载荷
-
解析SA载荷:
parse_ipsec_sa_body()
这个接口是快速模式协商IPSec策略的核心接口,包括封装协议 (ESP | AH | IPCOM)、加密算法、认证算法(完整性算法)、隧道模式or传输模式等等,都是在此接口中进行协商的。此外,该函数也可以完成应答报文的SA载荷的封装。
近700行的代码,不再另行说明了。
-
-
解析Nonce载荷
-
如果支持PFS,则解析KE载荷
- 启动PFS功能(完美向前加密),则第二阶段需要再进行一次DH交换,因此需要重新计算生成KE载荷。PFS简单的说如果第一阶段的秘钥被破解(无论采用何种方式),由第一阶段密钥衍生的第二阶段密钥则不受影响。这就要求在第二阶段再次进行DH交换。
-
构建密钥交换材料申请结构信息,包括: