牛骨文教育服务平台(让学习变的简单)
博文笔记

ipsec VPN防重放的实现过程-图解(参考strongswan)

创建时间:2018-10-12 投稿人: 浏览次数:387

防重放机制有三种:时间戳、挑战和滑动窗口。在strongswan中,使用了第三种机制->滑动窗口,根据strongswan源代码,它的主要原理画了一个简图,我自己设置了一个游标,方便理解,表示指向当前接受报文的最大序列号,因为滑动窗口是跟这个值相关的

如图:滑动窗口长度为10,这边假设从96-105序号的报文全部都接受了,当前序号为105的报文,观察当接受序列号106时候会发生怎样的变化。

滑动窗口它所维护序号就是96-105,滑动窗口的特点是,滑动窗口是维护[当前序列号-滑动窗口大小+1]至[当前序列号]的报文,相应序列号的报文有收到,相应窗口置1,否则置0,当然当前96到105是全部都接受了,当报文来时,它会做如下判断:

1.如果接下来获得报文序号是m,m>105 && m - 105 < 10,那么窗口会往前移动,如下一个报文是106,窗口会往前移动,变成维护97-106的序列号,游标移动到106。

 2.如果接下来获得报文序号是m,m<105,比如m=98,那么窗口会检查对应窗口是否有置1,已经置1了就会丢弃报文,实现放重发功能,如果该窗口为0,证明还没接受过报文,此时会接受报文,但是游标不移动。

(这里的第1点要注意要判断m-105<10,比如当前是接受序号为117报文而不是106,它维护的是[117-10+1]-[117]=108-117,108已经大于105了,所以滑动窗口要全部置零,这点在后面的图解中有体现)

所以总结就一点:

序列号>游标值,移动游标实现移动滑动窗口,序列号<游标值,不移动游标进行防重发验证

strongswan防重放代码原理:

在代码中它是申请了一个数组,数组的长度就是滑动窗口的长度,它的构造很巧妙,如图,还是跟上述一样,比如当前接受到最大序列号为105,当它接受到106的报文时的变化。

 可以看出,序列号为105,根据上面的它维护的是[105-10+1]-[105]=96-105,它在数组形式如图,数组下标为0,置1,表示接受101报文已接受,一直到数组下标为9,置1,表示已接受100的报文。当106报文到来时候,下标5对应的数组值先置0再置1,表示接受了106的报文,同时也把已接受96的报文信息删除了,实现滑动窗口的移动。

以这种规律,再假设现在接受序列号报文214,那么它在数组的形式是:

先确定214在哪个下标中,然后依次填写其他值,就可以知道它在数组的形式。

strognswan代码以及简易图解防重放过程:

图中,报文来的序号依次为1,2,3,5,6,7,8,910,11,13,13,25

序号1到10的报文我画成灰色(中间缺失了序号4的报文),11到13的报文我画成橘色(中间缺失了序号12的报文), 25为绿色,滑动窗口为白色表示没有相应置1,其他颜色都是表示已经置1。

/**
 * Set or unset a bit in the window.
 */
static inline void set_window_bit(private_esp_context_t *this,
								  u_int index, bool set)
{
	u_int i = index / CHAR_BIT;

	if (set)
	{
		this->window.ptr[i] |= 1 << (index % CHAR_BIT);
	}
	else
	{
		this->window.ptr[i] &= ~(1 << (index % CHAR_BIT));
	}
}

/**
 * Get a bit from the window.
 */
static inline bool get_window_bit(private_esp_context_t *this, u_int index)
{
	u_int i = index / CHAR_BIT;

	return this->window.ptr[i] & (1 << index % CHAR_BIT);
}

/**
 * Returns TRUE if the supplied seqno is not already marked in the window
 */
static bool check_window(private_esp_context_t *this, uint32_t seqno)
{
	u_int offset;

	offset = this->last_seqno - seqno;
	offset = (this->seqno_index - offset) % this->window_size;
	return !get_window_bit(this, offset);
}

METHOD(esp_context_t, verify_seqno, bool,
	private_esp_context_t *this, uint32_t seqno)
{
	if (!this->inbound)
	{
		return FALSE;
	}

	if (seqno > this->last_seqno)
	{	/*       |----------------------------------------|
		 *  <---------^   ^   or    <---------^     ^
		 *     WIN    H   S            WIN    H     S
		 */
		return TRUE;
	}
	else if (seqno > 0 && this->window_size > this->last_seqno - seqno)
	{	/*       |----------------------------------------|
		 *  <---------^      or     <---------^
		 *     WIN ^  H                WIN ^  H
		 *         S                       S
		 */
		return check_window(this, seqno);
	}
	else
	{	/*       |----------------------------------------|
		 *                       ^  <---------^
		 *                       S     WIN    H
		 */
		return FALSE;
	}
}
METHOD(esp_context_t, set_authenticated_seqno, void,
	private_esp_context_t *this, uint32_t seqno)
{
	u_int i, shift;

	if (!this->inbound)
	{
		return;
	}

	if (seqno > this->last_seqno)
	{	/* shift the window to the new highest authenticated seqno */
		shift = seqno - this->last_seqno;
		shift = shift < this->window_size ? shift : this->window_size;
		for (i = 0; i < shift; ++i)
		{
			this->seqno_index = (this->seqno_index + 1) % this->window_size;
			set_window_bit(this, this->seqno_index, FALSE);
		}
		set_window_bit(this, this->seqno_index, TRUE);
		this->last_seqno = seqno;
	}
	else
	{	/* seqno is inside the window, set the corresponding window bit */
		i = this->last_seqno - seqno;
		set_window_bit(this, (this->seqno_index - i) % this->window_size, TRUE);
	}
}

相关的代码的函数如图,代码的执行顺序是在报文解密前,调用verify_seqno函数,验证是否相应滑动窗口有置1,在解密成功后执行set_authenticated_seqno,移动游标即更新滑动窗口。

声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。