首页 技术 正文
技术 2022年11月20日
0 收藏 551 点赞 2,271 浏览 13491 个字

快速模式第三包:quick_inR1_outI2()

文章目录

1. 序言

在上一篇中博客中长话短说了第二包的处理流程,前两个报文的交互是快速模式的关键交互,用来协商会话参数(加解密算法、会话秘钥等),而第三包则是对前两个报文的认证,流程上简单了很多 。quick_inR1_outI2()处理流程实现的功能如下:

  • 解析SA载荷(对端选择的加解密算法信息)、KE载荷(pfs)、Nonce载荷
  • 构造第三个报文
  • 生成密钥生成材料keymats
  • 建立完整的IPsec SA
    • 发起端建立的为完整IPSec SA,包括入SA和出SA,响应端有所不同。
  • 启动DPD检测

2. quick_inR1_outI2()的处理流程

第三个报文的完整处理流程如下:

3. 快速模式第③包报文格式

该报文中的杂凑载荷是对前面交换的认证。第③包仅有ISAKMP头部和杂凑载荷构成,杂凑载荷的消息摘要是以一个0字节的MsgID、去掉载荷头的发起者Nonce以及去掉了载荷头的响应者Nonce为输入参数生成的,计算公式如下:

H

A

S

H

(

3

)

=

P

R

F

(

S

K

E

Y

a

,

0

M

s

g

I

D

N

i

b

N

r

b

)

HASH(3) = PRF(SKEY-a, 0|MsgID|Ni_b|Nr_b)

HASH(3)=PRF(SKEY−a,0∣MsgID∣Nib​∣Nrb​)

4. quick_inR1_outI2()源码分析

接口quick_inR1_outI2()的主要功能如下:

  • 验证报文的完整性
  • 解析响应端选择的SA载荷
    • 加解密算法(ESP协议、AH协议,…)
    • 认证算法
    • 封装模式(隧道模式 ?传输模式)
  • 解析Nonce载荷
  • 如果启用PFS
    • 解析KE载荷
    • 再次计算DH交换值
stf_status
quick_inR1_outI2(struct msg_digest *md)
{
struct state *const st = md->st; /* HASH(2) in *//*验证报文的哈希载荷*/
CHECK_QUICK_HASH(md
, quick_mode_hash12(hash_val, hash_pbs->roof, md->message_pbs.roof
, st, &st->st_msgid, TRUE)
, "HASH(2)", "Quick R1"); /* SA in */
{
struct payload_digest *const sa_pd = md->chain[ISAKMP_NEXT_SA];/*解析对端选择的SA载荷*/
RETURN_STF_FAILURE(parse_ipsec_sa_body(&sa_pd->pbs
, &sa_pd->payload.sa, NULL, TRUE, st));
} /* Nr in *//*解析对端Nonce载荷*/
RETURN_STF_FAILURE(accept_v1_nonce(md, &st->st_nr, "Nr")); /* [ KE ] in (for PFS) *//*根据配置策略解析对端KE载荷*/
RETURN_STF_FAILURE(accept_PFS_KE(md, &st->st_gr, "Gr", "Quick Mode R1")); if(st->st_pfs_group) {/*如果支持PFS功能,则需要进行DH算法计算*/
struct dh_continuation *dh = alloc_thing(struct dh_continuation
, "quick outI2 DH");/* set up DH calculation */
dh->md = md;
passert(st != NULL);
set_suspended(st, md);
pcrc_init(&dh->dh_pcrc);
dh->dh_pcrc.pcrc_func = quick_inR1_outI2_continue;
return start_dh_secret(&dh->dh_pcrc, st
, st->st_import
, INITIATOR
, st->st_pfs_group->group);
} else {
/* just call the tail function */
struct dh_continuation dh;dh.md=md;
return quick_inR1_outI2_cryptotail(&dh, NULL);
}
}

4. quick_inR1_outI2_cryptotail()源码分析

接口quick_inR1_outI2_cryptotail()的主要功能如下:

  • 检验ID载荷收发是否一致

    这里是通过ID载荷来协商IPSec隧道的保护子网信息

  • NAT-T相关处理

  • 构造应答报文

  • 计算keymats值

  • 建立IPSecSA

  • 初始化本隧道的DPD定时器

stf_status
quick_inR1_outI2_cryptotail(struct dh_continuation *dh
, struct pluto_crypto_req *r)
{
struct msg_digest *md = dh->md;
struct state *st = md->st;
struct connection *c = st->st_connection; if (st->st_pfs_group != NULL && r!=NULL) {
finish_dh_secret(st, r);/*获取密钥信息*/
if(!r->pcr_success) {
return STF_FAIL + INVALID_KEY_INFORMATION;
}
}#ifdef NAT_TRAVERSAL
... ...
#endif /* [ IDci, IDcr ] in; these must match what we sent */ {
struct payload_digest *const IDci = md->chain[ISAKMP_NEXT_ID];
struct payload_digest *IDcr;if (IDci != NULL)/*应答报文中包含ID载荷*/
{
/* ??? we are assuming IPSEC_DOI */ /* IDci (we are initiator) *//*确定收发ID载荷是否一致*/
if (!check_net_id(&IDci->payload.ipsec_id, &IDci->pbs
, &st->st_myuserprotoid, &st->st_myuserport
, &st->st_connection->spd.this
, &st->st_localaddr
, "our client"))
return STF_FAIL + INVALID_ID_INFORMATION; /* we checked elsewhere that we got two of them */
IDcr = IDci->next;/*响应端ID载荷是否匹配*/
passert(IDcr != NULL); /* IDcr (responder is peer) */ if (!check_net_id(&IDcr->payload.ipsec_id, &IDcr->pbs
, &st->st_peeruserprotoid, &st->st_peeruserport
, &st->st_connection->spd.that
, &st->st_remoteaddr
, "peer client"))
return STF_FAIL + INVALID_ID_INFORMATION; /*
* if there is a NATOA payload, then use it as
* &st->st_connection->spd.that.client, if the type
* of the ID was FQDN
*/
#ifdef NAT_TRAVERSAL
... ...
#endif}
else
{
/* no IDci, IDcr: we must check that the defaults match our proposal */
if (!subnetisaddr(&c->spd.this.client, &c->spd.this.host_addr)/*两个地址一样即可*/
|| !subnetisaddr(&c->spd.that.client, &c->spd.that.host_addr))
{
loglog(RC_LOG_SERIOUS, "IDci, IDcr payloads missing in message"
" but default does not match proposal");
return STF_FAIL + INVALID_ID_INFORMATION;
}
}
} /**************** build reply packet HDR*, HASH(3) ****************/ /* HDR* out done ISAKMP头部已经填充完毕*/ /* HASH(3) out -- sometimes, we add more content */
{
u_char/* set by START_HASH_PAYLOAD: */
*r_hashval,/* where in reply to jam hash value */
*r_hash_start; /* start of what is to be hashed *//*填充hash载荷*/
START_HASH_PAYLOAD(md->rbody, ISAKMP_NEXT_NONE);/*计算哈希载荷值,用于对端检测报文的完整性*/
(void)quick_mode_hash3(r_hashval, st);
r_hash_start = r_hash_start; /* otherwise complaint about never used */
} /* Derive new keying material *//*计算密钥生成材料*/
compute_keymats(st); /* Tell the kernel to establish the inbound, outbound, and routing part
* of the new SA (unless the commit bit is set -- which we don't support).
* We do this before any state updating so that
* failure won't look like success.
*/
if (!install_ipsec_sa(md->pst, st, TRUE))/*建立IPsecSA*/
return STF_INTERNAL_ERROR; /* encrypt message, except for fixed part of header */
/*加密*/
if (!encrypt_message(&md->rbody, st))
{
delete_ipsec_sa(st, FALSE);
return STF_INTERNAL_ERROR;/* ??? we may be partly committed */
} st->st_connection->newest_ipsec_sa = st->st_serialno; /* note (presumed) success */
if (c->gw_info != NULL)
c->gw_info->key->last_worked_time = now(); /* If we have dpd delay and dpdtimeout set, then we are doing DPD
on this conn, so initialize it */
if (st->st_connection->dpd_delay && st->st_connection->dpd_timeout) {
if(dpd_init(st) != STF_OK) {/*启动DPD定时器*/
delete_ipsec_sa(st, FALSE);
return STF_FAIL;
}
} return STF_OK;
}

4. compute_keymats()源码分析

compute_keymats()是一个很重的函数,功能是第二阶段计算生成密钥材料,它的计算方式如下:

  • 如果启用PFS功能:

K

E

Y

M

A

T

S

=

P

R

F

(

S

K

E

Y

S

T

R

d

,

g

x

y

p

r

o

t

o

c

o

l

S

P

I

N

i

b

N

r

b

)

KEYMATS = PRF(SKEYSTR-d, g^{xy} | protocol | SPI | Ni-b |Nr-b )

KEYMATS=PRF(SKEYSTR−d,gxy∣protocol∣SPI∣Ni−b∣Nr−b)

  • 未用PFS功能:

K

E

Y

M

A

T

S

=

P

R

F

(

S

K

E

Y

S

T

R

d

,

p

r

o

t

o

c

o

l

S

P

I

N

i

b

N

r

b

)

KEYMATS = PRF(SKEYSTR-d, protocol | SPI | Ni-b |Nr-b )

KEYMATS=PRF(SKEYSTR−d,protocol∣SPI∣Ni−b∣Nr−b)

需要注意的是:

  • g

    x

    y

    g^{xy}

    gxy是第二阶段通过额外的DH交换计算得到的(DH计算出的共享秘钥),存储在st->st_shared中。此变量虽然在第一阶段时也有生成,但是第二阶段使用了第一阶段复制的state状态,并未使用第一阶段中的st_shared的值,因此第二阶段的st->st_shared只有在进行额外的DH交换后才会生成,且不会使用第一阶段的共享密钥的值。

static void
compute_keymats(struct state *st)
{
if (st->st_ah.present)
compute_proto_keymat(st, PROTO_IPSEC_AH, &st->st_ah, "AH");
if (st->st_esp.present)
compute_proto_keymat(st, PROTO_IPSEC_ESP, &st->st_esp, "ESP");
}/*
* Produce the new key material of Quick Mode.
* RFC 2409 "IKE" section 5.5
* specifies how this is to be done.
*###############################################
*compute_proto_keymat非常重要的密钥协商函数
*###############################################
*/static void
compute_proto_keymat(struct state *st
, u_int8_t protoid
, struct ipsec_proto_info *pi
, const char *satypename)
{
size_t needed_len = 0; /* bytes of keying material needed */ /* Add up the requirements for keying material
* (It probably doesn't matter if we produce too much!)
*/
switch (protoid)
{
case PROTO_IPSEC_ESP:
switch (pi->attrs.transattrs.encrypt)/*加密算法*/
{
case ESP_NULL:
needed_len = 0;
break;
case ESP_DES:
needed_len = DES_CBC_BLOCK_SIZE;
break;
case ESP_3DES:
needed_len = DES_CBC_BLOCK_SIZE * 3;
break;
case ESP_AES:
needed_len = AES_CBC_BLOCK_SIZE;
/* if an attribute is set, then use that! */
if(st->st_esp.attrs.transattrs.enckeylen) {/*如果属性载荷设置了此参数,则使用此参数的值*/
needed_len = st->st_esp.attrs.transattrs.enckeylen/8;
}
break; default:
#ifdef KERNEL_ALG
if((needed_len=kernel_alg_esp_enc_keylen(pi->attrs.transattrs.encrypt))>0) {
/* XXX: check key_len "coupling with kernel.c's */
if (pi->attrs.transattrs.enckeylen) {
needed_len=pi->attrs.transattrs.enckeylen/8;
DBG(DBG_PARSING, DBG_log("compute_proto_keymat:"
"key_len=%d from peer",
(int)needed_len));
}
break;
}
#endif
bad_case(pi->attrs.transattrs.encrypt);
}
DBG(DBG_PARSING, DBG_log("compute_proto_keymat:"
"needed_len (after ESP enc)=%d",
(int)needed_len)); switch (pi->attrs.transattrs.integ_hash)/*哈希算法*/
{
case AUTH_ALGORITHM_NONE:
break;
case AUTH_ALGORITHM_HMAC_MD5:
needed_len += HMAC_MD5_KEY_LEN;
break;
case AUTH_ALGORITHM_HMAC_SHA1:
needed_len += HMAC_SHA1_KEY_LEN;
break;
default:
#ifdef KERNEL_ALG
if (kernel_alg_esp_auth_ok(pi->attrs.transattrs.integ_hash, NULL) == NULL) {
needed_len += kernel_alg_esp_auth_keylen(pi->attrs.transattrs.integ_hash);
break;
}
#endif
case AUTH_ALGORITHM_DES_MAC:
bad_case(pi->attrs.transattrs.integ_hash);
break; }
DBG(DBG_PARSING, DBG_log("compute_proto_keymat:"
"needed_len (after ESP auth)=%d",
(int)needed_len));
break; case PROTO_IPSEC_AH:
switch (pi->attrs.transattrs.encrypt)
{
case AH_MD5:
needed_len = HMAC_MD5_KEY_LEN;
break;
case AH_SHA:
needed_len = HMAC_SHA1_KEY_LEN;
break;
default:
#ifdef KERNEL_ALG
if (kernel_alg_ah_auth_ok(pi->attrs.transattrs.integ_hash, NULL)) {
needed_len += kernel_alg_ah_auth_keylen(pi->attrs.transattrs.integ_hash);
break;
}
#endif
bad_case(pi->attrs.transattrs.encrypt);
}
break; default:
bad_case(protoid);
}/*将所有算法需要的密钥长度全部相加,从而生成所需长度的密钥材料*/ pi->keymat_len = needed_len; /* Allocate space for the keying material.
* Although only needed_len bytes are desired, we
* must round up to a multiple of ctx.hmac_digest_len
* so that our buffer isn't overrun.
*/
{
struct hmac_ctx ctx_me, ctx_peer;
size_t needed_space;/* space needed for keying material (rounded up) */
size_t i;hmac_init_chunk(&ctx_me, st->st_oakley.prf_hasher, st->st_skeyid_d);ctx_peer = ctx_me;/* duplicate initial conditions */
needed_space = needed_len + pad_up(needed_len, ctx_me.hmac_digest_len);
replace(pi->our_keymat, alloc_bytes(needed_space, "keymat in compute_keymat()"));
replace(pi->peer_keymat, alloc_bytes(needed_space, "peer_keymat in quick_inI1_outR1()"));/*
* 准备计算秘钥所需的所有材料
*1. DH交换生成的共享秘钥
*2. 协议protocol
*3. SPI
*4. Ni_b
*5. Nr_b
*/
for (i = 0;; )
{
/*1. DH交换生成的共享秘钥*/
if (st->st_shared.ptr != NULL)
{
/* PFS: include the g^xy */
hmac_update_chunk(&ctx_me, st->st_shared);
hmac_update_chunk(&ctx_peer, st->st_shared);
}
/*2. 协议protocol*/
hmac_update(&ctx_me, &protoid, sizeof(protoid));
hmac_update(&ctx_peer, &protoid, sizeof(protoid));
/*3. SPI*/
hmac_update(&ctx_me, (u_char *)&pi->our_spi, sizeof(pi->our_spi));
hmac_update(&ctx_peer, (u_char *)&pi->attrs.spi, sizeof(pi->attrs.spi));
/*4. Ni_b*/
hmac_update_chunk(&ctx_me, st->st_ni);
hmac_update_chunk(&ctx_peer, st->st_ni);
/*5. Nr_b*/
hmac_update_chunk(&ctx_me, st->st_nr);
hmac_update_chunk(&ctx_peer, st->st_nr); hmac_final(pi->our_keymat + i, &ctx_me);
hmac_final(pi->peer_keymat + i, &ctx_peer); i += ctx_me.hmac_digest_len;
if (i >= needed_space)
break; /* more keying material needed: prepare to go around again */
hmac_reinit(&ctx_me);
hmac_reinit(&ctx_peer); hmac_update(&ctx_me, pi->our_keymat + i - ctx_me.hmac_digest_len, ctx_me.hmac_digest_len);
hmac_update(&ctx_peer, pi->peer_keymat + i - ctx_peer.hmac_digest_len, ctx_peer.hmac_digest_len);
}
}
/*双方能计算出对端的密钥材料信息???*/
DBG(DBG_CRYPT,
DBG_log("%s KEYMAT\n",satypename);
DBG_dump(" KEYMAT computed:\n", pi->our_keymat, pi->keymat_len);
DBG_dump(" Peer KEYMAT computed:\n", pi->peer_keymat, pi->keymat_len));
}

5. dpd_init()源码分析

DPD功能对应有两个事件:

  • EVENT_DPD_TIMEOUT

    第一阶段的定时器,在第二阶段已经断开的情况下,用来检测第一阶段是否超时

  • DPD_EVENT

    第二阶段的定时器,长时间未通讯时断开连接,并启动第一阶段的定时器

这个定时器机制实际使用时会复杂一点。可以参考注释说明。

/**
* Initialize RFC 3706 Dead Peer Detection
*
* @param st An initialized state structure
* @return void
*
* How DPD works.
*
* There are two kinds of events that can be scheduled.
* At most one of them is schedule at any given time.
*
* The EVENT_DPD_TIMEOUT event, if it ever goes off, means that
* neither the ISAKMP SA nor the IPsec SA has *RECEIVED* any DPD
* events lately.
*
* 0) So, every time we receive a DPD (R_U_THERE or R_U_ACK), then
* we delete any DPD event (EVENT_DPD or EVENT_DPD_TIMEOUT), and
* we schedule a new DPD_EVENT (sending) for "delay" in the future.
*
* 1) When the DPD_EVENT goes off, we check the phase 2 (if there is one)
* SA to see if there was incoming traffic. If there was, then we are happy,
* we set a new DPD_EVENT, and we are done.
*
* 2) If there was no phase 2 activity, we check if there was a recent enough
* DPD activity (st->st_last_dpd). If so, we just reschedule, and do
* nothing.
*
* 3) Otherwise, we send a DPD R_U_THERE message, and set the
* EVENT_DPD_TIMEOUT on the phase 1.
*
* One thing to realize when looking at "ipsec whack --listevents" output,
* is there there will only be DPD_EVENT_TIMEOUT events if there are
* outstanding R_U_THERE messages.
*
* The above is the basic idea, but things are a bit more complicated because
* multiple phase 2s can share the same phase 1 ISAKMP SA. Each phase 2 state
* has its own DPD_EVENT. Further, we start a DPD_EVENT for phase 1 when it
* gets established. This is because the phase 2 may never actually succeed
* (usually due to authorization issues, which may be DNS or otherwise related)
* and if the responding end dies (gets restarted, or the conn gets reloaded
* with the right policy), then we may have a bum phase 1 SA, and we can not
* re-negotiate. (This happens WAY too often)
*
* The phase 2 dpd_init() will attempt to kill the phase 1 DPD_EVENT, if it
* can, to reduce the amount of work.
*
* The st_last_dpd member which is used is always the one from the phase 1.
* So, if there are multiple phase 2s, then if any of them receive DPD data
* they will update the st_last_dpd, so the test in #2 will avoid the traffic
* for all by one phase 2.
*
* Note that the EVENT_DPD are attached to phase 2s (typically), while the
* EVENT_DPD_TIMEOUT are attached to phase 1s only.
*
* Finally, if the connection is using NAT-T, then we ignore the phase 2
* activity check, because in the case of a unidirectional stream (VoIP for
* a conference call, for instance), we may not send enough traffic to keep
* the NAT port mapping valid.
*
*/stf_status
dpd_init(struct state *st)
{
/**
* Used to store the 1st state
*/
#ifdef HAVE_LABELED_IPSEC
if(st->st_connection->loopback) {
openswan_log("dpd is not required for ipsec connections over loopback");
return STF_OK;
}
#endif
struct state *p1st; /* find the related Phase 1 state */
p1st = find_state_ikev1(st->st_icookie, st->st_rcookie,
&st->st_connection->spd.that.host_addr, 0);/*第一阶段的msgid为0*/ if (p1st == NULL) {
loglog(RC_LOG_SERIOUS, "could not find phase 1 state for DPD");/*
* if the phase 1 state has gone away, it really should have
* deleted all of its children.
* Why would this happen? because a quick mode SA can take
* some time to create (DNS lookups for instance), and the phase 1
* might have been taken down for some reason in the meantime.
* We really can not do anything here --- attempting to invoke
* the DPD action would be a good idea, but we really should
* do that outside this function.
*/
return STF_FAIL;
} /* if it was enabled, and we haven't turned it on already */
if (p1st->hidden_variables.st_dpd) {
time_t n = now();
openswan_log("Dead Peer Detection (RFC 3706): enabled");if(st->st_dpd_event == NULL || (st->st_connection->dpd_delay + n) < st->st_dpd_event->ev_time) {
delete_dpd_event(st);
event_schedule(EVENT_DPD, st->st_connection->dpd_delay, st);
} } else {
openswan_log("Dead Peer Detection (RFC 3706): not enabled because peer did not advertise it");
} if(p1st != st) {/*第一阶段的DPD_EVENT事件已经没有必要,可以进行删除了,第二阶段会有自己的DPD_EVENT*/
/* st was not a phase 1 SA, so kill the DPD_EVENT on the phase 1 */
if(p1st->st_dpd_event != NULL
&& p1st->st_dpd_event->ev_type == EVENT_DPD) {
delete_dpd_event(p1st);
}
}
return STF_OK;
}

6. install_ipsec_sa()源码分析

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:8,904
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,430
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,246
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,058
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,690
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,727