RADIUS 服务器收到设备端转发的用户名信息后,将该信息与数据库中的用户名表对比,找到该用户名对应的密码信息,用随机生成的一个加密字对它进行加密处理,同时也将此加密字通过 RADIUS Access-Challenge 报文发送给设备端,由设备端转发给客户端程序。
客户端程序收到由设备端传来的加密字(EAP-Request/MD5 Challenge 报文)后,用该加密字对密码部分进行加密处理(此种加密算法通常是不可逆的),生成 EAP-Response/MD5 Challenge 报文,并通过设备端传给认证服务器。
RADIUS 服务器将收到的已加密的密码信息(RADIUS Access-Request 报文)和本地经过加密运算后的密码信息进行对比,如果相同,则认为该用户为合法用户,反馈认证通过的消息(RADIUS Access-Accept 报文和 EAP-Success 报文)。
改造后 账号密码+设备信息认证 流程如下如图所示:
在客户端发起802.1X网络认证之前,设备认证服务端需要主动采集终端设备相关信息(MAC地址,CPU序列号,硬盘序列号等),通过SHA256摘要算法,生成设备信息摘要,将设备相关信息保存到设备认证服务端数据库中
通过改造原有802.1X 客户端程序,在改造前流程第5步,发送 MD5 Challenge 的同时,读取出端设备相关信息并生成设备信息摘要,并通过RSA非对称加密算法,加密摘要信息,通过EAP-MD5协议扩展字段 EAP-MD5 Extra Data 传递到freeradius认证服务器
freeradius认证服务器通过配置开启自身rlm_rest模块,将加密后的设备摘要信息转发到设备认证服务模块
设备认证服务模块读取加密后的摘要信息,通过私钥进行解密,然后在认证服务端数据库中进行查询,如果可以匹配到同样的摘要信息,则表示该终端设备信息合法,返回成功状态码给freeradius认证服务器,freeradius认证服务器会结合账号密码认证结果统一判断,如果 账号密码+设备信息 都认证通过,则允许用户访问网络,否则不允许用户访问网络
关键代码 客户端相关代码 func (h *Handle) SendResponseMD5Chall(id uint8, salt, user, pass []byte) error { // MD5 Challenge MD5加密后的密码信息 plain := []byte{id} plain = append(plain, pass...) plain = append(plain, salt[:0x10]...) cipher := md5.Sum(plain) data := append([]byte{uint8(len(cipher))}, cipher[:]...) //初始化密钥对 rsaCryptoKey := GenRasKeyFromPem("./config/client_private.pem", "./config/server_public.pem") //获取设备信息摘要(64位字符串) deviceInfo := GetSHA256DeviceInfo() //加密设备信息摘要 enc, err := rsaCryptoKey.RsaEncrypt([]byte(deviceInfo)) if err != nil { return err } //base64编码 base64ExtarData := base64.StdEncoding.EncodeToString(enc) //将加密后的设备信息摘要加入到EAP请求包内 data = append(data, []byte(base64ExtarData)...) eth := layers.Ethernet{ SrcMAC: h.srcMacAddr, DstMAC: h.dstMacAddr, EthernetType: layers.EthernetTypeEAPOL, } eapol := layers.EAPOL{ Version: 0x01, Type: layers.EAPOLTypeEAP, Length: uint16(5 + len(data)), } eap := layers.EAP{ Code: layers.EAPCodeResponse, Id: id, Type: layers.EAPTypeOTP, TypeData: data, Length: eapol.Length, } if err := h.send(ð, &eapol, &eap, &fillLayer); err != nil { return err } return nil } 设备认证服务模块 //授权操作 // http.StatusNoContent 对应freeradius ok 状态 // http.StatusUnauthorized 对应freeradius reject 状态 func Authorize(c *gin.Context) { var ( err error = nil data []byte = nil auth model.AuthorizeInfo = model.AuthorizeInfo{} ) //读取freeradius rest模块转发的消息 data, err = ioutil.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusUnauthorized, model.ErrResultModel(model.ParamErr, err.Error())) return } log.Info("Authorize data:%v", string(data)) err = json.Unmarshal(data, &auth) if err != nil { c.JSON(http.StatusUnauthorized, model.ErrResultModel(model.ParamErr, err.Error())) return } if len(auth.EAPMessage) <= 46 { c.JSON(http.StatusUnauthorized, model.ErrResultModel(model.ParamErr, "Authorize wrong length")) return } //EAPMessage消息结构:前14位为请求头信息,15-46中间32位是MD5加密后的密码信息,46-末尾是Extra Data 扩展字段的信息 extarData := auth.EAPMessage[46:] log.Info("Authorize extarData:%v", extarData) //解析设备摘要信息 decHexExtarData, err := hex.DecodeString(extarData) if err != nil { c.JSON(http.StatusUnauthorized, model.ErrResultModel(model.ParamErr, err.Error())) return } decBase64ExtarData, err := base64.StdEncoding.DecodeString(decHexExtarData) if err != nil { c.JSON(http.StatusUnauthorized, model.ErrResultModel(model.ParamErr, err.Error())) return } //解密设备摘要信息 deviceInfo, err := config.RsaCryptoKey.RsaDecrypt(decBase64ExtarData) if err != nil { c.JSON(http.StatusUnauthorized, model.ErrResultModel(model.ParamErr, err.Error())) return } //校验设备摘要信息是否在数据库内 _, err = dao.IsExist(deviceInfo) if err != nil { c.JSON(http.StatusUnauthorized, model.ErrResultModel(model.ParamErr, err.Error())) return } //校验设备摘要信息通过则响应 http.StatusNoContent 204 状态 c.JSON(http.StatusNoContent, model.OkResultModel("success")) return } freeradius认证服务模块freeradius认证服务模块主要为相关配置,以实现认证消息转发的功能
编译安装