深入了解校园网802.1x认证的EAP协议(2)——神州数码认证细节

各地的校园网认证有很多种品牌,正因为EAP可扩展的特点,基本上每个品牌的认证方案都会互不相同。从商业角度考虑,当使用了某个品牌的认证系统后,很可能就不能使用其他牌子的交换机了;而对用户来说,这不仅带来了以后维护的问题,还因为各个厂商开发的EAP客户端的投入不同,质量良莠不齐,占用内存大、限制多(比如有些为了防止用户开代理,加入多网卡检测,结果用户就不能使用虚拟机、手机GPRS Modem等)、不支持非Win平台。

这就是我们研究这些厂商自定义的EAP协议的意义。

PT实现的神州数码Linux客户端项目主页:http://code.google.com/p/zdcclient/

以下的说明是基于EAP协议过程的补充描述,如果不清楚EAP,可先读深入了解校园网802.1x认证的EAP协议(1)——EAP的总体流程

神州数码的私有信息尾结构神州数码协议里面最特别的地方是有一段46字节的信息,其位于EAP报文后,属于EAPOL报文的尾端部分,在发往服务器的EAP_RESPONSE_IDENTITY和EAP_RESPONSE_MD5_Challenge两种报文里面都需要附上该46字节的信息以供验证。

在PT的神州数码验证程序zdclient里面,zdclient.h定义了这个信息结构:

struct dcba_tailer {
    bpf_u_int32     local_ip;
    bpf_u_int32     local_mask;
    bpf_u_int32     local_gateway;
    bpf_u_int32     local_dns;
    u_char          username_md5[16];
    u_char          client_ver[13];
};
local_ip 4字节 本机IP
local_mask 4字节 本机掩码
local_gateway 4字节 本机网关
local_dns 4字节 本机DNS服务器
username_md5 16字节 用户名的MD5值
client_ver 13字节 客户端版本号ASCII码

当然这个结构体只有45字节,实际上在其附到EAP报文时,46字节的首字节用以表示认证所使用的DHCP模式,但由于其加入到上面这个结构体中会引起内存对齐问题,所以暂且让这个DHCP位不属于这个结构。

DHCP位只有0和非0两个状态,前者表示静态,非DHCP模式,后者则是DHCP模式。

这里所谓的DHCP模式并不跟操作系统给网卡获得IP那个DHCP概念等价。在常见的DHCP模式中,完成了认证之后需要重新运行dhclient之类的DHCP客户端来更新网卡的IP,但实际上并不一定如此,实际上是否需要更新得看具体环境,比如广州大学这里,我们需要让程序以DHCP模式认证,但并不需要更新IP,因为系统启动后已经自动获得了。

在不同的验证系统,对信息体的校验的严紧度也不同(估计在服务器可配置)。一般静态IP模式下,需要正确的local_ip段来验证用户是否属于特定子网、端口,但是在动态DHCP模式下,在认证之前网卡是没有合法IP的,因此可以模仿Windows系统发送一个169.254.x.x的伪IP。至于网关和DNS地址,在实际使用中发现填不填影响不大,可留为0。用户名MD5就是用户名字符串的16字节MD5值;剩下的13字节则是包含认证客户端的版本号信息,也是一个普通的字符串,目前已知的版本号包括`3.5.04.1013fk’、`3.5.04.1110fk’、`3.5.04.0324’等……有些网络环境会验证这个字符串,如果客户端版本不合适,就发出相应警告,比如目前武汉大学的认证系统只能使用`3.5.04.1013fk’、`3.5.04.1110fk’两个比较新的版本号来登录,但此前广州大学的神数系统对版本号通吃。

程序里面对这个结构的初始化代码是这样的:

    u_char local_info_tailer[46] = {0};
 
    local_info_tailer[0] = dhcp_on;
 
    struct dcba_tailer *dcba_info_tailer =
                (struct dcba_tailer *)(local_info_tailer + 1);
 
    dcba_info_tailer->local_ip          = local_ip;
    dcba_info_tailer->local_mask        = local_mask;
    dcba_info_tailer->local_gateway     = local_gateway;
    dcba_info_tailer->local_dns         = local_dns;
 
    char* username_md5 = get_md5_digest(username, username_length);
    memcpy (dcba_info_tailer->username_md5, username_md5, 16);
    free (username_md5);
 
    strncpy ((char*)dcba_info_tailer->client_ver, client_ver, 13);

然后我们构建一个IDENTITY报文。帧的目的地址是802.1x标准的多播地址01-80-c2-00-00-03(这里没登出Ether帧头部信息)。

    u_char eap_resp_iden_head[9] = {0x01, 0x00,
                                    0x00, 5 + 46 + username_length,  /* eapol_length */
                                    0x02, 0x01,
                                    0x00, 5 + username_length,       /* eap_length */
                                    0x01};
    eap_response_ident = malloc (14 + 9 + username_length + 46);
 
    data_index = 0;
//以太网帧头
    memcpy (eap_response_ident + data_index, eapol_eth_header, 14);
    data_index += 14;
//EAP帧头
    memcpy (eap_response_ident + data_index, eap_resp_iden_head, 9);
    data_index += 9;
//用户名
    memcpy (eap_response_ident + data_index, username, username_length);
    data_index += username_length;
//尾信息
    memcpy (eap_response_ident + data_index, local_info_tailer, 46);

分别把以太网报文头、EAP头、用户名、信息尾复制到一个数组内,这就是我们最后要发送的报文了。

不过观察神州官方版客户端发出的报文,DCBA信息体并不一定总紧跟eap报文,其在RESPONSE MD5 Challenge 报文里面就基本位于报文的最后,开始于0xa8,中间留空了一大堆0。在zdclient里面没有这样处理,一样紧跟在eap报文后。

注意EAPOL和EAP的长度字段,如果不算46字节的信息尾,它们是相等的,这在解释EAP报文时候提到过。

当成功认证了之后,服务器大概每隔20多秒会重新发来一个REQUEST IDENTITY报文,这个REQUEST IDENTITY和认证过程中的REQUEST IDENTITY只有一个字节的差别,就是eap_id字段,认证时为0x01,保鲜阶段为0x03,zdclient里面对两个报文使用同一个缓冲区,加了个hack,发保鲜报文时候就把这个位置改成0x03。

整个EAP认证过程中需要发出去的报文也只有四种,至于剩下的两个START和OFF,都只有几个字节,不涉及神州数码的信息尾字段,跟标准里面一样。

服务器的反馈信息

我们除了构建这些发出去的报文外,其实对服务器返回的信息兴趣不大,因为只需要判断其中的几个状态位,就知道下一步该干什么了。但是神州数码的反馈报文里面通常除了这些信息尾外,在SUCCESS和FAILURE报文里面通常都会包含一段中文文字,告诉用户是什么问题或者什么通知。

这些信息PT是通过盯着抓来的报文盯出来的,因为其使用GB2312编码的中文,通常使用含高位的ASCII值,看到一堆数值都大于0x80,尝试着放到Python里面按GBK解码,才发现的。

用来搜索报文内的中文信息的python函数:

def searchgbk(byte_array):
    for i in range (len (byte_array)):
        print "[%02x] %s" % (i, byte_array[i:].decode('gbk', 'ignore'))

最后总结出中文信息的规律:
在报文的特定位置出现0x12的首导符,紧跟着一个字节表示该信息的长度,之后便是该信息。0x12出现的位置有:[0x2A]、[0x42]、[0x9A]、[0x120]

最后的完整过程可见print_server_info函数的实现,print_server_info获得字符串的地址后送入转码函数,由iconv的API转换成UTF-8编码的字符串后才由printf输出。

zdclient的报文“驱动式”实现

回顾神州数码协议的整个流程,可知基本上客户端基本上都是在服务器发来了请求报文之后才需要发出应答报文(颇有颠倒了客户-服务角色的意味),其实这样对验证服务器的压力挺大的,不过这不关我们事,而且这也方便了我们对客户端程序的设计。所以zdclient采用了“报文驱动式”的流程,就是说,除了一开始发送一个上线请求,以后接收到什么就应答什么,其他时间一直在无限循环里面loop,睡觉。

libpcap提供了回呼(callback)机制,就是设定了某个回呼函数,只有当网卡接收到特定报文时候才进行中断响应。zdclient里面的get_packet函数就是回呼函数,所有工作都是有它驱动完成的。希望这一点有助于阅读和以后再次开发zdclient的朋友理解代码。

文章分类 Programming 标签: , , ,
3 comments on “深入了解校园网802.1x认证的EAP协议(2)——神州数码认证细节
  1. Insion说道:

    PT你好。我想在mac os x(leopard)下用你的源码重新编译一份for mac版的神州数码的登录器,我可以提供我的环境数据,我想你能不能协助一下我.

  2. Delusion说道:

    原来你就是传说中的高人,虽然看不大懂内容,不过你的探索可是我这个校园mac族的福音!谢谢你!加油!

  3. sadman说道:

    PT哥.我们学校用神州数码3.5.10.0621版本的.能解决一下下吗.您浪费一点点时间可是救了我们好多人啊.

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*