没有会话的HTTP请求签名

5
我考虑创建一个REST Web服务,确保每个发送给它的请求都满足以下条件:
  • 请求是由声称发送该请求的用户生成的;
  • 请求没有被其他人修改(URI/方法/内容/日期);
  • 对于GET请求,应该可以生成一个包含足够信息的URI来检查签名并设置过期日期。这样,用户就可以将临时读取权限委托给协作者,在生成的URI上限制一段时间。
客户端使用基于其密码的ID和内容签名进行身份验证。 完全不应该有会话或服务器状态!服务器和客户端共享一个密钥(密码) 在考虑了一些非常好的人之后,似乎不存在REST服务能够简单地实现我的用例(HTTP Digest和OAuth需要服务器状态并且传输量很大)。
因此,我想象了一个,并请您提出关于如何设计它的建议(我将开源发布它,并希望它能帮助他人)。
该服务使用自定义的“Content-signature”标头存储凭据。已验证的请求应包含此标头:
Content-signature: <METHOD>-<USERID>-<SIGNATURE>

<METHOD> is the sign method used, in our case SRAS.
<USERID> stands for the user ID mentioned earlier.
<SIGNATURE> = SHA2(SHA2(<PASSWORD>):SHA2(<REQUEST_HASH>));
<REQUEST_HASH> = <HTTP_METHOD>\n
                 <HTTP_URI>\n
                 <REQUEST_DATE>\n
                 <BODY_CONTENT>;

请求在创建后10分钟内失效。

例如,典型的HTTP请求如下:

POST /ressource HTTP/1.1
Host: www.elphia.fr
Date: Sun, 06 Nov 1994 08:49:37 GMT
Content-signature: SRAS-62ABCD651FD52614BC42FD-760FA9826BC654BC42FD

{ test: "yes" }

服务器将会回答:
401 Unauthorized

或者

200 OK

变量应该是:

<USERID> = 62ABCD651FD52614BC42FD
<REQUEST_HASH> = POST\n
                 /ressource\n
                 Sun, 06 Nov 1994 08:49:37 GMT\n
                 { test: "yes" }\n

URI参数

一些参数可以添加到URI中(它们会覆盖头信息):

  • _sras.content-signature=<METHOD>-<USERID>-<SIGNATURE> :将凭据放入URI中,而不是HTTP头中。这使得用户可以共享已签名的请求;
  • _sras.date=Sun, 06 Nov 1994 08:49:37 GMT (请求日期*):请求创建时的日期。
  • _sras.expires=Sun, 06 Nov 1994 08:49:37 GMT (过期日期*):告诉服务器在指定日期之前不应过期请求。

*日期格式:http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18

感谢您的评论。

2个回答

5
设计签名协议时需要考虑以下几个问题,其中一些问题可能与您的特定服务无关:
1. 通常在非标准标头前添加“X-Namespace-”前缀,在您的情况下,您可以将标头命名为“X-SRAS-Content-Signature”。
2. 日期标头可能无法提供足够的分辨率用于nonce值,因此建议使用至少具有1毫秒分辨率的时间戳。
3. 如果您未存储至少最后一个nonce,则仍然可以在10分钟窗口期内重放消息,这对于POST请求可能是不可接受的(可能会在REST Web服务中创建具有相同值的多个实例)。这对于GET PUT或DELETE动词来说不应该是问题。
但是,在PUT上,可以通过在建议的10分钟窗口期内强制多次更新相同对象来用于拒绝服务攻击。在GET或DELETE上存在类似的问题。
因此,您可能需要存储与每个用户ID相关联的至少最后一个已使用的nonce,并在所有身份验证服务器之间实时共享此状态。
4. 这种方法还要求客户端和服务器在不到10分钟的偏差内进行时钟同步。如果您具有无法控制时钟的AJAX客户端,则可能很难调试或无法强制执行。这还要求在UTC中设置所有时间戳。
另一种选择是放弃10分钟窗口期要求,但验证时间戳单调递增,这再次需要存储最后一个nonce。如果将客户端的时钟更新为早于上次使用的nonce的日期,则仍然存在问题。直到客户端的时钟超过最后一个nonce或服务器nonce状态被重置之前,访问将被拒绝。
对于无法存储状态的客户端,单调递增的计数器不是选项,除非客户端可以向服务器请求最后使用的nonce。这将在每个会话开始时执行一次,然后在每个请求中递增计数器。
5. 您还需要注意由于网络错误而重新传输的情况。您不能假设服务器未收到最后一条消息,对于尚未通过客户端接收TCP Ack的最后一条消息,在TCP级别以上需要递增nonce,并使用新nonce重新计算签名。但是需要添加消息编号以防止在服务器上进行双重执行:双重POST将导致创建2个对象。
6. 您还需要签署用户ID,否则攻击者可能能够为所有nonce尚未达到重放消息的用户重复播放相同的消息。
7. 您的方法不能保证客户端服务器是真实的并且没有被DNS劫持。通常认为服务器身份验证对于安全通信很重要。可以通过使用与请求相同的nonce来签署服务器的响应来提供此服务。

嗨@jean。1-我会查看RFC。2-我不明白你的意思,没有nonce,但让我们看看下一个问题。3-是的,你是对的。为了正确处理重放攻击,我应该将已使用的签名存储到memcached服务器中以查看是否已被使用(在10分钟后过期)。4-我可以同步它们,第一个请求给出服务器日期,ajax客户端检查服务器和自己之间的差异。并提供一个好的UTC DATE。5-那不是问题。并且可以检查签名是否已被使用6-是的,正确!7-是的,我会检查添加支持。 - Sébastien VINCENT
@ Sébastien,2- 在您提出的协议中,实际上将日期用作一次性号码。如果在同一秒钟内发出两个请求,则它们将具有相同的日期,并且应该因为使用了前一个请求的相同的一次性号码而被拒绝。将分辨率提高到一毫秒可能足够,也可能不足够。4- 如果您使用设置请求进行同步,则只需使用计数器可能更容易。此外,您仍然可能存在后台日期更新问题。5- 如果检查签名,则应将第二条消息拒绝为未经授权的(重放)消息,这可能会混淆客户端。 - Jean Vincent
2- 如果日期已经被查看过,我不会拒绝该请求。也许以前我表述得不够清楚。 4- 我只保存本地时间和服务器时间之间的差异秒数。这可以通过从服务器发送的任何请求完成。没有必要为此发出请求,只需要检查上一个请求即可。 5- 这是预期的行为。如果请求似乎失败了,用户必须使用新签名创建一个新请求(并更改日期的秒部分)。 - Sébastien VINCENT
@Sebastien 2- 如果一个已经被看到的日期没有被拒绝,那么就没有nonce。这容易受到重放攻击的影响,例如多个POST导致创建了2个或更多对象。在这种情况下,我不理解日期的目的。您需要澄清您想要通过以下方式实现的目标:“这样用户可以在生成的URI上为有限的时间段将临时READ权限委派给协作者”。这听起来像是一个安全问题,因为委派应该在授权级别而不是消息完整性级别处理。5- 这可能会导致双重POST。 - Jean Vincent
2- Nonce是散列签名。请求不能被使用两次。如果请求发生变化,则签名也会发生变化。如果请求尚未到达服务器,我将创建一个新的完全相同但带有新签名的请求(日期用于签名)。委托意味着我想要为某人提供访问我拥有权限的资源的URI。我可能会考虑这个问题,因为你说得对,那不是做这件事的正确场所。 5- 我需要在其他地方处理它。在REST架构中,我必须尽可能地尝试成为幂等的。 - Sébastien VINCENT

1

我想指出你可以用OAuth来实现这个功能,尤其是"2-legged OAuth",客户端和服务端共享一个秘密。请看https://www.rfc-editor.org/rfc/rfc5849#page-14。在你的情况下,你需要省略oauth_token参数并且可能使用HMAC-SHA1签名方法。这并不是特别繁琐的事情;你不需要通过 OAuth token 获取流程来做到这一点。这样做的好处是可以使用任何几个现有的开源OAuth库。

至于服务器端状态,你确实需要跟踪哪个秘密属于哪个客户端,以及最近使用了哪些nonce(以防止重放攻击)。如果你在HTTPS上运行这些内容,则可以跳过nonce检查/生命周期,但如果你要这样做,那么HTTPS +基本认证可以为您提供所有描述的功能,而无需编写新软件。


1
谢谢。我不确定我是否真的想进入OAuth大机器。但也许我会尝试“2-legged OAuth”。感谢您的回答。 - Sébastien VINCENT

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接