handle认证(Authentication)流程分析

前言

更新时间 更新内容
2021-01-01 公私钥认证流程分析

前段时间在接手了一个handle相关的项目, 需要对handle系统的安全性进行一些分析. 笔者会在本文中记对handle系统的认证流程进行分析.


正文

handle 系统简介

先简单介绍一下handle系统, handle系统是一个名称 解析系统, 所提供的功能和 DNS 系统类似. 不过DNS的主要功能是把域名解析到ip, 而handle系统的则是将一个字符串(handle)解析到一个互联网资源(Internet resources), 比如一篇论文(DOI解析系统就是基于handle系统的), 一篇博客, 一个网站等等. 关于handle协议更多详细介绍请参考官网[1]文档和rfc3650[2]. (网上也有很多科普handle协议的文章. 请自行查阅, 不再赘述)

下文中可能会出现”handle协议”这个词. “handle协议”和”handle系统”的关系可以理解为: “handle系统”是基于”handle协议”实现并建立起来的一整个系统, 包括GHR, LHS, client等组成部分, 各个组成部分之间使用handle协议进行通信.

handle 系统认证模型简介

handle系统支持很多操作, 比如查询一个handle的值, 增加一个handle, 删除一个handle, 修改一个handle的value等等.

大多数查询操作都是不需要进行认证的: 任何一个客户端给服务端发送一个合法查询请求, 就可以得到查询结果.

但是对于加handle, 修改handle的值等这些操作作, 客户端在发起这些特权操作的时候, 服务端会对客户端的权限进行一次认证, 确保客户端具有执行这些s操作的权限.

接下来本文中会用”非特权操作”指代这些不需要认证的操作, 用”特权操作”指代这些需要进行认证的操作

handle系统支持两种认证方式:

  • 非对称公私钥认证
  • 对称密钥认证

接下来本文中用””公钥”, “私钥’’指代非对称加密中的公私钥, 用”密钥”指代对称加密中的密钥.

在rfc3652[4]中专门有个section(3.5. Client Authentication)来介绍客户端认证流程的.

在一个完整的认证流程中, 客户端和服务端之间可能会交换多组数据, 这些数据中有以下几组是关键的

  • server发给client的challenge
  • client计算得到一个基于challenge和其它信息(如私钥/密钥)的answer, 并回复给server
  • server通过对answer的验证(比如通过公钥/密钥进行验证)来判断认证成功与否.

一次认证过程中可能会用到不止一个TCP流, 所以handle协议的数据包定义中也会有一个session id字段, 用于标明这些数据都是属于同一个session的. sever端会记录某个session id对应的session的当前状态(challenge, 是否认证成功, 公钥/密钥等信息)

大题思路就是challenge-answer这个模型, 接下来我们分别对两种加密方式进行更深入的分析.

分析的过程中会涉及到一些对handle协议数据包格式相关的信息, 这简单说一下, 更详细的信息建议参考rfc3652[4]

handle协议数据包格式简介

根据rfc3652[4]中的定义, handle协议中将节点之间通信的数据包称为Message, Message应该是属于应用层的概念, 支持基于传输层的TCP/UDP协议进行传输.

一个Message由四个部分组成:

  • Envelope
  • Header
  • Body
  • Credential

四者之间的排列顺序见下图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.----------------------.
| | ; Message wrapper for proper message
| Message Envelope | ; delivery. Not protected by the
| | ; digital signature in the Message
| | ; Credential.
|----------------------|
| | ; Common data fields for all handle
| Message Header | ; operations.
| |
|----------------------|
| | ; Specific data fields for each
| Message Body | ; request/response.
| |
|----------------------|
| | ; Contains digital signature or
| Message Credential | ; message authentication code (MAC)
| | ; upon Message Header and Message
'----------------------' ; Body.

Fig 2.2: Message format under the Handle protocol

其中Envelope和Header的大小是固定的, 分别是0x14和0x18, 而且两者也都是必须存在的. 而Body和Credential的大小都是不顾定的, 且两者也都不是必须的.

Envelope的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
.---------------------------------------------------------------.
| MajorVersion | MinorVersion | MessageFlag |
|---------------------------------------------------------------|
| SessionId |
|---------------------------------------------------------------|
| RequestId |
|---------------------------------------------------------------|
| SequenceNumber |
|---------------------------------------------------------------|
| MessageLength |
'---------------------------------------------------------------'

其中sessionID用于标识当前message所属于的session, sessionID由server初始化.

Header的格式定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
.---------------------------------------------------------------.
| OpCode |
|---------------------------------------------------------------|
| ResponseCode |
|---------------------------------------------------------------|
| OpFlag |
|---------------------------------------------------------------|
| SiteInfoSerialNumber | RecursionCount| |
|---------------------------------------------------------------|
| ExpirationTime |
|---------------------------------------------------------------|
| BodyLength |
'---------------------------------------------------------------'

其中OpCode表示正在进行的操作(如OC_ADD_VALUE(104)就表示现在进行的操作是add value, server端会选择对应的body解码的方式).

ResponseCode由server端设置, 用来表示server端返回状态(比如RC_AUTHEN_NEEDED表示需要client进行认证操作, client则会从body中解码出认证所需要的信息(challenge)).

非对称公私钥认证流程分析

handle系统的认证流程是服务端主动发起的. 我们以一个add value操作为例, 来说明整个认证流程.

(1) 用户首先发起一个add value 的 request, 并发送给服务端

  • OpCode设为OC_ADD_VALUE
  • 将所需数据编码并放入body段

(2) 服务端收到请求后判断这是一个特权操作, 需要进行认证, 于是构造 response message.

message的数据段(body)构成如下:

1
request_digest || nonce

其中:

  • request_digest是对用户发送的addvalue value request的hash, client端可以通过这个来确认收到的response和之前发起的request是对应的, 关于这个数据结构的定义参考rfc3652[4]
  • nonce是一个随机数, 长度为16字节(这儿多说几句. rfc3652[4]中规定nonce的长度为20字节, 但是通过扒官网[1]下载的客户端源码发现长度其实是16字节, 这种官方实现和rfc不一致的问题有很多, 我也发邮件问过官方的人, 得到的答复是新版rfc正在修订中, emmm 希望早点出来吧).

同时设置Message的一些字段:

  • OpCode 保持不变, 仍为 OC_ADD_VALUE
  • ResponseCode 设为 RC_AUTHEN_NEEDED
  • 初始化一个session id

然后将数据包返回给client.

(3) client收到数据包之后通过ResponseCode 发现需要认证, 于是开始构造answer.

  1. 解析response message, 得到 request_digest, nonce 和 session id.
  2. 使用私钥对 request_digest.data || nonce 进行digest_sign操作, 得到signature:
    1. 首先计算hash: digest = HASH(request_digest.data || nonce), hash函数由服务端返回的request_digest结构体中定义
    2. 使用私钥对digest进行签名: signature = sign(digest, private_key)
    3. answer 由signature和一些其余数据组成(hash函数类型等)
    4. 对应的公钥通常也是存在handle系统中的, 需要给出公钥对应的handle id 和handle index(如 0.na/10.1038(200)), 这个数据也需要添加到answer中

得到answer之后将其编码到body段, 并构造一个message, 其中:

  • session id 设为第(2)步response中包含的session id
  • opecode 设置为 OC_CHALLENGE_RESPONSE

如果是通过TCP传输的话, 需要新建一个sockfd重新connect server. 如果用之前发送add value request那个sockd发送answert message, 不会得到任何回复.

将这个数据包发送给server. 我们将这个数据包称为challenge response

(4) server端收到challenge respnose后进行如下操作:

  • 从envelope中解码出session id, 通过其找到的nonce和request digest.
  • 通过body段解码出answer 和 公钥地址.
  • 计算并使用公钥验证answer是否正确.

如果验证通过, server就会执行第(1)步的add value操作, 并返回一个 response_code为 RC_SUCCESS 的message.

如果验证失败, server不会执行add value操作, 返回一个response_code 为 RC_AUTHEN_FAILED 的message.

至此大概讲明白了整个认证流程, 其中有很多细节没有涉及到, 具体实现细节笔者已经放到github上了, 可以参考HandleClient[6]中的auth.py.

对称密钥认证流程分析

todo

结语

为了自己实现一遍整个认证过程遇到了太多的坑, 也花了不少时间(大概有半个月吧, 虽然期间有很多时间都在摸鱼2333). 不过认证功能这个大头实现之后实现各种管理功能应该就会简单许多了.

整个分析和实现过程中最大的收获是学到了很多密码学相关的知识(主要是openssl库的使用233), 另外发现python的OpenSSL库提供的功能不是很完善, 文档也很简单.

最后记录一下实现过程中遇到的两个大坑吧:

  • 将answer message发送给server之后没有回显. 通过使用cpp版的client并进行抓包分析发现answer是一个新的tcp链接, 我也不知道要为啥这么设计… 把tcp当udp用可还行233
  • 使用私钥计算digest_sign结果不对. 通过这篇博客[5]才发现需要对hash结果进行padding, 将其补齐到和私钥一样的长度, 学习了.

关于通过密钥认证的部分等用到时再分析吧, 不过大致流程可能差不多.

参考

  1. handle 官网
  2. Handle System Overview – rfc3650
  3. Handle System Namespace and Service Definition – rfc3651
  4. Handle System Protocol (ver 2.1) Specification – rfc3652
  5. Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography Specifications Version 2.1 – rfc3447
  6. HandleClient – github