IKEv2协议协商流程: (IKE-SA-INIT 交换)第一包

“我正在参加「掘金·启航计划」”

1. IKEv2 协商总体框架

IKEv1协议建立一对IPSec SA,使用主动模式需要9个报文,使用野蛮模式需要使用6个报文方能协商成功。IKEv2对IKEv1协议进行了优化,IKEv2只需要进行两次交互,使用 4 条消息就可以完成一个 IKEv2 SA 和一对 IPsec SA 的协商建立。IKEv2 定义了三种交互:

  • 初始交换

  • 创建子 SA 交换

  • 通知交换

    下面简单介绍一下 IKEv2 协商过程中的初始交换过程:

在这里插入图片描述

初始化交换通过两次交换共4个报文便可以完成一对IKE SA和IPSec SA的协商。上图主要用来描述协商报文的内容和对应的处理函数入口。下图则是用来说明各接口对应的协商状态。协商过程中是根据该状态来确定当前的协商阶段。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jyJmKgYJ-1602431591586)(F:%5C%E9%9A%8F%E7%AC%94%5CVPN%5CIKEv2.assets%5Cimage-20201011194904088.png)] 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 {...}表示里面的内容被加密和认证保护==。

2. 第一包流程图

在这里插入图片描述

3. openswan源码学习

3.1 ikev2parent_outI1()

==此函数是IKEv2协议发起协商的入口函数==。主要功能为:

  • 新建一个协商状态结构:state
  • 发起端随机成功Cookie值
  • 将连接上的参数信息、配置信息初始化state结构上参数
    • 隧道本端IP和端口
    • 隧道对端IP和端口
    • 隧道的出接口
    • 配置策略
    • ... ...
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);
}
复制代码

3.2 ikev2parent_outI1_withstate()

此函数的主要功能包括如下几个方面:

  • 根据配置策略(==认证方式: PSK or RSA etc.==)选择使用的SADB模板
  • 根据配置的算法信息(==alg_info_ike==)生成对应的SADB结构
  • 将SADB由IKEv1类型转换为IKEv2类型(两者的SA结构不相同)。
  • 获取SADB上的DH组(第一个即可,用作猜想的DH组号)
    • 使用猜想的DH组生成DH的公共值gi
    • ... ...
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;
    }
}
复制代码

3.3 ikev2_parent_outI1_common()

此函数最主要的功能就是构建协商报文。具体的步骤包括如下几个方面:

  • 构建IKEv2报文头部
  • IKEv2新增的用于抗重放功能的Cookie challenge操作
    • 如果先前收到了响应端的Cookie通知报文(载荷)时,使用收到的cookie构造一个通知载荷,用于相应端的抗重放。
  • 构建SA载荷。
  • 构建KE载荷
  • 构建Nonce载荷
  • 构建NAT-D载荷
  • 保存当前报文供后续的认证等操作
  • 发送报文
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;
}
复制代码

4. 注意事项

4.1 关于此报文中涉及的对IKEv2引入的“新特性”说明

  • 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 请求,则可以继续后续的协商过程。

4.2 在IKEv1与IKEv2在SA载荷结构上的不同之处:

  • ==IKEv2中的SA载荷部分内容==

在这里插入图片描述

  • ==IKEv1中的SA载荷部分内容==

在这里插入图片描述

IKEv1与IKEv2除了载荷类型不同,报文的结构也发生了变化,IKEv2中没有了属性载荷,相反IKEv1中的每一个属性载荷对应IKEv2中的一个变化载荷。因此IKEv2在oakley_alg_makedb()生成SA后需要sa_v2_convert()进行依次格式转换,转换为IKEv2的SA格式。


未经本人同意,不得转载!!!


猜你喜欢

转载自juejin.im/post/7109118166193668104