IKEv1协议建立一对IPSec SA,使用主动模式需要9个报文,使用野蛮模式需要使用6个报文方能协商成功。IKEv2对IKEv1协议进行了优化,IKEv2只需要进行两次交互,使用 4 条消息就可以完成一个 IKEv2 SA 和一对 IPsec SA 的协商建立。IKEv2 定义了三种交互:
初始交换、
创建子 SA 交换
通知交换。
下图简单介绍一下 IKEv2 协商过程中的初始交换过程,之后我们将对ikev2_parent_inI1outR1()
接口相关的处理流程做一个简单说明。
初始化交换通过两次交换共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_parent_inI1outR1()
。
此函数是IKEv2协议 响应端开始协商的入口函数。主要功能包括:
根据收到报文四元组(IP和端口)查找连接:c
新建一个协商状态结构:state
根据发起端的地址等信息生成Cookie值
将连接上的参数信息、配置信息初始化state结构上参数
抗重放检测(Cookie challenge)
解析报文中的KE载荷,获取到DH组信息
static stf_status
ikev2_parent_inI1outR1_tail(struct pluto_crypto_req_cont *pcrc
, struct pluto_crypto_req *r);
stf_status ikev2parent_inI1outR1(struct msg_digest *md)
{
struct state *st = md->st;
lset_t policy = POLICY_IKEV2_ALLOW;
lset_t policy_hint = LEMPTY;
/*
* 根据隧道两端协商地址和端口来查找连接
*/
struct connection *c = find_host_connection(ANY_MATCH, &md->iface->ip_addr
, md->iface->port
, KH_IPADDR
, &md->sender
, md->sender_port
, POLICY_IKEV2_ALLOW, LEMPTY, &policy_hint);
if(c==NULL) {/*连接查找失败则提示相应的错误信息*/
if(policy_hint & POLICY_IKEV2_ALLOW) {
/* connection not found, because IKEv2 was not allowed */
/* send back AUTHENTICATION_FAILED per WG mailing list discussion */
openswan_log("connection refused, IKEv2 not authorized");
return STF_FAIL + v2N_AUTHENTICATION_FAILED;
}/*
* be careful about responding, or logging, since it may be that we
* are under DOS
*/
DBG_log("no connection with matching policy found\n");
return STF_FAIL + v2N_AUTHENTICATION_FAILED;
}
loglog(RC_COMMENT, "tentatively considering connection: %s\n", c ? c->name : "<none>");
if(!st) {/*如果没有对应的状态,则新建一个state*/
st = new_state();
/* set up new state */
memcpy(st->st_icookie, md->hdr.isa_icookie, COOKIE_SIZE);
/* initialize_new_state expects valid icookie/rcookie values, so create it now */
/*相应方根据发起方的地址信息、当前时间生成一个cookie*/
get_cookie(FALSE, st->st_rcookie, COOKIE_SIZE, &md->sender);
initialize_new_state(st, c, policy, 0, NULL_FD, pcim_stranger_crypto);
st->st_ikev2 = TRUE;
st->st_localaddr = md->iface->ip_addr;
st->st_localport = md->iface->port;
st->st_remoteaddr = md->sender;
st->st_remoteport = md->sender_port;
st->st_ike_maj = md->maj;
st->st_ike_min = md->min;
change_state(st, STATE_PARENT_R1);md->st = st;
md->from_state = STATE_IKEv2_BASE;
md->transition_state = st;
}
/* check,as a responder, are we under dos attack or not
* if yes go to 6 message exchange mode. it is a config option for now.
* TBD set force_busy dynamically
* Paul: Can we check for STF_TOOMUCHCRYPTO ?
*/
if(force_busy == TRUE)/*开启抗重放!!!!!!!!*/
{
u_char dcookie[SHA1_DIGEST_SIZE];
chunk_t dc;
/*计算用于cookie challenge的cookie值*/
ikev2_get_dcookie( dcookie, st->st_ni, &md->sender, st->st_icookie);
dc.ptr = dcookie;
dc.len = SHA1_DIGEST_SIZE;/*20bytes*/ /* check if I1 packet contian KE and a v2N payload with type COOKIE */
/*报文格式??? : HDR + NOTIF + KE*/
if ( md->chain[ISAKMP_NEXT_v2KE] && md->chain[ISAKMP_NEXT_v2N] &&
(md->chain[ISAKMP_NEXT_v2N]->payload.v2n.isan_type == v2N_COOKIE))
{/*对方的协商报文中包含了cookie*/
/*
* ISAKMP_NEXT_v2N : IKEv2 通知载荷
* ISAKMP_NEXT_v2Ni : 发起端Nonce载荷
* ISAKMP_NEXT_v2Nr : 接收端Nonce载荷
*/
u_int8_t spisize;
const pb_stream *dc_pbs;
chunk_t blob;
DBG(DBG_CONTROLMORE
, DBG_log("received a DOS cookie in I1 verify it"));
/* we received dcookie we send earlier verify it */
spisize = md->chain[ISAKMP_NEXT_v2N]->payload.v2n.isan_spisize;
dc_pbs = &md->chain[ISAKMP_NEXT_v2N]->pbs;
blob.ptr = dc_pbs->cur + spisize;
blob.len = pbs_left(dc_pbs) - spisize;
DBG(DBG_CONTROLMORE
,DBG_dump_chunk("dcookie received in I1 Packet", blob);
DBG_dump("dcookie computed", dcookie, SHA1_DIGEST_SIZE));
/*检查收到的cookie和本端发送的cookie是否一致!!!*/
if(memcmp(blob.ptr, dcookie, SHA1_DIGEST_SIZE)!=0) {
openswan_log("mismatch in DOS v2N_COOKIE,send a new one");
SEND_V2_NOTIFICATION_DATA(md, st, v2N_COOKIE, &dc);
return STF_FAIL + v2N_INVALID_IKE_SPI;
}
DBG(DBG_CONTROLMORE
,DBG_log("dcookie received match with computed one"));
}
else/*收到的报文中并不包含COOKIE载荷,因此需要发起cookie challenge*/
{/*COOKIE challenge通知载荷发送*/
/* we are under DOS attack I1 contains no DOS COOKIE */
DBG(DBG_CONTROLMORE
,DBG_log("busy mode on. receieved I1 without a valid dcookie");
DBG_log("send a dcookie and forget this state"));
SEND_V2_NOTIFICATION_DATA(md, st, v2N_COOKIE, &dc);
return STF_FAIL;
}
}
else {
DBG(DBG_CONTROLMORE ,DBG_log("will not send/process a dcookie"));
}
/*
* If we did not get a KE payload, we cannot continue. There should be
* a Notify telling us why. We inform the user, but continue to try this
* connection via regular retransmit intervals.
*/
if(md->chain[ISAKMP_NEXT_v2N] && (md->chain[ISAKMP_NEXT_v2KE] == NULL))
{
const char *from_state_name = enum_name(&state_names, st->st_state);
const u_int16_t isan_type = md->chain[ISAKMP_NEXT_v2N]->payload.v2n.isan_type;
openswan_log("%s: received %s"
, from_state_name
, enum_name(&ikev2_notify_names, isan_type));
return STF_FAIL + isan_type;
} else if( md->chain[ISAKMP_NEXT_v2N]) {
/* XXX/SML: KE payload came with a notification-- is there a problem? */
DBG(DBG_CONTROL,DBG_log("received a notify.."));
}
/*
* We have to agree to the DH group before we actually know who
* we are talking to. If we support the group, we use it.
*
* It is really too hard here to go through all the possible policies
* that might permit this group. If we think we are being DOS'ed
* then we should demand a cookie.
*/
{
struct ikev2_ke *ke;
if (md->chain[ISAKMP_NEXT_v2KE] == NULL)
return STF_FAIL;ke = &md->chain[ISAKMP_NEXT_v2KE]->payload.v2ke;
/*KE载荷中包含DH GROUP信息*/
st->st_oakley.group=lookup_group(ke->isak_group);/*查找DH GROUP,并存储在st->st_oakley上*/
if(st->st_oakley.group==NULL) {
char fromname[ADDRTOT_BUF]; addrtot(&md->sender, 0, fromname, ADDRTOT_BUF);
openswan_log("rejecting I1 from %s:%u, invalid DH group=%u"
,fromname, md->sender_port, ke->isak_group);
return v2N_INVALID_KE_PAYLOAD;
}
}
/* now. we need to go calculate the nonce, and the KE */
{
struct ke_continuation *ke = alloc_thing(struct ke_continuation
, "ikev2_inI1outR1 KE");
stf_status e;ke->md = md;
set_suspended(st, ke->md);
if (!st->st_sec_in_use) {
pcrc_init(&ke->ke_pcrc);
ke->ke_pcrc.pcrc_func = ikev2_parent_inI1outR1_continue;
e = build_ke(&ke->ke_pcrc, st, st->st_oakley.group, pcim_stranger_crypto);
if(e != STF_SUSPEND && e != STF_INLINE) {
loglog(RC_CRYPTOFAILED, "system too busy");
delete_state(st);
}
} else {
e = ikev2_parent_inI1outR1_tail((struct pluto_crypto_req_cont *)ke
, NULL);
}
reset_globals();
return e;
}
}
此函数最主要的功能就是构建应答报文。具体的步骤包括如下几个方面:
保存对端发送的协商报文,用于后续认证操作
解析协商报文中的通知载荷
构建IKEv2报文头部
构建SA建议载荷
解析对端KE载荷
解析对端Nonce载荷。
构建KE载荷
构建Nonce载荷
构建NAT-D载荷
保存当前报文供后续的认证等操作
发送报文
static stf_status
ikev2_parent_inI1outR1_tail(struct pluto_crypto_req_cont *pcrc
, struct pluto_crypto_req *r)
{
struct ke_continuation *ke = (struct ke_continuation *)pcrc;
struct msg_digest *md = ke->md;
struct payload_digest *const sa_pd = md->chain[ISAKMP_NEXT_v2SA];
struct state const st = md->st;
stf_status notok;
int numvidtosend=0;
#ifdef PLUTO_SENDS_VENDORID
numvidtosend++; / we send Openswan VID */
#endif
if (sa_pd == NULL) {
return STF_FAIL;
}
/* note that we don't update the state here yet */
/* record first packet for later checking of signature */
clonetochunk(st->st_firstpacket_him, md->message_pbs.start
, pbs_offset(&md->message_pbs), "saved first received packet");
/*
* verify the NAT DETECTION notify messages before answering.
* on the responder side, this allows us to detect when *we* are behind
* at NAPT (probably with a port-forward).
*
* If we are, then we set a bit saying so, which later on will make us pick the
* UDP encapsulation for packets. It is up to the initiator to switch ports
* from 500 to 4500. Could be they have already done so, we do not care here.
*/
if(md->chain[ISAKMP_NEXT_v2N]) {/*处理通知载荷,其中包括NAT-D探测*/
ikev2_process_notifies(st, md);
}
/* make sure HDR is at start of a clean buffer */
zero(reply_buffer);
init_pbs(&reply_stream, reply_buffer, sizeof(reply_buffer), "reply packet");
/* HDR out */
{
struct isakmp_hdr r_hdr = md->hdr;memcpy(r_hdr.isa_rcookie, st->st_rcookie, COOKIE_SIZE);
r_hdr.isa_version = IKEv2_MAJOR_VERSION << ISA_MAJ_SHIFT | IKEv2_MINOR_VERSION;
r_hdr.isa_np = ISAKMP_NEXT_v2SA;
r_hdr.isa_flags = ISAKMP_FLAGS_R|IKEv2_ORIG_INITIATOR_FLAG(st);
r_hdr.isa_msgid = st->st_msgid;
if (!out_struct(&r_hdr, &isakmp_hdr_desc, &reply_stream, &md->rbody))
return STF_INTERNAL_ERROR;
}
/* start of SA out */
{
struct ikev2_sa r_sa = sa_pd->payload.v2sa;/*收到的报文中的SA头部信息*/
v2_notification_t rn;
pb_stream r_sa_pbs;r_sa.isasa_np = ISAKMP_NEXT_v2KE; /* XXX */
if (!out_struct(&r_sa, &ikev2_sa_desc, &md->rbody, &r_sa_pbs))
return STF_INTERNAL_ERROR;
/* SA body in and out *//*解析对方的SA建议载荷,并将选择的建议载荷填充到r_sa_pbs中*/
rn = ikev2_parse_parent_sa_body(&sa_pd->pbs, &sa_pd->payload.v2sa,
&r_sa_pbs, st, FALSE);
if (rn != v2N_NOTHING_WRONG)
return STF_FAIL + rn;
}
/解析对端的KE载荷,存储在st_gi中/
if((notok = accept_v2_KE(md, st, &st->st_gi, "Gi"))!=STF_OK) {
return notok;
}
/* Ni in *//*解析对端的Nonce载荷,存储在st_ni中*/
RETURN_STF_FAILURE(accept_v2_nonce(md, &st->st_ni, "Ni"));
/* send KE *//*填充KE*/
if(!ship_v2KE(st, r, &st->st_gr, &md->rbody, ISAKMP_NEXT_v2Nr))
return STF_INTERNAL_ERROR;
/* send NONCE *//*填充Nonce*/
unpack_nonce(&st->st_nr, r);
if(!justship_v2Nonce(st, &md->rbody, &st->st_nr, 0)) {
return STF_INTERNAL_ERROR;
}
if(!justship_v2nat(st, &md->rbody)) {/*填充NAT-D*/
return STF_INTERNAL_ERROR;
}
/* Send VendrID if needed VID */
{
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"))
return STF_INTERNAL_ERROR;
}
close_message(&md->rbody);
close_output_pbs(&reply_stream);
/* let TCL hack it before we mark the length. */
TCLCALLOUT("v2_avoidEmitting", st, st->st_connection, md);
/* keep it for a retransmit if necessary */
freeanychunk(st->st_tpacket);
clonetochunk(st->st_tpacket, reply_stream.start, pbs_offset(&reply_stream)
, "reply packet for ikev2_parent_inI1outR1_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");
/* while waiting for initiator to continue, arrange to die if nothing happens */
delete_event(st);
event_schedule(EVENT_SO_DISCARD, 300, st);
return STF_OK;
}
该函数主要的作用是解析协商报文中的SA建议载荷,并从中选择本地支持的算法进行协商。
主要流程如下:
根据配置的SA建议载荷转换为SADB结构
将SADB结构转换为IKEv2的SA结构
解析报文中的SA载荷
将报文SA中的算法进行排列组合, 并与本地SADB中的算法进行匹配
根据算法标识获取算法详细参数,并将其存储在state上
如果为响应端
v2_notification_t
ikev2_parse_parent_sa_body(
pb_stream sa_pbs, / body of input SA Payload */
const struct ikev2_sa *sa_prop UNUSED, /* header of input SA Payload */
pb_stream *r_sa_pbs, /* if non-NULL, where to emit winning SA */
struct state *st, /* current state object /
bool selection UNUSED / if this SA is a selection, only one
* tranform can appear. /
)
{
pb_stream proposal_pbs;
struct ikev2_prop proposal;
unsigned int np = ISAKMP_NEXT_P;
/ we need to parse proposal structures until there are none */
unsigned int lastpropnum=-1;
bool conjunction, gotmatch;
struct ikev2_prop winning_prop;
struct db_sa *sadb;
struct trans_attrs ta;
struct connection *c = st->st_connection;
/*根据配置确定选用的SADB模板*/
int policy_index = POLICY_ISAKMP(c->policy
, c->spd.this.xauth_server
, c->spd.this.xauth_client);
struct ikev2_transform_list itl0, *itl;
memset(&itl0, 0, sizeof(struct ikev2_transform_list));
itl = &itl0;
/* find the policy structures */
/*根据策略配置生成SADB信息*/
sadb = st->st_sadb;
if(!sadb) {
st->st_sadb = &oakley_sadb[policy_index];
sadb = oakley_alg_makedb(st->st_connection->alg_info_ike
, st->st_sadb, 0);
if(sadb != NULL) {
st->st_sadb = sadb;
}
sadb = st->st_sadb;
}
/*将SA结构由IKEv1转换为IKEv2*/
sadb = st->st_sadb = sa_v2_convert(sadb);
gotmatch = FALSE;
conjunction = FALSE;
zero(&ta);
while(np == ISAKMP_NEXT_P) {/*一般情况下只有一个建议载荷*/
/*
* note: we don't support ESN,
* so ignore any proposal that insists on it
*/
if(!in_struct(&proposal, &ikev2_prop_desc, sa_pbs, &proposal_pbs))
return PAYLOAD_MALFORMED;
if(proposal.isap_protoid != PROTO_ISAKMP) {
loglog(RC_LOG_SERIOUS, "unexpected PARENT_SA, expected child");
return PAYLOAD_MALFORMED;
}
... ...
gotmatch = FALSE;
{/*将报文中的建议载荷转换为struct ikev2_transform_list*/
stf_status ret = ikev2_process_transforms(&proposal
, &proposal_pbs, itl);
if(ret != STF_OK) return ret;
}
np = proposal.isap_np;/*下一个建议载荷 或者为空*/
if(ikev2_match_transform_list_parent(sadb
, proposal.isap_propnum
, itl)) {/*匹配成功的算法组合存储在itl中*/winning_prop = proposal;
gotmatch = TRUE;
/* gotmatch is true, so will never go inside if*/
}
}
/*
* we are out of the loop. There are two situations in which we break
* out: gotmatch == FALSE, means nothing selected.
*/
if(!gotmatch) {
return NO_PROPOSAL_CHOSEN;
}
/*
* since we found something that matched, we might need to emit the
* winning value.
*
* 从对方的建议载荷中找到了合适的算法信息;
*/
/*加密算法*/
ta.encrypt = itl->encr_transforms[itl->encr_i];
ta.enckeylen = itl->encr_keylens[itl->encr_i] > 0 ?
itl->encr_keylens[itl->encr_i] : 0;
ta.encrypter = (struct encrypt_desc *)ike_alg_ikev2_find(IKE_ALG_ENCRYPT
, ta.encrypt
, ta.enckeylen);
passert(ta.encrypter != NULL);
if (ta.enckeylen <= 0)
ta.enckeylen = ta.encrypter->keydeflen;
/*完整性哈希算法*/
ta.integ_hash = itl->integ_transforms[itl->integ_i];
ta.integ_hasher= (struct hash_desc *)ike_alg_ikev2_find(IKE_ALG_INTEG,ta.integ_hash, 0);
passert(ta.integ_hasher != NULL);
/*PRF 哈希算法*/
ta.prf_hash = itl->prf_transforms[itl->prf_i];
ta.prf_hasher = (struct hash_desc *)ike_alg_ikev2_find(IKE_ALG_HASH, ta.prf_hash, 0);
passert(ta.prf_hasher != NULL);
/*DH 算法*/
ta.groupnum = itl->dh_transforms[itl->dh_i];
ta.group = lookup_group(ta.groupnum);
st->st_oakley = ta;/*将算法信息存储到state上*/
if (r_sa_pbs != NULL)/*将选中的算法填充到应答报文的SA载荷上*/
{
return ikev2_emit_winning_sa(st, r_sa_pbs
, ta
, /*parentSA*/TRUE
, winning_prop);
}
return NOTHING_WRONG;
}
用途
名称
编号
IKE转换类型Ⅰ(加密)
ENCR_3DES
3
ENCR_NULL
11
ENCR_AES_CBC
12
ENCR_AES_CTR
13
IKE转换类型Ⅱ(PRF)
PRF_HMAC_MD5
1
PEF_HMAC_SHA1
2
PRF_AES128_CBC
4
IKE转换类型Ⅲ(完整性)
AUTH_HMAC_MD5_96
1
AUTH_HMAC_SHA1_96
2
AUTH_AES_XCBC_96
5
IKE转换类型Ⅳ(DH组)
1024 MODP (Group 2)
2
2048 MODP (Group 14)
14
安全关联(SA)负载包含了一个SPI数值和一套建议(通常是一个)。这些建议是通过一些复杂的建议结构关联起来的。每一个建议结构都会被编号(参见上表)并且包含一个IPsec协议标识符,此标识符用来支持采用的协议,如IKE, ESP, AH。
每一个建议载荷包含一个或者多个变换载荷用来描述指定协议的算法。通常情况下,AH协议仅有一个变换载荷(与完整性检验算法有关),ESP协议有两个变换载荷(与完整性检验算法和加密算法相对应),DH算法有四个转换结构(DH组编号、PRF算法、完整性检验算法、加密算法)。而第二个报文中的SA加密套件中的建议载荷便属于DH转换结构。
IKE_SA_INIT交换报文中处理SA载荷外,还包括KE载荷和Nonce载荷。
DH一旦交换完成,双方便可以计算出自己的SKEYSEED,该值用来生成所有与IKE_SA相关的子密钥,包括:SK_d, SK_ai, SK_ar, SK_ei, SK_er, SK_pi, SK_pr。
SKEYSEED的计算方式如下:
S
K
E
Y
S
E
E
D
=
p
r
f
(
N
i
∣
N
r
,
g
i
r
)
SKEYSEED = prf(Ni | Nr, g^{ir} )
SKEYSEED=prf(Ni∣Nr,gir)
密钥
用途
SK_ai, SK_ar
用于认证
SK_ei, SK_er
用于加密
SK_d
用于派生CHILD_SA密钥
SK_p
用于在IKE_AUTH交换中生成AUTH载荷
版本所有,谢绝转载!!!
手机扫一扫
移动阅读更方便
你可能感兴趣的文章