在snmp发展到V3版本后,把snmp的安全性提升到一个新高度,这同时也带来了实现上的复杂性。在02年,03年我都曾经想进一步的了解它的实现,但都没什么进展。
这次在实现Csnmp的过程中,又一次的接触到V3的底层实现机理。现把我们在实现Csnmp中的V3模块的时候,对V3的一些实现细节做一总结,希望能缩短一些朋友掌握SNMPV3的时间和难度。(本文针对的朋友是对snmpV3有个接触的,故对v3术语不做解释)
我们先把加密和HMAC部分先放一下,等我们先讲几个别的问题后,再来看加密和HMAC这部分的时候,就会非常清晰了。
现在先把读写控制权限这部分(VACM)做一分析。VACM这部分在Csnmp中实现的时候有如下三个Vector每个分别装如下一个类:SecuToGroupTableItem、VacmAccessTableItem、ViewTreeFamiTableItem它们分别代表VacmSecurityToGroupTable、VacmAccessTable、VacmViewTreeFamilyTable这三张V3协议中的表。这三张表是配合起来一起完成对读写Mib信息的权限控制的。首先,用securityName和securityModel从SecuToGroupVector取出其对应的groupName, 其次用刚才取到的groupName加上SecurityModel、SecurityLevel以及是读、还是写操作这四个参数从VacmAccessVector取出其相应的viewName。最后一步是VACM的关键,通过刚才上一步取到的ViewName,再加上request OID进行如下权限判断:对于ViewNameVector中的每一条记录首先判断其ViewName是否和我们取出来的ViewName相同,相同的话,看request OID是否是以该条记录中的subtree为开头的,如果是的话,再判断该条记录的familyType是included(1)还是excluded(2),如果是“included(1)“的话则继续判断按同样的步骤判断下一条记录,直到没有符合ViewName相同的记录。如果是“excluded(2)”的话,则退出,并向Manager(管理端)发出AuthError的错误Response。
也许上面的文档写的不怎么清楚,但请各位稍微仔细掠一下的,就会发现VACM这部分其实较为简单,因为在我们实现的过程中这一块真正核心的代码估计不超过100行。
下面我会花较大篇幅把各位关心的V3的加密和HMAC部分做一分析。首先讲解一下Key Localization也就是密钥的具体化,即该管理端下的每个Agent都有一个不同于其他Agent的Key。之所以要key Localization,这主要是V3协议考虑到一个管理端要管理多个Agent,如果管理端对每个Agent的密钥都要取记住的话,这将是非常不方便的,第二个原因是即使有一个Agent的key被attacker取到了,他也无法知道其他的Agent的key。在实现Key Localization的时候,为满足上面的一般都这么做:先用用户的password,通过(MD5)处理得到对应的User key0,然后把刚才的User key0+你要通讯的Agent的EngineID,再一次通过MD5处理,这就获得了你所需要的Localized Key。
这个Localized Key在接下来的加密和认证过程中都要用到(这是因为管理者可以只用一个password来产生加密的Key和认证的Key, 也就是加密的Key == 认证的key,但出于更好的安全性考虑的话,建议用两个不同的password来分别产生加密Key和认证Key)。
好,现在先就V3的Authentication的处理做一分析: 首先按SNMPV3 PDU的要求把所有部分都加上,但把USM部分中的msgAuthenticationParameters部分用连续的12个(byte)0x00填充。等所有部分都填充好后,用刚才讲到的认证Key+这个Request PDU一起,送入MD5,产生出一个12个byte的msgAuthenticationParameters,并用这个替换掉开始放入的12个空白byte,其实我们可以把msgAuthenticationParameters看作一个MAC它起到一个电子指纹的作用,这样一个V3版本的Request PDU就可以发给某一Agent了。
V3的加密处理过程如下:用DES算法进行加密。这就首先要得到一个8个byte的salt,实现的时候一般都是通过方法产生的,由那个你要通讯的Agent的snmpEngineBoots(重启次数)+管理端产生的一个系统随机数一起构造出8byte的salt。
在得到salt后,加上开始得到的加密Key,它们一起通过DES算法把byte[]形式化了的contextEngineID、contextName、PDU加密成密文。在Agent端的解密处理如下:加密Key的产生如管理端一样,salt就是Request PDU的msgPrivacyParameters。它们一起解密DES密文,得到加密前的东西。
水平有限只能写的这么清楚了,如有费解的地方请参考相关V3的部分。其实V3的实现,还有一些需要注意的,但这里没法一一讲清楚,我就再拣一个地方稍稍讲解一下。
V3版本的Agent和管理端的第一次通讯过程如下:1)管理端发起通讯请求,因为是第一次通讯故管理端它没有这个Agent的EngineID,于是msgAuthoritativeEngineID这一域置为NULL,并且User Name这一域为Initial,把这一Request PDU1发给Agent端,Agent收到这样一个Request PDU,它发现msgAuthoritativeEngineID为NULL,于是报错并向管理端发送report1,这个报文中包括了该Agent的EngineID以及snmpEngineBoots等域。 2)管理端在收到这个report1后,把这个Agent的EngineID存储下来。接下来如果管理端再向这个代理端发送Request PDU2,这个PDU的msgAuthoritativeEngineID字段就有值了。但这次还是会收到从Agent端来的错误报告,这次是报”notInTimeWindows)这是因为还没进行Agent和管理端的时间同步问题。这次Agent端返回给管理端的错误报告中包括了当前Agent的运行时间。管理端在收到这个report2后,又保存一下这个Agent的最新系统时间,供下次使用。在通过这样两次“握手“后,管理端和Agent才可以真正开始SNMP交互。
仅以此文献给所有那些执着追求的朋友们。
―――netcomm