赞
踩
IKEv1协议建立一对IPSec SA,使用主动模式需要9个报文,使用野蛮模式需要使用6个报文方能协商成功。IKEv2对IKEv1协议进行了优化,IKEv2只需要进行两次交互,使用 4 条消息就可以完成一个 IKEv2 SA 和一对 IPsec SA 的协商建立。IKEv2 定义了三种交互:
初始交换、
创建子 SA 交换
通知交换。
下面简单介绍一下 IKEv2 协商过程中的初始交换过程:
初始化交换通过两次交换共4个报文便可以完成一对IKE SA和IPSec SA的协商。上图主要用来描述协商报文的内容和对应的处理函数入口。下图则是用来说明各接口对应的协商状态。协商过程中是根据该状态来确定当前的协商阶段。
RFC文档中的报文格式:
Initiator Responder
-------------------------------------------------------------------
HDR, SAi1, KEi, Ni -->
<-- HDR, SAr1, KEr, Nr, [CERTREQ]
HDR, SK {IDi, [CERT,] [CERTREQ,]
[IDr,] AUTH, SAi2,
TSi, TSr} -->
<-- HDR, SK {IDr, [CERT,] AUTH,
SAr2, TSi, TSr}
其中:
报文字段 | 说明 |
---|---|
HDR | 报文头部 |
SAi1、SAr1 | IKE SA建议 |
SAi2、SAr2 | IPSEC SA建议载荷 |
KEi、KEr | DH算法公共值 |
Ni、Nr | Nonce随机数 |
CERT、CERTREQ | 证书载荷、证书请求载荷 |
IDi、IDr | ID载荷 |
TSi、TSr | 流量选择器,使用此载荷完成保护子网的协商 |
AUTH | 认证数据载荷 |
这里面需要说明的是:报文中的SK并不是一个载荷。而是:SK {…}表示里面的内容被加密和认证保护。
此函数是IKEv2协议发起协商的入口函数。主要功能为:
stf_status ikev2parent_outI1(int whack_sock , struct connection *c , struct state *predecessor , so_serial_t *newstateno , lset_t policy , unsigned long try , enum crypto_importance importance , struct xfrm_user_sec_ctx_ike * uctx UNUSED ) { struct state *st = new_state(); /*发起端随机生成cookie值*/ get_cookie(TRUE, st->st_icookie, COOKIE_SIZE, &c->spd.that.host_addr); /*将连接上的信息初始化到状态上*/ initialize_new_state(st, c, policy, try, whack_sock, importance); if(newstateno) *newstateno = st->st_serialno; /* * initialize the local end point address, so that NAT calculation will * have something to work with. * *在set_state_ike_endpoints中将隧道两端的地址更新到了st_localaddr,st_port上 *这里再次修改为出接口的地址和端口,暂不清楚用意 */ st->st_localaddr = st->st_interface->ip_addr; st->st_localport = st->st_interface->port; return ikev2parent_outI1_withstate(st, whack_sock, c , predecessor, policy , try, importance , uctx); }
此函数的主要功能包括如下几个方面:
stf_status ikev2parent_outI1_withstate(struct state *st , int whack_sock , struct connection *c , struct state *predecessor , lset_t policy , unsigned long try /* how many attempts so far */ , enum crypto_importance importance , struct xfrm_user_sec_ctx_ike * uctx UNUSED ) { struct db_sa *sadb; int groupnum; int need_to_add_pending = 0; /*根据配置的策略索引找到对应的SADB模板*/ int policy_index = POLICY_ISAKMP(policy , c->spd.this.xauth_server , c->spd.this.xauth_client); /* set up new state */ st->st_ikev2 = TRUE;/*用来标记是否为IKEv2*/ change_state(st, STATE_PARENT_I1);/*当前状态为STATE_PARENT_I1*/ st->st_try = try; /* IKE version numbers -- used mostly in logging */ st->st_ike_maj = IKEv2_MAJOR_VERSION; st->st_ike_min = IKEv2_MINOR_VERSION; st->st_policy = policy & ~POLICY_IPSEC_MASK; st->st_ikev2_orig_initiator = TRUE; ... ... /* * now, we need to initialize st->st_oakley, specifically, the group * number needs to be initialized. */ groupnum = 0; st->st_sadb = &oakley_sadb[policy_index]; /*根据配置信息alg_info_ike生成相应的sadb*/ sadb = oakley_alg_makedb(st->st_connection->alg_info_ike , st->st_sadb, 0); if(sadb != NULL) { st->st_sadb = sadb; } /*由于ikev1和ikev2的SA载荷的结构不一致,因此需要做一个转换*/ sadb = st->st_sadb = sa_v2_convert(st->st_sadb); { unsigned int pc_cnt; /* look at all the proposals */ if(st->st_sadb->prop_disj!=NULL) {/*获取SA载荷中采用的DH组,只获取第一个DH即可*/ for(pc_cnt=0; pc_cnt < st->st_sadb->prop_disj_cnt && groupnum==0; pc_cnt++) { struct db_v2_prop *vp = &st->st_sadb->prop_disj[pc_cnt]; unsigned int pr_cnt; /* look at all the proposals */ if(vp->props!=NULL) { for(pr_cnt=0; pr_cnt < vp->prop_cnt && groupnum==0; pr_cnt++) { unsigned int ts_cnt; struct db_v2_prop_conj *vpc = &vp->props[pr_cnt]; for(ts_cnt=0; ts_cnt < vpc->trans_cnt && groupnum==0; ts_cnt++) { struct db_v2_trans *tr = &vpc->trans[ts_cnt]; if(tr!=NULL&& tr->transform_type == IKEv2_TRANS_TYPE_DH) { groupnum = tr->transid; } } } } } } } if(groupnum == 0) { groupnum = OAKLEY_GROUP_MODP2048;/*如果未设置DH组,则默认使用MODP2048,这里是猜想!!!*/ } st->st_oakley.group=lookup_group(groupnum); st->st_oakley.groupnum=groupnum; /* now. we need to go calculate the nonce, and the KE */ { struct ke_continuation *ke = alloc_thing(struct ke_continuation , "ikev2_outI1 KE"); stf_status e; ke->md = alloc_md(); ke->md->from_state = STATE_IKEv2_BASE; ke->md->svm = &ikev2_parent_firststate_microcode; ke->md->st = st; set_suspended(st, ke->md); if (!st->st_sec_in_use) {/* st_sec_in_use ???*/ pcrc_init(&ke->ke_pcrc); ke->ke_pcrc.pcrc_func = ikev2_parent_outI1_continue; /*计算KE需要确定使用的DH组,因此使用了st->st_oakley.group*/ e = build_ke(&ke->ke_pcrc, st, st->st_oakley.group, importance); if( (e != STF_SUSPEND && e != STF_INLINE) || (e == STF_TOOMUCHCRYPTO)) { delete_state(st); } } else { /* this case is that st_sec already is initialized */ e = ikev2_parent_outI1_tail((struct pluto_crypto_req_cont *)ke , NULL); } reset_globals(); return e; } }
此函数最主要的功能就是构建协商报文。具体的步骤包括如下几个方面:
static stf_status ikev2_parent_outI1_common(struct msg_digest *md , struct state *st) { /* struct connection *c = st->st_connection; */ int numvidtosend = 0; #ifdef PLUTO_SENDS_VENDORID numvidtosend++; /* if we need to send Openswan VID */ #endif /* set up reply */ /* * reply_buffer:真正开辟的内存空间,用来存储报文 * reply_stream:可以简单的认为是个指针,记录已经使用的位置 * **/ init_pbs(&reply_stream, reply_buffer, sizeof(reply_buffer), "reply packet"); /*1. 构造HDR头部*/ { struct isakmp_hdr hdr; zero(&hdr); /* default to 0 */ /* testing fake major new IKE version, should fail */ if(DBGP(IMPAIR_MAJOR_VERSION_BUMP)) hdr.isa_version = IKEv2_MAJOR_BUMP << ISA_MAJ_SHIFT | IKEv2_MINOR_VERSION; /* testing fake minor new IKE version, should success */ else if(DBGP(IMPAIR_MINOR_VERSION_BUMP)) hdr.isa_version = IKEv2_MAJOR_VERSION << ISA_MAJ_SHIFT | IKEv2_MINOR_BUMP; else { /* normal production case with real version */ hdr.isa_version = IKEv2_MAJOR_VERSION << ISA_MAJ_SHIFT | IKEv2_MINOR_VERSION; } hdr.isa_xchg = ISAKMP_v2_SA_INIT; hdr.isa_flags = IKEv2_ORIG_INITIATOR_FLAG(st); memcpy(hdr.isa_icookie, st->st_icookie, COOKIE_SIZE); /* R-cookie, msgid are left zero */ if (!out_struct(&hdr, &isakmp_hdr_desc, &reply_stream, &md->rbody)) { reset_cur_state(); return STF_INTERNAL_ERROR; } } /* send an anti DOS cookie, 4306 2.6, if we have received one from the * responder */ if(st->st_dcookie.ptr) { chunk_t child_spi; memset(&child_spi, 0, sizeof(child_spi)); /* *anti Dos时通过在报文中添加一个通知载荷,里面包含对端的cookie信息(st->st_dcookie) * * HDR头部中的np字段填充的是设么????? --->ship_v2N -->pbs_set_np -->out_struct */ ship_v2N(ISAKMP_NEXT_NONE, ISAKMP_PAYLOAD_NONCRITICAL, PROTO_ISAKMP, &child_spi, v2N_COOKIE, &st->st_dcookie, &md->rbody); } /* SA out */ { u_char *sa_start = md->rbody.cur; /* if we have an OpenPGP certificate we assume an * OpenPGP peer and have to send the Vendor ID */ /* * * 如何判断一个SA是ikev1 or ikev2的结构??? */ if(st->st_sadb->prop_disj_cnt == 0 || st->st_sadb->prop_disj) {/*SA结构未转换???*/ st->st_sadb = sa_v2_convert(st->st_sadb); } if (!ikev2_out_sa(&md->rbody , PROTO_ISAKMP , st->st_sadb , st, TRUE /* parentSA */ , ISAKMP_NEXT_v2KE)) { openswan_log("outsa fail"); reset_cur_state(); return STF_INTERNAL_ERROR; } /* save initiator SA for later HASH */ if(st->st_p1isa.ptr == NULL) /* no leak! (MUST be first time) */ { clonetochunk(st->st_p1isa, sa_start, md->rbody.cur - sa_start , "sa in main_outI1"); } } /* send KE */ if(!justship_v2KE(st, &st->st_gi, st->st_oakley.groupnum, &md->rbody, 0)) return STF_INTERNAL_ERROR; /* send NONCE */ if(!justship_v2Nonce(st, &md->rbody, &st->st_ni, 0)) { return STF_INTERNAL_ERROR; } if(!justship_v2nat(st, &md->rbody)) {/*填充NAT-D载荷*/ return STF_INTERNAL_ERROR; } /* Send Vendor VID if needed */ {/*省略了NAT-T的VID???*/ /*pluto_vendorid在init_pluto_vendorid()完成了初始化*/ pbs_set_np(&md->rbody, ISAKMP_NEXT_v2V); if (!out_generic_raw(0, &isakmp_vendor_id_desc, &md->rbody , pluto_vendorid, strlen(pluto_vendorid), "Vendor ID"))/*pluto_vendorid内容并不是真正的VID*/ return STF_INTERNAL_ERROR; } close_message(&md->rbody); close_output_pbs(&reply_stream); /* let TCL hack it before we mark the length and copy it */ TCLCALLOUT("v2_avoidEmitting", st, st->st_connection, md); freeanychunk(st->st_tpacket); clonetochunk(st->st_tpacket, reply_stream.start, pbs_offset(&reply_stream) , "reply packet for ikev2_parent_outI1_tail"); /* save packet for later signing *//*将第一个报文保存起来*/ freeanychunk(st->st_firstpacket_me); clonetochunk(st->st_firstpacket_me, reply_stream.start , pbs_offset(&reply_stream), "saved first packet"); /* Transmit */ send_packet(st, __FUNCTION__, TRUE); delete_event(st); event_schedule(EVENT_v2_RETRANSMIT, EVENT_RETRANSMIT_DELAY_0, st); reset_cur_state(); return STF_OK; }
IKEv2 支持DH猜想
在 IKE_SA_INIT 交换阶段,发起方采用“猜”的办法,猜一个响应方最可能使用的 DH 组携带在第一条消息中发送。响应方根据发起方“猜”的 DH 组来响应发起方。如果发起方猜测成功,则这样通过两条消息就可以完成 IKE_SA_INIT 交换。如果发起方猜测错误,则响应方会回应一个INVALID_KE_PAYLOAD 消息,并在该消息中指明将要使用的 DH 组。之后,发起方采用响应方指定的 DH 组重新发起协商。这种 DH 猜想机制,使得发起方的 DH 组配置更为灵活,可适应不同的响应方。
IKEv2 支持cookie-challenge机制
在 IKE_SA_INIT 交换中消息是明文传输的,响应方接收到第一个消息后无法确认该消息是否来自一个仿冒的地址。如果此时一个网络攻击者伪造大量地址向响应方发送 IKE_SA_INIT 请求,根据IKEv1 协议,响应方需要维护这些半开的 IKE 会话信息,从而耗费大量响应方的系统资源,造成对响应方的 DoS 攻击。IKEv2 使用 cookie-challenge 机制来解决这类 DoS 攻击问题。当响应方发现存在的半开 IKE SA 超过指定的数目时,就启用 cookie-challenge 机制。响应方收到 IKE_SA_INIT 请求后,构造一个 Cookie通知载荷并响应发起方,若发起方能够正确携带收到的 Cookie 通知载荷向响应方重新发起IKE_SA_INIT 请求,则可以继续后续的协商过程。
IKEv2中的SA载荷部分内容
IKEv1中的SA载荷部分内容
IKEv1与IKEv2除了载荷类型不同,报文的结构也发生了变化,IKEv2中没有了属性载荷,相反IKEv1中的每一个属性载荷对应IKEv2中的一个变化载荷。因此IKEv2在oakley_alg_makedb()
生成SA后需要sa_v2_convert()
进行依次格式转换,转换为IKEv2的SA格式。
未经本人同意,不得转载!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。