0. 隐藏服务:概述和准备工作
隐藏服务旨在为Tor网络上的基于双向通信流中的响应方提供匿名性。与常规的Tor连接不同,在常规的Tor连接中,保证了请求方的匿名性,却没有保证方的匿名性,而隐藏服务则尝试提供双向匿名。
参与者:
- 隐藏服务提供方
- Host,“Server”:运行隐藏服务的Tor实例
- 用户:同隐藏服务进行通信的客户
- 隐藏目录服务(HSDir):一种Tor节点,该节点承载了来自隐藏服务主机的签名内容,以便用户可以同隐藏服务联系
- 介绍结点(Introduction Point):一种Tor节点,接受连接隐藏服务的请求,并匿名地将这些请求转发给隐藏服务。
- 会和结点(Rendezvous Point):客户端和服务器连接到的Tor节点,并在它们之间中继流量。
0.1 对以前版本的改进
- 更好的加密方式(用
SHA3/ed25519/curve25519
代替SHA1/DH/RSA1024
) - 改进后的目录协议,降低了泄漏信息给目录服务的可能性,并且能更好的抵御相关攻击
- 洋葱地址具有更好的安全性,防止被冒充
- 介绍/会合协议的扩展性更好
- 提供了洋葱服务的离线密钥
- 提供了更高级的客户授权功能
0.2 符号和名词规范
除非另有说明,否则所有的八位字节整数都是大端序(big-endian,指的是低地址端存放高位字节)
我们用两种方式编写字节序列:
- 如
[AB AD 1D EA]
中所示,在方括号中的两位十六进制值序列。 - 用引号括起来的一串字符,如“Hello”。这些字符串中的字符以ascii形式进行编码;除非明确描述为以NULL结尾,否则字符串不会以nul结尾。
我们使用竖线“|”来表示连接。
我们使用INT_N(val)
表示无符号整数“val”的网络(大端)编码,以N个字节为单位。 例如,INT_4(1337)
为[00 00 05 39]
。 值将被截断,如下所示:val%(2 ^(N * 8))
。 例如,INT_4(42)
是42%4294967296(32位)
。
0.3 加密构造块
- 一种由强熵源支持的伪随机数生成器。PRNG的输出在被发布到网络之前应始终进行哈希处理,以避免将原始PRNG字节泄漏到网络中(参见[PRNG-REFS])。
- 流密码
STREAM(iv,k)
,其中iv是长度为S_IV_LEN
字节的随机数,而k是长度为S_KEY_LEN
字节的密钥。 - 公钥签名系统
SIGN_KEYGEN()-> seckey,pubkey;
SIGN_SIG(seckey,msg)-> sig;
和SIGN_CHECK(pubkey,sig,msg)-> {“ OK”,“ BAD”};
其中私钥的长度为SIGN_SECKEY_LEN
个字节,公钥的长度为SIGN_PUBKEY_LEN
个字节,而签名的长度为SIGN_SIG_LEN
字节。
该签名系统还必须支持如附录[KEYBLIND]和[SUBCRED]部分所讨论的密钥盲操作:SIGN_BLIND_SECKEY(seckey,blind)-> seckey2
和SIGN_BLIND_PUBKEY(pubkey,blind)-> pubkey2
。 - 一种公钥协商系统“PK”,提供
PK_KEYGEN()->seckey,pubkey; PK_VALID(pubkey)->{“OK”,“BAD”}; PK_HANDSHAKE(seckey,pubkey)->output;
其中,密钥长度为PK_SECKEY_LEN
字节,公钥长度为PK_PUBKEY_LEN
字节,握手产生输出的长度为PK_OUTPUT_LEN
字节。 - 加密散列函数
H(d)
,应具有原像和抗冲突功能。 它产生长度为HASH_LEN字节的哈希值。 - 密码消息认证码
MAC(key,msg)
产生长度为MAC_LEN
字节的输出。 KDF(message,n)
:一个输出为n个字节的密钥派生函数(KDF)。
首先,我建议:
- 使用AES256-CTR实例化STREAM。
- 用Ed25519和[KEYBLIND]中的blinding协议(盲密钥协议)实例化SIGN。
- 使用Curve25519实例化PK。
- 用SHA3-256实例化H(哈希函数)。
- 使用SHAKE-256实例化KDF。
- 用H(k_len | k | m)实例化MAC(key = k,message = m),其中k_len是htonll(len(k))。
出于遗留目的,我们指定了与旧版本的Tor引入协议和会合协议的兼容性。如rend-spec.txt中所述,它们使用了RSA1024,DH1024,AES128和SHA1。
与提案[proposal 220]一样,所有签名都不是在字符串本身上生成的,而是在那些以区别值为前缀的字符串上生成的。
0.4 协议构建块 [BUILDING-BLOCKS]
在下面的部分中,我们需要传输Tor节点的位置和身份。 我们以Tor协议中EXTEND2单元所使用的链路标识格式进行操作。
NSPEC(链接说明符的数目) [1 byte]
链接说明符类型如tor-spec.txt中所述。 每组链路说明符必须至少包括类型为[00](TLS-over-TCP,IPv4),[02](旧节点标识)和[03](ed25519标识密钥)的说明符。
从0.4.1.1-alpha开始,Tor在v3洋葱服务协议链接说明符列表中同时包含IPv4和IPv6链接说明符。 无论Tor实际用于连接/扩展到远程中继节点的地址是什么,所有可用地址都应作为链接说明符包括在内。
我们还合并了Tor的链路扩展握手协议,如tor-spec.txt中所述的CREATE2和CREATED2单元中使用的。 在这些握手中,知道服务器公钥的客户端发送消息并从该服务器接收消息。 一旦交换完成,双方就拥有了一组共享的前向安全密钥,并且客户端知道除非他们(攻击者)拥有并控制了与服务器的公钥相对应的私钥,否则没有人可以共享该前向安全密钥。
前向安全密钥:“perfect forward secrecy”则是由Christoph G. Günther在EUROCRYPT ’89提出的,其最初用于定义会话密钥交换协议的一种安全性。
(Perfect)Forward secrecy的大致意思是:用来产生会话密钥(session key)的长期密钥(long-term key)泄露出去,不会造成之前通讯时使用的会话密钥(session key)的泄露,也就不会暴漏以前的通讯内容。简单的说,当你丢了这个long-term key之后,你以后的行为的安全性无法保证,但是你之前的行为是保证安全的。
0.5 协议中指定的relay-cell类型
- 32 — RELAY_COMMAND_ESTABLISH_INTRO
- 从隐藏服务主机发送到介绍点,用于建立介绍点,详见讨论[REG_INTRO_POINT]。
- 33 — RELAY_COMMAND_ESTABLISH_RENDEZVOUS
- 从客户端发送至会合结点,用于创建会合结点,详见讨论[EST_REND_POINT]
- 34 — RELAY_COMMAND_INTRODUCE1
- 从客户端发送至介绍点,用于将请求信息(会合结点等)发送给介绍结点,详见[SEND_INTRO1]
- 35 — RELAY_COMMAND_INTRODUCE2
- 从介绍结点发送至隐藏服务主机,格式与INTRODUCE1相同,用于将请求信息转发给隐藏服务,详见[FMT_INTRO1]和[PROCESS_INTRO2]
- 36 — RELAY_COMMAND_RENDEZVOUS1
- 从隐藏服务主机发送至会合结点,尝试将隐藏服务的链路连接到客户端的链路,详见[JOIN_REND]
- 37 — RELAY_COMMAND_RENDEZVOUS2
- 从会合点发送到客户端,报告隐藏服务链路到客户端链路的连接,详见[JOIN_REND]
- 38 — RELAY_COMMAND_INTRO_ESTABLISHED
- 从介绍结点发送至隐藏服务主机,报告尝试建立介绍结点的状态,详见[INTRO_ESTABLISHED]
- 39 — RELAY_COMMAND_RENDEZVOUS_ESTABLISHED
- 从会合点发送至客户端,确认接收到ESTABLISH_RENDEZVOUS单元,详见[EST_REND_POINT]
- 40 — RELAY_COMMAND_INTRODUCE_ACK
- 从介绍结点发送至客户端,确认接收到INTRODUCE1单元,并回复请求信息发送成功/失败,详见[INTRO_ACK]
1. 协议概述
1.1 总览
就是隐藏服务的基本原理,此处略过
1.2 隐藏服务命名方式 [NAMING]
隐藏服务的名称是其长期主身份密钥。 通过使用Base 32中对整个密钥(包括版本字节和校验和)进行编码,然后在末尾附加字符串“ .onion”,可以将其编码为主机名,其结果是一个56个字符的域名。
(这是对旧版隐藏服务协议的更改,v2协议中我们将1024位的RSA密钥使用SHA1哈希(160bit),取其前半(80bit)部分并使用Base32编码后作为洋葱地址)
1.3 访问控制 [IMD:AC]
通过上述过程,在多个点对隐藏服务实施访问控制。此外,还可以选择使用隐藏服务与其客户端之间带外交换的预共享密钥来实施额外的客户端授权访问控制。
访问控制的第一阶段发生在下载HS描述符时。具体地说,为了下载描述符,客户端必须知道服务端使用了哪个盲签名密钥对其进行的签名。(请参见下一节了解有关key blinding的更多信息)
要获取介绍结点,客户端必须解密隐藏服务描述符的主体。为此,客户机必须知道隐藏服务的_unblinded_
公钥,这使得没有该信息的实体(例如不知道洋葱地址的HSDirs)无法使用该描述符。
此外,如果启用了可选项:客户端授权,则使用每个授权客户端的x25519密钥对隐藏服务描述符的第三层(详见v3描述符的层次结构)进行superencrypted,以进一步确保未经授权的实体无法对其进行解密。
为了使介绍结点向隐藏服务发送集合请求,客户机需要使用在隐藏服务描述符中找到的每个介绍结点的身份验证密钥。
最后一级的访问控制发生在HS服务器本身,根据请求的内容,HS服务器可能决定响应或不响应客户端的请求。协议在这一点上是可扩展的:至少,服务器要求客户端证明其对隐藏服务描述符的加密部分的内容有所了解。 如果启用了客户端授权选项,则该服务端可能还会要求客户端证明其是否了解预共享私钥。
1.4 分发隐藏服务描述符 [IMD:DIST]
隐式服务描述符会定期存储在不同位置,以防止单个或一小部分的隐藏目录服务成为DoS的攻击目标并用以阻断隐藏服务的正常连接。
对于每个时间段,Tor权威目录服务都会共同生成一个随机值。
(有关如何将此值合并到投票实践中的描述,请参见第2.3节;在其他提案(包括[SHAREDRANDOM-REFS])中描述了生成该值的方法。)
该值与隐藏目录服务的公共标识密钥相结合,确定了每个HSDir在哈希环中的位置,以查找在该期间生成的描述符。
每个隐藏服务描述符在哈希环中的位置都是基于其签名密钥决定的。 请注意,隐藏服务描述符并未直接使用其公钥进行签名。 相反,Tor使用了一套盲密钥系统[KEYBLIND]为每个隐藏服务每日创建一个新的密钥。 任何知道隐藏服务证书的客户端都可以在给定时间段内导出这些盲签名密钥。 在缺少该证书的情况下,导出盲签名密钥是不可能的。
每个描述符的主体也使用从证书派生的密钥进行加密。
为了避免出现惊群效应,即每个隐藏服务在每个周期开始时都会生成并上传新的描述符,每个描述符在其盲签名密钥的有效时间内都保持在线。 在新密钥上线之前,上一个周期的密钥仍然有效。
1.5 扩展到多个主机
该设计与我们当前扩展隐藏服务的方法兼容。 具体来说,隐藏服务提供方可以使用onionbalance在HSDir层上的多个节点之间实现高可用性。 此外,提供方可以使用提议255在介绍层(introduction layer)上对其隐藏服务实现负载均衡。 有关该主题和替代设计的更多讨论,请参见[SCALING-REFS]。
1.6 向后兼容旧版隐藏服务协议
此设计与旧版本隐藏服务协议的客户端,服务器和hsdir节点协议不兼容,如rend-spec.txt中所述。 另一方面,它旨在允许使用较旧的Tor节点作为会合结点和介绍结点。
1.7 保持加密密钥离线
在这种设计中,隐藏服务的身份私钥可以离线存储。 它仅用于生成盲签名密钥,该签名密钥用于对描述符的签名密钥进行签名。
为了运作隐藏服务,隐藏服务提供方可以预先生成多个盲签名密钥和描述符签名密钥(及其证书;请参见下面的[DESC-OUTER]和[HS-DESC-ENC]),及其相应的描述符加密密钥,然后将其导出到隐藏服务主机。
因此,在隐藏服务遭到破坏的情况下,对手只能在有限的时间内模拟它(这取决于提前生成了多少个签名密钥)。
重要的是不要将盲签名密钥的私有部分发送到隐藏服务,因为攻击者可以从中获取主身份私钥。盲签名的私钥部分应仅用于为描述符签名密钥创建证书。
(注意:尽管协议允许它们,但从0.3.2.1-alpha开始就没有实现脱机密钥。)
1.8 加密密钥和抗重放攻击
为了避免介绍结点重放介绍请求,隐藏服务主机决不能接受同一请求两次。隐藏服务设计的早期版本在这里使用了经过身份验证的时间戳,但是包含当前时间的视图可能会创建有问题的指纹信息。(?)(更多讨论见提案222。)
1.9 密钥汇总
[在下面的文本中,“加密密钥对”大致是“可以用来执行Diffie-Hellman的密钥对”,而“签名密钥对”则大致是“可以用来进行ECDSA的密钥对”。]
在本文档中公/私钥对的定义:
- 隐藏服务的主身份密钥(Master identity key):
一种主签名密钥对,用作隐藏服务的标识。该密钥是长期的,不能单独用于签名任何内容;它只用于生成盲签名密钥,如[keybind]和[SUBCRED]中所述。公钥被编码在“.onion”地址中。
- 盲签名密钥(Blinding signing key):
从主身份密钥派生的密钥对,用于对描述符签名密钥进行签名。每个隐藏服务的盲签名密钥都会定期更改。若客户端获得由隐藏服务的主身份公钥和可选秘密消息组成的“证书”,其可以根据该证书导出隐藏服务的盲签名公钥。 该盲签名公钥用作类DHT结构目录系统中的索引(请参见[SUBCRED])。
- 描述符签名密钥(Descriptor signing key):
用于对隐藏服务描述符进行签名的密钥。该密钥由盲签名密钥进行签名。与盲签名密钥和主身份密钥不同,此密钥的私钥部分必须由隐藏服务主机在线存储。该密钥的公钥部分(以证书的形式)则包含在HS描述符的未加密部分中(请参阅[DESC-OUTER])。
- 介绍点身份验证密钥(Introduction point authentication key):
一种短期签名密钥对,用于标识给定介绍结点的隐藏服务。 为每个介绍结点创建一个新的密钥对; 这些密钥对用于签署隐藏服务主机在引入介绍结点时发出的请求,以便知道此密钥公共部分的客户端可以将其引入请求发送到正确的隐藏服务。 任何密钥对都不能与一个以上的介绍结点一起使用。 (以前在rend-spec.txt中称为“服务密钥(service key)”)
- 介绍点加密密钥(Introduction point encryption key):
同介绍结点建立连接时使用的短期加密密钥对,扮演类似于Tor节点洋葱密钥的角色。针对每个介绍点都会创建一个新的密钥对。
本文档中定义的对称密钥:
- 描述符加密密钥:用于加密隐藏服务描述符主体的对称加密密钥。依据当前的周期时间和隐藏服务的证书派生。
其他地方定义的公/私钥对:
- 洋葱密钥(Onion key):短期加密密钥对
- Tor节点的身份密钥
在其他地方定义的类似于对称密钥的东西:
- 链路握手协议中的KH:一种不可预测的值,其作为Tor链路扩展握手协议的一部分,用于将请求绑定到特定链路上。
1.9.1 客户端授权密钥 [CLIENT_AUTH]
当启用客户端授权后,隐藏服务的每个授权客户端都有另外两个与该隐藏服务共享的非对称密钥对。 没有这些密钥的客户端将无法访问该隐藏服务。 在整个文档中,我们假设这些预共享密钥以安全的带外方式(我的理解是使用Tor链路之外的传输通道)在隐藏服务及其客户端之间交换。
具体来说,每个授权客户端都拥有:
- 一个x25519密钥对,用于计算解密密钥,客户端使用该密钥解密隐藏服务描述符。 参见[HS-DESC-ENC]。
- 一个ed25519密钥对,它允许客户端计算签名,以向隐藏服务提供方证明该客户端已被授权。 这些签名被插入到INTRODUCE1单元中,如果没有它们,就无法完成向隐藏服务介绍客户端信息的操作。 参见[INTRO-AUTH]
交换这些密钥的正确方法是在客户端生成密钥,并将相应的公钥通过带外方式发送给隐藏服务提供方。 另一种更简单但不太安全的方法是在隐藏服务提供方生成密钥对并将相应的私钥传递给其客户端。 有关如何管理这些密钥的更多详细信息,请参见[CLIENT-AUTH MGMT]。
2. 生成并发布隐藏服务描述符 [HSDIR]
隐藏服务描述符遵循与其他Tor目录对象相同的格式。它们被匿名地发布到携带HSDir标志、HSDir=2协议版本且tor版本>=0.3.0.8(因为在这个版本中修复了一个bug)的Tor服务器上。
2.1 推导盲密钥和子证书 [SUBCRED]
在不同的时间段(有关时间段的定义,请参见[TIME-PERIODS]),隐藏服务主机会使用不同的盲私钥来签名其目录信息,而客户端使用不同的盲公钥作为获取该信息的索引 。
有关密钥派生方法的候选项,请参见附录[KEYBLIND]。
此外,客户端和隐藏服务主机在每个时间周期内都会派生一个子证书。解密每个时间周期的隐藏服务描述符以及在引入过程中向隐藏服务主机进行身份验证都需要用到子证书。与证书不同的是,它在每个时间周期都会更改。 知道了子证书,即使与盲私钥结合使用,也无法使隐藏服务主机派生出主证书——因此,可以安全地将子凭证放在隐藏服务主机上,同时使隐藏服务的私钥保持脱机状态。
某一时间周期的子证书生成方式如下:
subcredential=H("subcredential"\ |\ credential\ |\ blinded\_public\_key)
在上述公式中,credential对应于:
credential=H("credential"\ |\ public\_identity\_key)
其中public_identity_key是隐藏服务的主身份公钥。
2.2 查找、上传和下载隐藏服务描述符
为了避免针对隐藏服务描述符的审查攻击,我们会随着时间的推移将它们存储在不同的目录中,并使用共享随机值来防止这些目录被提前预测。
tor服务器托管隐藏服务的条件取决于以下几条:
- 当前时间段
- 每日的子证书
- 隐藏目录服务的公钥
- 在每个时间周期都会变化的共享随机值(SRV)
-
一组网络范围的网络状态共识参数。(共识参数是权威目录服务投票并发布在共识文件中的整数值,如dir-spec.txt第3.3节中所述。)
2.2.1 将时间划分成多个时段 [TIME-PERIOD]
为了防止一组隐藏目录服务被希望对隐藏服务进行审查的攻击者利用,隐藏服务描述符上传的位置随着时间而变化。
“时间段”的长度由共识参数“hsdir-interval”控制,介于30分钟到14400(10天)分钟之间。 默认时间段长度为1440分钟(24小时)。
时间段从Unix epoch开始(1970年1月1日),其计算方法是将自Unix epoch以来的分钟数除以该时间段。 但是,我们希望时间段从每天的12:00UTC开始,因此我们从纪元开始的分钟数中减去(12*60)分钟的“旋转时间偏移量(rotate time offset)”,然后除以时间段(有效地使“我们的”纪元从1970年1月1日的12:00UTC开始)
举个例子:如果当前时间为2016-04-13 11:15:01 UTC,则表示自纪元以来过了1460546101秒(24342435分钟)。然后我们从该分钟数中减去12*60分钟的“旋转时间偏移量”,得到24341715。如果当前的时间段长度是1440分钟,那么我们可以看到当前的时间段编号(period number)是16903。
具体地说,时间段16903开始于纪元后16903*1440*60+(12*60*60)秒,即2016-04-12 12:00 UTC;结束于纪元后16904*1440*60+(12*60*60)秒,即2016-04-13 12:00 UTC。
2.2.2 何时上传隐藏服务描述符 [WHEN-HSDESC]
隐藏服务会定期将其描述符上传到负责维护该描述符的HSDir中。该HSDirs集合是根据[WHERE-HSDESC]中的规定确定的。
具体来说,每当隐藏服务发布其描述符时,它还会设置一个定时器,该定时器被设置为未来60~120分钟之间的一个随机时间。当定时器触发时,隐藏服务需要为当前时间段再次发布其隐藏服务描述符。[TODO:使用共识参数控制重新发布时间段?]
2.2.2.1 重叠描述符
隐藏服务需要上传多个描述符,以便具有比其更旧或更新的共识文件的客户端可以访问它们。服务需要在每个即将到来的时间段开始之前将其描述符上传到HSDirs,以便客户端可以随时获取它们。 此外,即使在上一个时间段已经过去,服务也应继续上传其旧的描述符,这样可以确保只有上一个时间段共识文件的客户端也可以访问它们。
因此,隐藏服务在每个点都维护着两个有效的描述符信息。另一方面,客户端部分没有重叠描述符的概念,而是总是下载当前时间段的描述符信息以及SRV。服务的职责是确保描述符对所有客户端都可用。请参阅[FETCHUPLOADDESC]一节了解如何实现这一点。
2.2.3 在何处上传隐藏服务描述符 [WHERE-HSDEC]
本节指定在任何给定时间下如何形成HSDir哈希环。每当需要一个时间值(例如,为了获得当前的时间段编号),我们假设客户端和HS服务端使用他们最新且有效的共识文件中的valid-after时间。
以下共识参数控制着隐藏服务描述符的存储位置:
- hsdir_n_replicas:范围为[1,16]的整数,默认为2
- hsdir_spread_fetch:范围为[1,128]的整数,默认为3
- hsdir_spread_store:范围为[1,128]的整数,默认为4
为了确定给定隐藏服务描述符在给定时间段中的存储位置,在得出该时间段的盲公钥之后,上传和下载的计算方式如下:
\begin{aligned} for\ &replicanum\ in\ 1...hsdir\_n\_replicas:\\ &hs\_index(replicanum) = H(“store\textrm{--}at\textrm{--}idx" \ |\\ &{\phantom{hs\_index(replicanum) = H(}}blinded\_public\_key \ |\\ &{\phantom{hs\_index(replicanum) = H(
}}int\_8(replicanum) \ |\\ &{\phantom{hs\_index(replicanum) = H(
}}int\_8(period\_length) \ |\\ &{\phantom{hs\_index(replicanum) = H(
}}int\_8(period\_num) \ ) \end{aligned}
其中在[KEYBLIND]节中指定了blinded_public_key,period_length是时间段的长度(以分钟为单位),并且使用[TIME-PERIODS]节中指定的当前共识文件中的“valid-after”来计算period_num。
然后,对于当前共识文件中列出的带有HSDir标志位的每个Tor节点,该节点在HSDir哈希环中的目录索引的计算方式如下:
\begin{aligned} hsdir\_index(node)=H(&“node\textrm{--}idx"\ |\ node\_identity\ |\\ &shared\_random\_value\ |\\ &int\_8(period\_num)\ |\\ &int\_8(period\_length)\ ) \end{aligned}
其中shared_random_value是权威目录服务在[PUB-SHAREDRANDOM]部分中生成的共享值,而node_identity是该Tor节点的ed25519身份密钥。
最后,对于replicanum in 1 ... hsdir_n_replicas
,隐藏服务主机将描述符上传到前hsdir_spread_store(参数默认为4)个节点,该节点的目录索引刚好排在hs_index(replicanum)之后。 如果该隐藏服务较低编号的replicanum已经选择了某节点作为服务描述符存储的载体,则在后续replicanum选择hsdir_spread_store个节点时,先前已选择的节点都将被忽略(即跳过)。
我的理解是比如replicanum=0的描述符已经选择上传至hsdir0,那么replicanum=1的描述符在上传时就会自动忽略这个节点,以防止两个描述符副本上传到同一个HSDir中,降低隐藏服务被攻击的风险。
当选择从HSDir中下载描述符时,客户端会从索引之后的前hsdir_spread_fetch(参数默认为3)个节点中随机选择。(请注意,为了使系统更好地兼容HSDirs下线的情况,hsdir_spread_fetch可能小于hsdir_spread_store。)同样,此处的选择节点操作也将忽略replicanum较低的描述符已经选中的节点。
2.2.4 使用时间段和SRV获取/上传HS描述符 [FETCHUPLOADDESC]
隐藏服务和客户端需要使用正确的时间段(time periods,TP)和共享随机值(shared random value,SRV)才能成功获取和上传描述符。 此外,为了避免时钟偏移带来的问题,客户端和隐藏服务都使用实时共识文件中的“valid-after”时间作为决定上传和获取描述符的方式。 通过使用共识文件中的统一时间,我们最大程度地减少了由于系统时钟造成的客户端和隐藏服务不同步的问题。 本节中作出基于时间的决策,都假设它们使用的是共识文件中的时间而不是本地系统时间。
如[PUB-SHAREDRANDOM]所述,共识文件中包含两个共享随机值(当前时间段的SRV和前一个时间段的SRV)。 隐藏服务和客户端在获取/上传隐藏服务描述符时需要使用正确的SRV,这就要求共识文件中的SRV需要与描述符中的TP相匹配。 本节将准确说明这是如何工作的。
让我们从下面这个例子开始:

2.2.4.1 客户端获取描述符 [CLIENTFETCH]
下面是客户端如何使用TP和SRV来获取描述符的方法:
客户端将其TP与SRV同步,因此,他们始终希望将TP#N与SRV#N一起使用:为了实现此时间段,客户端在获取描述符时始终使用当前时间段。 现在,对于SRV,如果客户端当前时间位于当前TP和下一个SRV之间(即上图中用单虚线绘制的时间段),则使用当前的SRV;否则,如果客户端当前时间位于新的SRV和下一个新TP之间(即用上图中用双虚线绘制的时间段),它使用上一个周期的SRV。如下图所示:

如果客户端当前时间在TP#1之后的13:00也就是t1处,则它将使用TP#1和SRV#1来获取描述符。 同样,如果客户端当前时间在SRV#2之后的04:00,则仍将使用TP#1和SRV#1来获得描述符,只有当客户端时间过了TP#2之后,才会使用TP#2和SRV#2
这样就将TP#N和SRV#N关联起来一起使用了,总结就是一切依据TP走,换了TP,SRV才能跟着换
2.2.4.2 隐藏服务上传描述符 [SERVICEUPLOAD]
如上所述,服务在任何时候都维护着两个有效的描述符。 我们称它们为“第一”和“第二”服务描述符。 当隐藏服务收到共识文件中的“valid-after”时间已经超过下一个SRV周期的计算时间时,它会旋转其隐藏服务描述符。他们通过丢弃第一个描述符,将第二个描述符推送到第一个描述符的位置,并使用最新的数据重建第二个描述符的方式来实现描述符的旋转。
隐藏服务也如同客户端一样,会根据处于不同的时间区域采用不同的逻辑来选择SRV和TP值:
2.2.4.2.1 第一描述符的上传逻辑 [FIRSTDESCUPLOAD]
当隐藏服务当前处于当前TP和下一个新SRV之间(即用单虚线绘制的时间段)时,它会使用前一个时间段和前一个周期的SRV来上传其第一描述符:这旨在为共识文件仍处于上一个TP的的客户端提供服务。
示例:见上一节图,若隐藏服务的时间为TP#1之后的13:00,则它将使用TP#0和SRV#0上传其第一个描述符。 因此,如果客户端共识文件中的valid-after为11:00,则可以根据上述客户端逻辑访问它。
现在,如果隐藏服务处于新的SRV和下一个新的TP之间的时间段(即用双虚线绘制的时间段)中,则它将当前TP和上一个周期的SRV用作其第一描述符:这意味着可以为与隐藏服务处于同一TP的客户端提供描述符信息。
比如当前隐藏服务的时间处于2.2.4.1节中的t2处,则该隐藏服务会使用TP#1和SRV#1来上传其描述符。
其实这一节可以这么理解:因为第一描述符永远都是为还处于上一个TP/SRV周期的客户端服务的,所以当前隐藏服务使用的TP和SRV主要取决于上一个周期的客户端会如何使用它们。
2.2.4.2.2 第二描述符上传逻辑 [SECONDDESCUPLOAD]
当隐藏服务处于当前TP与下一个新SRV之间时(即用单虚线绘制的时间段),它会使用当前TP和当前SRV来上传其第二描述符:这旨在为共识文件的TP与隐藏服务相同的客户端提供服务。
举例:在上图中,该隐藏服务位于TP#1之后的13:00,则它将使用TP#1和SRV#1上传其第二描述符。
现在,如果隐藏服务处于新的SRV和下一个TP之间的时间段(即用双虚线绘制的时间段)中,则它使用下一个TP和当前的SRV上传第二个描述符:这意味着可以为拥有比隐藏服务的TP更新的客户端提供描述符信息
要预先生成为下一个时间段客户端提供服务的描述符。
例如当前时间处于SRV#2之后的4:00,因为当时间过了TP#2:12:00后,就有一批客户端会使用TP#2和SRV#2来计算描述符位置。如果此时隐藏服务的共识文件中的TP慢于客户端,这样的设计也能够保证客户端能获取到描述符信息。并且与隐藏服务处于同时间段的客户端也是可以访问描述符的(获取的是第一描述符,用TP#1和SRV#1)。
2.2.5 隐藏服务描述符的过期机制
隐藏服务将其描述符的descriptor-lifetime
字段设置为180分钟(3小时)。 隐藏服务通过按[WHEN-HSDESC]中的规定定期重新发布其描述符,从而确保其描述符在HSDir缓存中保持有效。
隐藏服务还必须保证它们的介绍链路处于有效状态,只要包含这些介绍结点的描述符还有效(即使是在TP改变之后)。
2.2.6 匿名上传和下载的URL
符合此规范的隐藏服务描述符通过HTTP POST请求上传到相对于隐藏目录服务的根目录的URL /tor/hs/<version>/publish
,并通过发送HTTP GET请求至URL /tor/hs/<version>/<z>
下载描述符,其中<z>是隐藏服务盲公钥的base64编码,而<version>是协议版本,在这种情况下为“ 3”。
必须在没有其他用途的匿名Tor链路上发送这些请求。(也就是说上述请求也是通过Tor匿名链路进行传输)
2.2.7 洋葱地址的客户端验证
当Tor客户端从用户那里接收到prop224洋葱地址(v3地址又称为prop224地址)时,它必须在尝试连接或获取其描述符之前首先验证洋葱地址。 如果验证失败,则客户端必须拒绝连接。
作为地址验证的一部分,Tor客户端应检查底层的ed25519密钥是否没有torison组件(?)。 如果Tor接受带有torison组件的ed25519密钥,则攻击者可以为单个ed25519密钥创建多个等效的洋葱地址,这些地址将映射到同一个隐藏服务。 我们要避免这种情况,因为它可能导致网络钓鱼攻击等行为(例如,假设一个浏览器插件阻止了某洋葱地址,但用户可以通过torison组件使用一个等效洋葱地址来绕开该插件)。
客户端检测此类欺诈性地址的正确方法是从洋葱地址中提取ed25519公钥,然后将其乘以ed25519组顺序,最后确保结果是ed25519的身份元素。 有关更多详细信息,请参见[TORSION-REFS]。
2.3 发布共享随机值 [PUB-SHAREDRANDOM]
我们用于降低HSDir上传位置可预测性的设计依赖于共享随机值(SRV),该值无法提前预测或受到攻击者的影响。权威目录服务必须运行一个协议,以便在每个hsdir周期至少生成一次该值。在这里,我们描述它们如何发布这些值。 有关更多信息,请参见[SHAREDRANDOM-REFS]。
根据proposal-250,我们在共识文件中添加了两行:
"shared-rand-previous-value" SP NUM_REVEALS SP VALUE NL "shared-rand-current-value" SP NUM_REVEALS SP VALUE NL
2.3.1 缺少SRV时的客户端行为
如果无法在共识文件中找到上一个周期或当前周期的共享随机值,则Tor客户端和隐藏服务需要自己生成随机值以供选择HSDirs时使用。
为此,Tor客户端和隐藏服务使用:
SRV=H(“shared\textrm{--}random\textrm{--}disaster"\ |\ int\_8(period\_length)\ | int\_8(period\_num)\ )
其中period_length是TP的长度(以分钟为单位),period_num的计算方式参照[TIME-PERIODS]。
2.3.2 隐藏服务与更改SRV
从理论上讲,由于权威目录服务器的离线或运行异常,共识文件中的SRV可能会在一段时间内发生变化或消失。
为了避免因为这种罕见事件造成客户端访问隐藏服务错误,隐藏服务应使用新的共享随机值来查找HSDirs并将其描述符上传到至其中。
2.4 隐藏服务描述符:外部格式 [DESC-OUTER]
表格的SP代表空格,NL代表换行
属性 | 出现次数 | 描述 |
---|---|---|
“hs-descriptor” SP version-number NL | 开始时1次 | 版本号是一个32位无符号整数,指示描述符的版本。 当前版本是“ 3”。 |
“descriptor-lifetime” SP LifetimeMinutes NL | 仅1次 | 描述符的生命周期,以分钟为单位。HSDir应该在隐藏服务描述符上传后至少LifetimeMinutes内使该描述符过期。 |
“descriptor-signing-key-cert” NL certificate NL | 仅1次 | “certificate”字段包含提案220格式的证书,并用“ —– BEGIN ED25519 CERT —–”包装。 该证书使用盲公钥来交叉验证短期的描述符签名密钥(descriptor signing key,后面会用到)。证书类型必须为[08],并且盲公钥必须作为签名密钥扩展名出现。 |
“revision-counter” SP Integer NL | 仅1次 | 描述符的修订号。如果HSDir重复接收到一个当前已保存的描述符,那么它将保留该描述符,并使用更大的修订计数器来描述该描述符。(检查修订计数器值是否单调增加,以防止攻击者用较旧版本的副本替换由给定密钥签名的较新描述符。其实这就是起到一个版本控制作用,区分描述符的新旧) |
“superencrypted” NL encrypted-string | 仅1次 | 一个加密的二进制大型对象(Blob),其格式将在下面的[HS-DESC-ENC]中进行讨论。 Blob是base64编码的,并包含在—– BEGIN MESSAGE —-和—- END MESSAGE —-的包装器中。 (结果文档不以换行符结尾。) |
“signature” SP signature NL | 仅1次,在末尾 | 使用descriptor-signing-key-cert中的签名密钥对前面所有字段的签名,其前缀为字符串“Tor onion service descriptor sig v3”。我们使用一个单独的密钥进行签名,这样隐藏服务主机不需要将其私有的盲密钥公开(因为签名用私钥)。 |
HSDirs接受最多50k字节的隐藏服务描述符(还应引入一个共识参数来控制此阈值)。
2.5 隐藏服务描述符:加密格式 [HS-DESC-ENC]
隐藏服务描述符由两层加密保护。客户端需要解密这两层才能连接到隐藏服务。
第一层加密防止不知道隐藏服务公钥的实体(例如HSDirs)获取描述符信息,而第二层加密仅在启用客户端授权时才使用,防止那些不具有有效客户端凭据的实体访问隐藏服务。
2.5.1 第一层加密 [HS-DESC-FIRST-LAYER]
2.5.1.1 第一层加密的逻辑
第一层加密的密钥和格式按照[HS-DESC-ENCRYPTION-KEYS]中的规定生成,并带有自定义参数:
\begin{aligned} {\it SECRET\_DATA}&=blinded\textrm{--}public\textrm{--}key\\ {\it STRING\_CONSTANT}&=''hsdir\textrm{--}superencrypted\textrm{--}data" \end{aligned}
[HS-DESC-ENCRYPTION-KEYS]中的加密方案使用从身份公钥(请参阅[SUBCRED])派生处的服务证书来确保只有知道身份公钥的实体才能解密第一层加密
该密文被放在描述符的“superencrypted”字段中。在加密之前,明文用NUL字节填充到最接近的10k字节的倍数。
2.5.1.2 第一层明文格式
客户端解密第一层加密后,他们需要解析第一层明文以获取包含在“encrypted”字段中的第二层密文。
如果启用了客户端身份验证,则隐藏服务将生成一个新的descriptor_cookie密钥(32个随机字节),并使用每个已授权客户端的身份x25519密钥对其进行加密。 已授权客户端可以使用descriptor_cookie来解密第二层。我们的加密方案还要求隐藏服务为每个新描述符生成一个临时x25519密钥对。
如果禁用了客户端身份验证,则会在下面的每个字段中放置伪数据,以对是否启用了客户端授权进行混淆操作。
以下是所有受支持的字段:
属性 | 出现次数 | 描述 |
---|---|---|
“desc-auth-type” SP type NL | 仅1次 | 该字段包含用于保护描述符的授权类型。唯一可识别的类型是“ x25519”,并指定为本节中描述的加密方案。如果禁用了客户端授权,则此处的值应为“ x25519”。 |
“desc-auth-ephemeral-key” SP key NL | 仅1次 | 由隐藏服务生成并以base64编码的临时x25519公钥。该密钥由以下加密方案使用。如果禁用了客户端授权,则此处的值应该是不被使用的一个新x25519公钥。 |
“auth-client” SP client-id SP iv SP encrypted-cookie | 至少1个 | (太长,见表格下说明) |
“encrypted” NL encrypted-string | 仅1次 | 包含第二层密文的加密Blob,其格式将在下面的[HS-DESC-SECOND-LAYER]中进行讨论。Blob是base64编码的,并包含在—– BEGIN MESSAGE —-和—- END MESSAGE —-包装器中。 |
对上述auth-client的额外描述:
启用客户端授权后,隐藏服务将为其每个授权客户端插入一行“auth-client”。如果禁用了客户端授权,则可以使用适当大小的随机数据填充此处的字段(“ client-id”的8个字节,“ iv”的16个字节和“ encrypted-cookie”的16个字节均使用base64编码)。
启用客户端授权后,每个“auth-client”行都包含为每个客户端加密的descriptor cookie。我们假设每个授权客户端都拥有一个预共享的x25519密钥对,该密钥对用于解密描述符cookie。
现在,我们概述descriptor cookie的加密方案,涉及到的密钥如下:
- client_x:授权客户端的x25519私钥
- client_X:授权客户端的x25519公钥
- hs_y:隐藏服务的临时x25519密钥对中的私钥
- hs_Y:隐藏服务的临时x25519密钥对中的公钥
- descriptor cookie:用于加密描述符第二层数据descriptor cookie
隐藏服务的计算结果如下:
SECRET_SEED = x25519(hs_y, client_X)
KEYS = KDF(subcredential | SECRET_SEED, 40)
CLIENT-ID = fist 8 bytes of KEYS
COOKIE-KEY = last 32 bytes of KEYS
“ auth-client”行中字段的描述如下:
- “client-id”字段是base64编码后的CLIENT-ID。
- “iv”字段是以base64编码的16个随机字节。
- “encrypted-cookie”字段包含以下descriptor cookie密文,并以base64编码:
encrypted-cookie = STREAM(iv, COOKIE-KEY) XOR descriptor_cookie
有关客户端如何解密descriptor cookie的逻辑,请参见[FIRST-LAYER-CLIENT-BEHAVIOR]部分。
2.5.1.3 客户端行为 [FIRST-LAYER-CLIENT-BEHAVIOR]
客户端在此阶段的目标是解密[HS-DESC-SECOND-LAYER]中描述的“encrypted”字段。
如果启用了客户端授权,则授权的客户端需要提取descriptor cookie来进行第二层的解密,如下所示:
授权客户端解析加密描述符的第一层,从“desc-auth-ephemeral-key”中提取临时密钥,并使用其x25519私钥计算上述部分所述的CLIENT-ID和COOKIE-KEY。然后,客户端使用CLIENT-ID查找正确的“auth-client”字段,其中包含descriptor cookie的密文。然后,客户端使用COOKIE-KEY和iv来解密descriptor_cookie,该Cookie用于解密描述符的第二层加密,如[HS-DESC-SECOND-LAYER]中所述。
2.5.1.4 隐藏客户端授权数据
隐藏服务应避免泄漏是否启用了客户端授权或有多少个授权客户端。
因此,即使禁用了客户端授权,隐藏服务也会向描述符添加伪造的“desc-auth-type”,“desc-auth-ephemeral-key”和“auth-client”行,如[HS-DESC-FIRST-LAYER]中所述。
隐藏服务还通过在其描述符中添加伪造的“auth-client”来避免泄漏授权客户端的数目。具体地说,描述符内总是包含16的倍数个授权客户端,如果授权客户端数目不是16的倍数,可以通过添加伪造的“auth client”来达到。
只要描述符的总大小在50k的最大限制范围内(描述符的大小限制也可由共识参数控制),客户端必须接受携带任意个“auth client”的描述符。
2.5.2 第二层加密 [HS-DESC-SECOND-LAYER]
第二层加密旨在保护描述符的机密性,防止未经授权的客户端访问隐藏服务。如果启用了客户端授权,则会使用descriptor_cookie对其进行加密,其中包含了连接到隐藏服务所需要的信息,比如其介绍结点列表。
如果禁用了客户端授权,则第二层HS加密将不提供任何额外的安全性,但仍然会使用。
2.5.2.1 第二层加密所用的密钥
第二层加密的密钥格式按照[HS-DESC-encryption-keys]中的规定生成,自定义参数如下:
\begin{aligned} {\it SECRET\_DATA}&=blinded\textrm{--}public\textrm{--}key\ |\ descriptor\_cookie\\ {\it STRING\_CONSTANT}&=''hsdir\textrm{--}encrypted\textrm{--}data'' \end{aligned}
如果禁用了客户端授权,则“descriptor_cookie”字段留空。
密文放在描述符(第一层中的)的“encrypted”字段上。
2.5.2.2 第二层的明文格式
解密第二层密文后,客户端最终可以获得介绍结点列表等信息。第二层的明文具体格式如下:
属性 | 出现次数 | 描述 |
---|---|---|
“create2-formats” SP formats NL | 仅1次 | 用空格分隔的整数列表,表示服务器可以识别的CREATE2单元格格式编号。如tor-spec.txt中所述,必须至少包含ntor。有关可识别的握手类型的列表,请参见tor-spec第5.1节。 |
“intro-auth-required” SP types NL | 至多1次 | 用空格分隔的介绍协议身份认证类型列表;有关更多信息,请参见[INTRO-AUTH]部分。只要客户端不支持该列表中身份认证类型的某一项,则该客户端将无法与隐藏服务取得联系。可识别的类型为:“password”和“ ed25519”。 |
“single-onion-service” | 不填写或至多1次 | 如果该项存在,则表示该隐藏服务是单一洋葱服务(有关该类型服务的更多详细信息,请参阅prop260)。0.3.0中引入了此字段,这意味着0.2.9服务不包括此字段。 |
后面紧跟着是0个或多个介绍点信息,格式如下:
- “introduction-point” SP link-specifiers NL
[始于介绍点部分,每个介绍点正好1次]
link-specifiers(链接说明符)是以[BUILDING-BLOCKS]中描述的格式对链接说明符块进行base64编码后的结果。
从0.4.1.1-alpha开始,服务在描述符中同时包含了IPv4和IPv6链路说明符。所有可用的地址都应该包含在描述符中,而不管隐藏服务实际用于连接/扩展到介绍点的地址是什么格式。
客户端不应该拒绝任何它不能识别的LSTYPE字段;相反,它应该在扩展到介绍结点的EXTEND请求中逐字使用它们。
客户端可以对描述符中的链接说明符执行基本的有效性检查。这些检查不应泄漏有关客户端版本、配置或共识的详细信息。(有关服务链接说明符处理,请参见3.3。)
- “onion-key” SP “ntor” SP key NL
[每一介绍结点只有一次]。
密钥是base 64编码的curve25519公钥,它是用于当客户端扩展到介绍结点时用于握手的洋葱密钥。
- “auth-key” NL certificate NL
[每一介绍结点只有一次]。
该证书是一个包含在“—-BEGIN ED25519 CERT—–”中的proposal220格式的证书,使用描述符签名密钥交叉验证介绍点的身份验证密钥。介绍点身份验证密钥包含在强制签名密钥扩展中。证书类型必须为[09]。
- “enc-key” SP “ntor” SP key NL
[每个介绍点只有一次]
密钥是一个base64编码的curve25519公钥,用于加密服务的引入请求。
- “enc-key-cert” NL certificate NL
[每个介绍点只有一次]
使用描述符签名密钥对加密密钥(enc-key)进行交叉认证。
- “legacy-key” NL key NL
[为空或每个介绍点最多一次]
密钥是PEM格式的ASN.1编码的RSA公钥,用于[LEGACY_EST_INTRO]中所述的旧版的介绍结点。
当介绍结点仅支持<= 0.2.9的旧协议(v2)或协议版本值为“HSIntro 3”时,才显示此字段。
- “legacy-key-cert” NL certificate NL
[为空或每个介绍点最多一次]
如果存在“legacy-key”,则该项必须存在。
该证书是一个包装在“—-BEGIN CROSSCERT—–”中的proposal220 RSA->Ed 交叉证书,使用描述符签名密钥交叉认证在“legacy-key”中找到的RSA公钥。
为了与将来对描述符格式的修订保持兼容,客户端应该忽略描述符中无法识别的行。同时因为允许使用其他加密和身份验证密钥格式;故客户端应忽略它们无法识别的密钥格式。
设法提取隐藏服务介绍结点的客户端可以使用[INTRO-protocol]中指定的引入协议进行处理。
2.5.3 派生隐藏服务描述符加密密钥 [HS-DESC-ENCRYPTION-KEYS]
在本节中,我们将介绍隐藏服务描述符的通用加密格式。 我们在两个加密层中都使用了相同的加密格式,因此我们引入了两个自定义参数SECRET_DATA和STRING_CONSTANT,这两个参数在不同层之间有所差异。
SECRET_DATA参数指定在加密密钥生成期间使用的秘密数据,而STRING_CONSTANT这一字符串常量仅仅是作为KDF的一部分使用。
下面是密钥的生成逻辑:
SALT = 16 bytes from H(random) 即使描述符的内容没有更改,每次我们重新构建描述符时该盐值也会更改(这样我们就不会泄漏介绍结点列表的变化情况) secret_input = SECRET_DATA | subcredential | INT_8(revision_counter) keys = KDF(secret_input | salt | STRING_CONSTANT, S_KEY_LEN + S_IV_LEN + MAC_KEY_LEN) SECRET_KEY = first S_KEY_LEN bytes of keys SECRET_IV = next S_IV_LEN bytes of keys MAC_KEY = last MAC_KEY_LEN bytes of keys
加密数据的格式如下:
SALT hashed random bytes from above [16 bytes] ENCRYPTED The ciphertext [variable] MAC D_MAC of both above fields [32 bytes]
最终的加密格式为:
ENCRYPTED = STREAM(SECRET_IV,SECRET_KEY) XOR Plaintext
上述的D_MAC计算公式如下:
D_MAC = H(mac_key_len | MAC_KEY | salt_len | SALT | ENCRYPTED) mac_key_len = htonll(len(MAC_KEY)) salt_len = htonll(len(SALT))
2.5.4 介绍结点的数目 [NUM_INTRO_POINT]
本节定义了隐藏服务描述符可以拥有的介绍结点数目的最小值,默认值和最大值:
最小值:0 – 默认值:3 – 最大值:20
介绍结点数目为0表示该服务仍然有效,但是目前不希望任何客户端访问该服务。 注意,随着增加更多的介绍结点,隐藏服务描述符的大小显着增加。
最大值为20的原因是为诸如OnionBalance之类的工具提供了足够的可伸缩性,以使其能够负载均衡多达120台服务器(20 x 6 HSDirs),而且还为了防止因描述符过大而对隐藏目录服务的负载造成影响。
发表评论