NLA是一种身份验证方法,它要求用户在创建会话前必须通过RDP会话主机服务器的身份验证。NLA在建立远程桌面连接并出现登录屏幕之前完成用户身份验证。这是比较安全的身份验证方法,有助于保护远程计算机避免恶意用户和恶意软件的攻击。网络级身份验证的好处是[1]:
1. 起初需要较少的远程计算机资源。验证用户身份之前,远程计算机使用有限的资源,而不是像以前版本那样启动完整的远程桌面连接。
2. 可以通过降低受到拒绝服务攻击的风险,帮助提高安全性。
Kerberos协议是由麻省理工研发用来保护Project Athena提供的网络服务器。这个协议以希腊神话中的人物Kerberos(或者Cerberus)命名,它在希腊神话中是地狱的一条凶猛的三头保卫神犬,Kerberos的官方网站是:http://web.mit.edu/Kerberos;Kerberos作为实现NLA的一种可选择协议,已经广泛应用到各种主流操作系统,如Windows, Unix, Linux,Mac OS等,作为一种安全的网络认证协议数年前已经被成功应用到微软的Windows操作系统中。
1 Kerberos原理
认证(Authentication)解决的是“如何证明某个人确确实实就是他或她所声称的那个人”的问题。对于如何进行认证,可以采用这样的方法:如果一个密钥仅仅存在于A和B,那么有个人对B声称自己就是A,B通过让A提供这个密钥来证明这个人就是他或她所声称的A。Kerberos也是基于这个认证原理来实现双方的认证,它使用了两个独立的逻辑部分:认证服务器和票据授权服务器组成的"可信赖的第三方",称为密钥分发中心(KDC)。Kerberos工作在用于证明用户身份的"票据"的基础上。KDC持有一个密钥数据库;每个网络实体-无论是客户还是服务器-共享了一套只有他自己和KDC知道的密钥。密钥的内容用于证明实体的身份。对于两个实体间的通信,KDC产生一个会议密钥,用来加密他们之间的交互信息[2]。Kerberos工作原理如下图1所示。
图1 Kerberos协议工作原理
Kerberos协议的安全主要依赖于参加者对时间的松散同步和短周期的叫做Kerberos票据的认证声明。 下面是对这个协议的一个简化描述,将使用以下缩写:
• AS(Authentication Server)= 认证服务器
• TGS(Ticket Granting Server)= 票据授权服务器。
• SS(SS)= 服务器。
• TGT(TGT)= 票据授权票据。
简单地说,客户向AS认证时用了一个长期共享密钥,并从AS得到一个票据。随后客户可以使用这个票据得到与SS通信必须的附加的票据,而不需要转而使用共享密钥。这些票据可以向SS证明身份。
图2 Kerberos消息基本流程
Kerberos消息的基本流程解释如下:
1. Client向KDC申请TGT(Ticket Granting Ticket):由KRB_AS_REQ与KRB_AS_REP消息实现。
2. Client通过获得TGT向KDC申请用于访问Server的Ticket:由KRB_TGS_REQ与KRB_TGS_REP消息实现。
3. Client最终为了Server对自己的认证向其提交Ticket:由KRB_AP_REQ和KRB_AP_REP消息实现。
2 软件设计与实现
由于该系统基于Kerberos V5实现,但并非完全是Kerberos V5协议的实现,是对该协议的一种扩展实现。该软件总体数据流程图如图3所示,由三部分通信实现:客户端(Client),KDC与服务器(Server)。
图3 基于Windows Server的Kerberos认证实现流程
3.1 Client与KDC消息交互
以Kerberos V5协议来看,Client与KDC主要实现KRB_AS_REQ、KRB_AS_REP、KRB_TGS_REQ与KRB_TGS_REP四条消息,由于该系统是对Kerberos V5协议的扩展,所以其实现与标准会有所区别。根据图3,笔者来分析Client与KDC消息交互的设计与实现。
1.KRB_AS_REQ:消息发送的主要内容有消息类型、用户的基本信息和时间戳等,数据结构定义如下(参考RFC4120 5.4.1):
typedef struct KDC_REQ_t {
int pvno; //协议的版本号V5,即该值等于5
int msg_type; //消息类型为KRB_AS_REQ
struct PA_DATA_t** padata; //预认证数据,根据消息类型不同传输不同的内容
KDC_REQ_BODY *req_body; //扩展的消息结构
} __PACKED__ KDC_REQ;
其中struct PA_DATA_t结构定义如下:
typedef struct PA_DATA_t {
int32_t type; //预认证数据类型
ubyte *value; //预认证数据内容
} __PACKED__ PA_DATA;
其中KDC_REQ_BODY类型定义如下:
typedef struct KDC_REQ_BODY_t {
ubyte4 kdc_options; //设定控制KDC的一些flag标记
PrincipalName cname; //客户端的用户名
char realm[KRB_MAX_NAME_LEN]; //要登陆的域名
PrincipalName sname; //要访问的Server名
char* from; //期望获得票据的开始时间,如果未设定,默认为当前时间
char* till; //期望票据的过期时间
char* rtime; //期望更新获得票据的过期时间
ubyte4 nonce; //随机数用于验证消息
int* etype; //可支持的加密类型,比如该系统支持RC4加解密算法
HostAddress addresses; //客户主机的主机名信息(可选)
EncryptedData enc_authorization_data;
Ticket adt; //附加的票据,根据kdc_options 中的ENC-TKT-IN-SKEY标记确定是否存在
} __PACKED__ KDC_REQ_BODY;
2.KRB_ERROR: KRB5KDC_ERR_PREAUTH_REQUIRED (25),KDC返回该消息表明需要附加的认证信息(PA-ENC-TIMESTAMP类型)。关于KRB_ERROR类型的消息,该系统中其数据结构定义(参考RFC4120 5.9.1)如下:
typedef struct krb_error_t {
int pvno; //同上
int msg_type; //同上
ubyte ctime[KRB_STAMP_LEN]; //当前的时间ASN.1中的GeneralTime格式
ubyte2 cusec; //当前时间的微秒数
ubyte stime[KRB_STAMP_LEN]; //Server端的当前时间
unsigned int susec; //Server端当前时间的微秒数
int code; //错误代码号,参考RFC4120 7.5.9
char crealm[KRB_MAX_NAME_LEN];
PrincipalName cname;
char realm[KRB_MAX_NAME_LEN];
PrincipalName sname;
char *e_text; //附加的消息帮助解释错误原因,可选的
e_data edata; //帮助恢复或处理错误的数据
} krb_error_msg;
3.KRB_AS_REQ:消息格式与1相同,主要区别是该消息中增加了PA-ENC-TIMESTAMP (2)消息,该消息的数据结构定义(参考RFC4120 5.2.7.2)如下:
typedef struct PA_ENC_TS_ENC_t {
ubyte patimestamp[15]; //客户端当前的时间,格式为:32303130303630343037343831365a //代表2010-06-04 07:48:16 (UTC)
int pausec; //客户端当前时间的微秒数
} __PACKED__ PA_ENC_TS_ENC;
4.KRB_AS_REP:该消息为KDC正常返回给Client的消息,消息包含票务Ticket消息和加密的信息。该消息的数据结构定义(参考RFC4120 5.4.2)如下:
typedef struct KDC_REP_t {
int pvno;
int msg_type;
struct PA_DATA_t* padata;
char crealm[KRB_MAX_NAME_LEN];
PrincipalName cname;
Ticket ticket; //需要转发的Ticket消息
EncryptedData enc_part; //加密的信息,数据格式为EncKDCRepPart
} __PACKED__ KDC_REP;
关于Ticket的定义参考RFC4120 5.3,用于传递交互的票据信息。关于EncKDCRepPart的定义参考RFC4120 5.4.2,该信息包含双方协商使用的Session Key和一些认证信息。
5.KRB_TGS_REQ: 该消息用来申请用于访问Server的Ticket,消息格式与KRB_AS_REQ相同。
6.KRB_TGS_REP:该消息返回响应KRB_TGS_REQ请求信息,消息格式与KRB_AS_REP相同。
9.KRB_TGS_REQ: 该消息用来申请用于访问Server的Ticket,消息格式与KRB_AS_REQ相同。
10.KRB_TGS_REP:该消息返回响应KRB_TGS_REQ请求信息,消息格式与KRB_AS_REP相同。
3.2 Client与Server消息交互
Client与Server之间的交互主要完成两件事情:一、协商Token用于后续的交互认证,由消息7(NegTokenInit)与8(NegTokenResp)消息实现;二、Client与Server的互相认证,由11(KRB_AP_REQ),12(KRB_AP_REP)和13(NegTokenResp)实现。
7.NegTokenInit:用于发送请求协商Token的OID列表,每一个OID代表一种可支持的GSS-API(Generic Security Service Application Program Interface)机制。NegTokenInit结构定义参考RFC4178 4.2.1,定义内容如下:
typedef struct NegTokenInit_t {
ubyte *mechTypes //OID列表
ubyte4 reqFlags //ContextFlags OPTIONAL, 该系统未使用
ubyte *mechToken //协商的Token内容
ubyte * mechListMIC //如果协商不支持完整性保护,该项就不使用
} __PACKED__ NegTokenInit;
8.NegTokenResp:响应NegTokenInit的请求,返回所支持的OID,以及相应的Token内容,该结构的定义参考RFC4178 4.2.1 4.2.2,定义如下:
typedef struct NegTokenResp _t {
enum negState {COMPLETED, INCOMPLETE, REJECT , REQUEST-MIC }; //协商的状态
ubyte * supportedMech; //支持的机制OID
ubyte *responseToken; //协商的Token内容
ubyte * mechListMIC; // MIC token计算根据RFC4178 第5小节。
} __PACKED__ NegTokenResp;
11.KRB_AP_REQ:该消息主要用于完成Client对Server的认证,即让Server明白该Client是合法的,是可信的。该消息的定义参考RFC4120 5.5.1节,定义如下:
struct AP_REQ_t {
int pvno;
int msg_type;
ubyte ap_options[5]; //请求的标记
Ticket ticket; //转发的Ticket
Authenticator auth; //加密的认证信息,包含客户可选的subkey,定义参考RFC4120 5.5.1
} __PACKED__ AP_REQ;
12.KRB_AP_REP:这个消息是根据KRB_AP_REQ消息中的ap_options(mutual-required标记)选项来产生的,用于证明自己就是Client要访问的Server。主要返回一个双方协商的subkey为后续的具体应用程序加解密使用。
13.NegTokenResp:与消息8格式相同,不同的是这里的negState的状态为COMPLETED,表示协商已完成。并且包含已计算好的mechListMIC值。
至此,关于Kerberos在RDP协议中的认证过程已经完成,为后续安全传递数据铺平了道路。
结 语
随着网络的飞速发展,网络安全认证日趋重要,Kerberos作为一种强大的网络认证协议已被广泛应用。本文讨论的是Kerberos基于Windows系统RDP协议中的一个典型网络认证应用,通过本文读者可以学习Kerberos协议的工作原理,以及它在实际工作中的具体应用实现。