IKE实现方案

1        RFC文档对IKE实现的要求

2        实现的总体思想

IKE是作为一个守护进程运行的,负责处理用户的管理配置命令、同协商实体的交互、IKE数据报的处理以及同内核的SADB的交互。整个系统就按照功能划分成几个模块:IKE管理模块、IKE验证模块、消息处理模块。其中消息处理模块按照消息的来源又分为几个子模块:网络消息处理模块、内核消息处理模块、状态消息处理模块和用户命令处理模块。为了各个模块能对协商的数据进行共享,设计了IKE状态库模块,能够提供统一的接口实现查询,更新、删除、添加等操作。IKE作为一个应用层协议实现在应用层,但是需要同内核SADB进行SA消息的传递所以要提供了一个接口。本方案中实现了PF_KEY协议作为内核和IKE守护进程的接口。

 

文本框:

 

 

 

左图描述了本系统的基本框架。

其中用户管理接口是整个IPSEC网关配置的一个子界面,提供用户一个友好管理、配置、监视界面。

UDP/500表示IKE守护进程是利用UDP协议的500端口进行网络通信。

 

 

 

 

 

 

 

 

2.1      模块功能简要介绍

   管理模块负责处理用户的命令并调用对应的处理函数,分析运行数据并返回给用户管理接口。

       消息服务器模块负责监控几个消息队列,对其中的事件调用注册的处理函数。消息队列分为管理消息,网络消息,内核消息以及SA状态消息。

       IKE验证模块负责验证IKE协议的载荷数据,并构造响应或请求数据报文。

       IKE状态库记录IKE运行期间的需要的协商信息和当前的SA信息。

2.2      模块的交互关系

             管理模块与消息处理模块

管理模块能够把管理员的某些命令传递给消息处理模块的管理消息处理子模块。通信机制是利用AF_UNIX socket协议簇。系统创建一个临时运行文件,然后管理模块和管理消息子模块都创建一个 AF_UNIX socket  int sock = socket(AF_UNIX, SOCK_STREAM, 0)。管理模块向此socket发送管理消息,管理消息处理子模块监听此socket

       消息处理模块与IKE验证模块

网络消息处理模块分析并提取数据,组织成msg_digest数据结构,然后把此数据作为参数调用IKE验证模块进行处理。IKE验证模块返回后继处理标志,把构建的响应载荷或失败代码添加到msg_digest结构中。

数据结构(概念结构)如下

struct msg_digest {              

          原始数据包

          消息到达的接口

       发送者ip地址

       发送者端口号

          IKE载荷

加密标志

        包对应的协商对象的状态

       当前状态对象

      响应报文载荷

          验证失败原因

}

3        各个模块的实现

3.1      管理模块的实现

3.1.1       设计思想

管理模块对上提供系统的运行状态和状态设置服务,对下则提供系统需要的各种初始参数,包括自身和远程安全网关的身份信息、密钥信息、协商策略、协商时机,也包括了必要的防火墙策略设置信息以增强安全性。模块设计能够进行实时监控,实时反映系统的运行状态、及时响应网管的命令。

配置文件记录系统的初始运行参数,它记录的是静态且一般为固定的信息。日志文件记录系统运行以来的所有事件,包括策略更改、状态的更改、网络异常事件。

管理模块对上层提供了命令接口可以直接地动态装载和更改协商信息、启动协商,删除SA。模块这些命令数据,再把参数传递给消息处理模块来具体执行。

3.1.2       具体实现

3.1.2.1      功能接口

1.       载入、重载协商通道参数,启动、关闭协商通道

2.       sa的更新、删除、添加

3.       静态配置文件的修改

4.       数据统计、状态显示、检查日志

 

3.1.2.2      安全考虑

配置文件中的配置信息和身份、密钥信息采用shadow加密方式。

3.2      消息服务器模块

3.2.1       设计思想

在系统运行期间,管理模块可能要发送管理消息,内核发出的SA请求,更新消息,以及网络上传来的协商消息另外系统自身由于网络拥塞产生超时消息或设置的SA生命过期更新消息。所以本模块被设计成服务器模式,并对每种消息类型设置一条消息队列。模块轮询这几条消息队列,如果有消息就调用对应的消息处理函数来处理。

 

3.2.2       具体实现

3.2.2.1      管理消息处理模块

Admsg_handle接收到管理模块的消息之后,对消息进行完整性检查,根据消息的类型调用对应的处理函数。例如, terminate_connection终止协商、initiate_connection启动协商等等。

 

3.2.2.2      内核消息处理模块

内核响应消息的处理也分为两种:同步和异步。

同步消息是当调用函数向内核发送了pfkey消息,阻塞该进程监听内核的响应消息。例如在程序完成阶段2 sa协商向内核发送一个安装sa的消息,然后就等待内核的响应消息。如果此时该pfkey socket接口内无消息可读就返回,否则一直阻塞监听该socket接口。由于内核处理之后的响应消息发送给所有已打开的pfkey socket 接口,所以接收响应消息时要根据进程号和pfkey消息号确定是否为自己的消息,如果进程号不是自己的进程号且又不是内核进程号0和消息类型为SADB_ACQUIRE,那么丢弃此条消息。如果符合以上两个条件,检查pfkey消息号,如果是自己的消息号,处理它否则把它排队到异步消息队列pfkey_iq_head。此时如果此pfkey socket接口一直有消息可读,就一直阻塞此进程直到等到有它的响应消息来,当然没有消息就立刻返回。

异步消息只有这两种情况,一是进程发送给内核迟迟没有处理而以致延误发送响应的,二是内核主动发出的消息,例如请求SASA的过期。

模块监听到有消息到达时,取出消息。根据消息类型调用对应的消息分析处理函数,检查它的完整性和合法性。然后根据SA类型调用响应函数。

3.2.2.3      网络消息处理模块

网络消息处理模块即充当服务器又充当客户端角色。当监听UDP/500端口时,它是服务器,作为协商的响应者。服务器生成另一个socket用于它们之间的协商通信。当它发出协商请求时,它作为协商的发起者充当客户端。

模块的功能是把本地的IKE载荷数据封装成UDP报,把接收到的UDP/500数据报剥离出IKE载荷。

 

3.2.2.4      超时事件处理模块

当网络消息处理模块向外发送UDP报时,为了防止报文丢失就在超时事件队列中注册此事件。当设置的超时时间到达,超时事件处理模块就调用注册的超时处理函数,重发或丢弃。直到它重发次数到达或此事件被清除。当需要的报文到达时,网络消息处理模块又会把注册的超时事件从队列中清除。

生成的sa对象的过期事件同样注册在超时队列中。当软过期或硬过期到达,调用注册的处理函数,重协商或删除sa.

 

3.3      IKE验证模块

3.3.1       设计思想

根据IKE协议的RFC文档,每种模式的协商过程都有固定的消息条数且每条消息的内容都作明确的规定。有差别的就是在不同的认证方式下,载荷的加密/解密方式有所不同。这样按照协商过程中接收到的消息来划分协商状态,每种状态都有对应的状态迁移函数。当协商过程中下一条消息到来,就调用此时状态所对应的迁移函数进行分析处理,验证通过就跃迁到下一个状态。所有的状态迁移函数都定义好之后,地址统一放在状态迁移函数表中。当启动ike时,把每个状态的迁移函数入口地址写到一张索引表中。这样当需要对某条消息进行处理时,以此时协商的状态作索引找到的状态函数的入口地址,然后根据认证方式找到正确的状态迁移函数进行处理。

在迁移函数里按照协议规定对载荷数据进行验证,验证通过就要构建响应载荷。如果认证出现错误,根据安全性级别决定是否向peer发送通知载荷,来构建通知载荷。其中在阶段1时即isakmp sa还没有建立起来,通知载荷是明文发送的其没有hash载荷。

在阶段2的状态处理函数中如果sa已建立起来,则要通过pfkey接口向内核安装该sa

3.3.2       具体实现

stf_status  State_Transfer_Function(struct msg_digest *md)

参数md是由网络消息处理函数提取出的IKE协商交换载荷。返回值的类型为stf_status指示网络消息处理模块的后继处理。

STF_IGNORE    忽略

STF_NO_REPLY  /*成功,但没有响应报文发送*/

    STF_UNPEND_QUICK /* 阶段1协商成功,没有响应消息;协商所有的阶段2协商 */

    STF_REPLY     /*成功,需要发送响应报文*/

    STF_REPLY_UNPEND_QUICK       /* 响应,阶段1成功;协商所有的阶段2协商*/

    STF_INTERNAL_ERROR  /* 抛弃所有与此接收到报文相关的,协商失败 */

STF_FAIL  /* 某些地方验证失败,抛弃所有为此协商建立的数据结构;发送一个通知载荷。*/

3.4      IKE状态库

3.4.1       设计思想

IKE在开始协商时需要知道一些必要的协商策略、密钥信息、协商网关的身份数据等必要数据。在协商期间还需要记录协商对象的状态,协商对象要存放要协商SA的所有协商信息。IKE自动重协商也要监视SA的状态。这些数据统一组织管理,提供统一接口便于各模块共享。

3.4.2       具体实现

虽然称之为库,但是它不需要复杂的操作,数据结构通过指针组织成链表形式。查找、更新、删除、添加在对应的链表上操作。系统共有两条全局变量链表,分别是connectionsstates

Connection :记录的是一条协商通道的信息,它的集合可以看作ike的策略库

名字:标识一条connection

策略:策略中既包含isakmp 认证策略,又包括ipsec 验证加密策略以及无sa时的策略,以掩码位指示。Pluto使用16位的整形数值来记录,只定义了它实现的策略且没有预留出别的策略的位置。我考虑把这几种策略分开,这样不会增加太多空间花销且不用进行额外的分类操作,还可以扩展。Isakmp 策略:PSK DSS_sigRSA_sigRSA_encryptPRSA_encryptIPSEC策略:EncryptAuthenticatecompresstunnelpfs;其它:PASS DROPON_HOLD

时间参数:包括isakmp SA存活期,ipsec sa存活期 ,更新密钥时间间隔rekeymargin,随机更新参数refuzz,更新密钥重试次数。当sa 的剩余时间小于rekeymargin * ( 1- rnd%)时进行更新,其中rnd0refuzz内的数。防止协商双方同时进行密钥更新产生冗余的sa协商。

协商两网关主机的信息:包括身份(IPaddrFQDNUSER@FQDN,主机地址,下一个路由地址,子网地址,端口号等等。

接口设备

协商的sa的序列号

连接类型:CK_TEMPLATE,通配的连接即peer的地址为anyaddr。用于监听移动用户和内核的协商请求;CK_PERMANENT,一般连接;CK_INSTANCECK_TEMPLATE连接的实例化。CK_GOING_AWAY,正在被删除的连接。

其它信息(网关信息包括公钥,身份,根据DNSsec

state :记录协商的sa的状态,属性值,它的集合可以看作是ikeSA

序列号:

依附的connection

使用的加密/验证信息,包括算法类型,加密器/验证器,oakley 组,

AH/ESP/IPCOMP 信息,包括具体的协议属性,spi,密钥

阶段2用到的完美向前oakley

DOISituation, 策略,消息序列号,已使用的消息序列号,DH值,各种密钥素材值

状态

对应的超时事件

所有的connection结构被连接到connections链上,查找、更新、删除、添加在此链上操作。

4        面向内核接口的实现

4.1      设计思想

我们通过实现PF_KEY协议来作为内核和IKE守护进程的接口。PF_KEY是一个新的socket协议簇,用于可信任的密钥管理程序和操作系统内核内部的密钥管理进行通信。这里的密钥管理程序就是IKE守护进程,而内核密钥管理是SADB。它的概念模型如下:

 

PF_KEY协议簇的socket的操作同其他类型的socket操作无差别。

int  s = socket(PF_KEY, SOCK_RAW, PF_KEY_V2);

IKE守护进程通过使用这个socket接口来发送和接收信息与内核通信。对这个socket 的操作同其它socket一样,bind(), connect(), socketpair(), accept(), getpeername(), getsockname(), ioctl(), and listen().

 实现的主要消息种类:

             SADB_ADD        向内核的SADB增加一个SA

             SADB_DELETE     从内核的SADB删除一个SA

             SADB_GET         从内核的SADB获取一个SA

             SADB_REGISTER   IKE守护进程注册策自己能提供服务的协议类型,AHESP或其它

             SADB_EXPIRE      内核发送给IKE守护进程某个SA过期,包括软、硬过期

4.2      具体实现

内核部分

内核启动时调用ipsec_init()(/ipsec_init.c),为了使用pf_key接口它调用pfkey_init()(/pfkey_v2.c)。此函数注册它要服务的SA类型:AH,ESP,IPIP,如果配置了IP_COMP那么也注册。首先建立几个全局变量结构结构,然后把定义的SA类型添加到对应的pfkey_supported_list队列中,然后注册pfkeysocket操作集。

struct supported_list *pfkey_supported_list[SADB_SATYPE_MAX+1]

每种SA类型一个链表数组,说明了当前内核支持的Sa类型。

struct socket_list *pfkey_registered_sockets[SADB_SATYPE_MAX+1];

每种SA类型一个链表数组,说明了在当前内核中注册使用的pfkey socket

struct socket_list *pfkey_open_sockets = NULL

应用pfkey接口的进程打开的socket

struct sock *pfkey_sock_list = NULL;

应用pfkey接口的进程打开的socket对应的sock

IKE 部分

首先在IKE启动时调用初始化函数init_kernel()初始化pfkey接口,创建一个PF_KEY  SOCKET   pfkeyfd = socket(PF_KEY, SOCK_RAW, PF_KEY_V2);

IKE利用这个socket接口与内核传递消息。

接着调用pfkey_register(void),注册能被协商的SA类型

函数的实现及详细说明