互联网工程任务组 (IETF) R. Shekh-Yusef,编辑
请求注释: 7616 Avaya
取代: 2617 D. Ahrens
类别:标准轨道 Independent
ISSN: 2070-1721 S. Bremer
Netzkonform
2015 年 9 月

HTTP 摘要访问认证


摘要

超文本传输协议 (HTTP) 提供了一种简单的挑战-响应认证机制,服务器可以用它对客户端请求发出挑战,客户端可以用它提供认证信息。本文档定义了可与 HTTP 认证机制一起使用的 HTTP 摘要认证方案。

本备忘录的状态

这是一个互联网标准轨道文档。

本文档是互联网工程任务组 (IETF) 的产物。它代表了 IETF 社区的共识。它已接受公开审查并已获互联网工程指导小组 (IESG) 批准发布。有关互联网标准的更多信息,请参阅 RFC 5741 第2节

有关本文档的当前状态、任何勘误以及如何提供反馈的信息,可在 http://www.rfc-editor.org/info/rfc7616 获取。

Copyright Notice

Copyright (c) 2015 IETF Trust and the persons identified as the document authors. All rights reserved.

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (http://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Simplified BSD License.

This document may contain material from IETF Documents or IETF Contributions published or made publicly available before November 10, 2008. The person(s) controlling the copyright in some of this material may not have granted the IETF Trust the right to allow modifications of such material outside the IETF Standards Process. Without obtaining an adequate license from the person(s) controlling the copyright in such materials, this document may not be modified outside the IETF Standards Process, and derivative works of it may not be created outside the IETF Standards Process, except to format it for publication as an RFC or to translate it into languages other than English.

1. 引言

HTTP 提供了一种简单的挑战-响应认证机制,服务器可用其对客户端请求发出挑战,客户端可用其提供认证信息。本文档定义了可与 HTTP 认证机制一起使用的 HTTP 摘要认证方案。

本文档对 [RFC2617] 进行了扩展,但总体向后兼容。有关本规范引入的新功能,请参见 附录 A

挑战-响应认证机制的细节在 "Hypertext Transfer Protocol (HTTP/1.1): Authentication" 中规定。[RFC7235]

本文件与 "Basic" 认证方案的定义 [RFC7617]、"HTTP Authentication-Info and Proxy-Authentication-Info Response Header Fields" [RFC7615] 以及 "Hypertext Transfer Protocol (HTTP/1.1): Authentication" [RFC7235] 一起,废弃了 [RFC2617]

1.1. 术语

本文档中的关键字 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", 和 "OPTIONAL" 应按 [RFC2119] 的定义来解释。

2. 语法约定

2.1. 示例

为了清晰和可读性,本文档中的扩展参数或示例中的头字段和参数可能会拆成多行。本文中任何缩进的行均为前一行的续行。

2.2. ABNF

本规范使用 [RFC5234] 的增强巴科斯-诺尔范式 (ABNF) 表示法,以及 [RFC7230] 的 ABNF 列表扩展。

3. 摘要访问认证方案

3.1. 整体操作

摘要方案基于简单的挑战-响应范式。摘要方案使用 nonce 值发出挑战,并可能指示支持用户名哈希。有效的响应包含用户名、密码、给定 nonce 值、HTTP 方法和请求 URI 的无密钥摘要。通过这种方式,密码不会以明文发送,并且用户名可以根据服务器指示进行哈希。用户名和密码必须以某种本文未涉及的方式预先约定。

3.2. 摘要值的表示

一个可选的头字段允许服务器指定用于创建无密钥摘要或摘要的算法。本文档新增了 SHA-256 和 SHA-512/256 算法。为与 [RFC2617] 保持向后兼容,仍然支持 MD5 算法,但 NOT RECOMMENDED

摘要的大小取决于所用算法。摘要中的比特按从最重要到最不重要的顺序,每四位映射为十六进制 ASCII 表示。每组四位用熟悉的十六进制字符 0123456789abcdef 表示;例如二进制 0000 表示为字符 '0',0001 表示为 '1',直到 1111 表示为 'f'。如果使用 MD5 算法计算摘要,则 MD5 摘要表示为 32 个十六进制字符,而 SHA-256 与 SHA-512/256 则表示为 64 个十六进制字符。

3.3. WWW-Authenticate 响应头字段

如果服务器接收到对受保护资源的请求,但未收到可接受的 Authorization 头字段,服务器应以 "401 Unauthorized" 状态码和带有 Digest 方案的 WWW-Authenticate 头字段响应,如上文框架所定义。该头字段的值可以包含下列参数:

realm

  • 一个应向用户显示的字符串,使他们知道应使用哪个用户名和密码。该字符串应至少包含执行认证的主机名,并且可以额外指示可能具有访问权限的用户集合。示例为 "registered_users@example.com"。(参见 RFC7235 第 2.2 节 获取更多细节。)

domain

  • 一个带引号的、以空格分隔的 URI 列表,如 [RFC3986] 所指定,定义保护空间。如果 URI 是 path-absolute,则相对于规范根 URL。(参见 RFC7235 第 2.2 节。)该列表中的绝对 URI 可能引用与 web-origin 不同的服务器。客户端可使用此列表来确定可发送相同认证信息的 URI 集合:任何具有该列表中某个 URI 作为前缀的 URI(在都变为绝对后)MAY 被视为属于相同的保护空间。如果省略此参数或其值为空,客户端SHOULD 假定保护空间包含 web-origin 上的所有 URI。
  • 该参数在 Proxy-Authenticate 头字段中没有意义,因为代理的保护空间始终是整个代理;如果存在,MUST 忽略之。

nonce

  • 服务器指定的字符串,应在每次返回 401 响应时唯一生成。建议该字符串为 Base64 或十六进制数据。由于该字符串作为带引号字符串在头字段行中传递,双引号字符不允许出现,除非适当转义。
  • nonce 的内容取决于实现。实现质量取决于良好的选择。例如,nonce 可能被构造成 Base64 编码的:
             timestamp H(timestamp ":" ETag ":" secret-data)
    
  • 其中 timestamp 是服务器生成的时间,最好包含微秒或纳秒或其他不重复的值;ETag 是与请求实体关联的 HTTP ETag 头字段的值;secret-data 是仅服务器知道的数据。采用此类 nonce 时,服务器在收到客户端认证头字段后会重新计算哈希部分,并在其与该头字段中的 nonce 不匹配或 timestamp 值不够新的情况下拒绝请求。通过这种方式,服务器可以限制 nonce 的有效期。包含 ETag 可防止针对资源更新版本的重放请求。将客户端的 IP 地址包含在 nonce 中似乎可以让服务器限制 nonce 的重用仅限最初获取它的同一客户端,但这会因单个用户的请求经常通过不同代理而失效,而且 IP 地址伪造并不难。
  • 实现可能选择不接受先前使用过的 nonce 或先前使用过的摘要,以防止重放攻击。或者,实现可能对 POST 或 PUT 请求使用一次性 nonce 或摘要,而对 GET 请求使用时间戳。关于相关问题的更多细节,请参见本文档的 第 5 节
  • nonce 对客户端是不可解读的(opaque)。

opaque

  • 由服务器指定的一串数据,客户端SHOULD 在后续对属于相同保护空间的请求的 Authorization 头字段中原样返回该值。建议该字符串为 Base64 或十六进制数据。

stale

  • 一个不区分大小写的标志,指示先前来自客户端的请求因 nonce 值已过期而被拒绝。如果 stale 为 true,客户端可以在不重新提示用户输入用户名和密码的情况下,简单地用新的加密响应重试请求。服务器SHOULD 仅在收到 nonce 无效的请求时将 stale 设置为 true。如果 stale 为 false、或任何非 true 的值,或 stale 参数不存在,则表明用户名和/或密码无效,MUST 获取新的值。

algorithm

  • 指示用于生成摘要和无密钥摘要的算法的字符串。如果未出现该参数,则假定为 "MD5"。如果算法无法识别,则应忽略该挑战(如果存在多个挑战,应使用其他挑战)。
  • 在 Digest 机制中,每种算法有两种变体:Session 变体和非 Session 变体。非 Session 变体表示为 "<algorithm>",例如 "SHA-256";Session 变体表示为 "<algorithm>-sess",例如 "SHA-256-sess"。
  • 在本文档中,应用摘要算法对数据 "data" 处理得到的字符串记为 H(data),对数据 "data" 使用密钥 "secret" 应用 KD 得到的字符串记为 KD(secret, data)。KD 表示 Keyed Digest,记号 unq(X) 表示删除引号并移除转义后的带引号字符串 X 的值。
         For "<algorithm>" and "<algorithm>-sess"
    
             H(data) = <algorithm>(data)
    
         and
    
             KD(secret, data) = H(concat(secret, ":", data))
    
  • 例如:
         For the "SHA-256" and "SHA-256-sess" algorithms
    
             H(data) = SHA-256(data)
    
  • 即摘要是对 secret 与冒号与 data 连接后的结果应用 "<algorithm>"。"<algorithm>-sess" 旨在允许高效的第三方认证服务器;关于使用差异,参见 第 3.4.2 节 的描述。

qop

  • 此参数MUST 被所有实现使用。它是一个或多个标记的带引号字符串,指示服务器支持的 "质量保护"(quality of protection)值。值 "auth" 表示认证;值 "auth-int" 表示带完整性保护的认证。下文描述了为这些选项计算 response 参数值的方法。未识别的选项MUST 被忽略。

charset

  • 这是服务器用于指示其支持的编码方案的可选参数。唯一允许的值是 "UTF-8"。

userhash

  • 这是服务器用于指示其支持用户名哈希的可选参数。有效值为 "true" 或 "false"。默认值为 "false"。

出于历史原因,发送方MUST 仅为下列参数生成带引号字符串语法:realm, domain, nonce, opaque, 和 qop。

出于历史原因,发送方MUST NOT 为下列参数生成带引号字符串语法:stale 和 algorithm。

3.4. Authorization 头字段

客户端应重试该请求,传递一个带 Digest 方案的 Authorization 头字段行,其定义按上文框架。opaque 和 algorithm 字段的值必须为用于所请求实体的 WWW-Authenticate 响应头字段中提供的那些值。

请求可包含下列参数:

response

  • 按下文定义计算出的十六进制数字字符串;它证明用户知道密码。

username

  • 指定 realm 中的用户名。带引号字符串可包含明文名称或以十六进制表示的哈希码。如果用户名包含 ABNF 带引号字符串产生式内部不允许的字符,则可以使用 username* 参数。在同一头字段选项中同时发送 username 和 username* 被视为错误,MUST 处理为错误。

username*

  • 如果 userhash 参数值被设置为 "false" 并且用户名包含 ABNF 带引号字符串中不允许的字符,则可使用此参数按 [RFC5987] 定义的扩展标注发送用户名。

realm

uri

qop

  • 指示客户端对消息应用了何种 "质量保护"。其值MUST 为服务器在 WWW-Authenticate 头字段中指示支持的备选项之一。这些值影响 response 的计算。注意该值为单一标记,而非像 WWW-Authenticate 中的带引号备选列表。

cnonce

  • 此参数MUST 被所有实现使用。cnonce 值是客户端提供的不可读的带引号 ASCII 字符串,用于客户端与服务器共同防止选择明文攻击、提供相互认证并提供一定的消息完整性保护。关于 rspauth 和 response 值的计算说明见下文。

nc

  • 此参数MUST 被所有实现使用。nc 表示 "nonce count"。nc 值是客户端使用该请求中 nonce 值发送的请求次数(包括当前请求)的十六进制计数。例如,对于对给定 nonce 值发送的第一个请求,客户端发送 "nc=00000001"。该参数的目的是允许服务器通过维护自己的计数副本来检测请求重放——如果同一 nc 值出现两次,则该请求为重放。关于 response 的构造描述见下文。

userhash

  • OPTIONAL 参数由客户端用于指示用户名已被哈希。有效值为 "true" 或 "false"。默认值为 "false"。

出于历史原因,发送方MUST 仅为下列参数生成带引号字符串语法:username, realm, nonce, uri, response, cnonce, 和 opaque。

出于历史原因,发送方MUST NOT 为下列参数生成带引号字符串语法:algorithm, qop, 和 nc。

如果某个参数或其值不合适,或缺少必需参数,适当的响应是返回 4xx 错误码。如果响应无效,则应记录一次登录失败,因为来自单个客户端的重复登录失败可能表示攻击者试图猜测密码。服务器实现SHOULD 在记录信息时谨慎,以免将明文密码(例如输入到 username 字段中的)写入日志。

上面对 response 的定义指明了其值的编码。下列定义展示了该值如何计算。

3.4.1. 响应

如果 qop 值为 "auth" 或 "auth-int":

      response = <"> < KD ( H(A1), unq(nonce)
                                   ":" nc
                                   ":" unq(cnonce)
                                   ":" unq(qop)
                                   ":" H(A2)
                          ) <">

有关 A1 和 A2 的定义见下文。

3.4.2. A1

如果 algorithm 参数的值为 "<algorithm>",例如 "SHA-256", 则 A1 为:

      A1       = unq(username) ":" unq(realm) ":" passwd

其中

      passwd   = < user's password >

如果 algorithm 参数的值为 "<algorithm>-sess",例如 "SHA-256-sess",则 A1 使用服务器在挑战中提供的 nonce 值和客户端在收到 WWW-Authenticate 挑战后在请求中发送的 cnonce 值来计算。它使用该挑战中的服务器 nonce(此处称为 nonce-prime)和响应中的客户端 nonce(此处称为 cnonce-prime),按如下方式构造 A1:

      A1       = H( unq(username) ":" unq(realm) ":" passwd )
                     ":" unq(nonce-prime) ":" unq(cnonce-prime)

这为后续请求和响应的认证创建了一个针对每个“认证会话”不同的“会话密钥”,从而限制了用任何一个密钥进行哈希的材料量。(注:有关认证会话的进一步讨论参见 第 3.6 节。)由于服务器只需使用用户凭据的哈希来创建 A1 值,该构造可与第三方认证服务配合使用,使得 Web 服务器无需实际的密码值。此类协议的具体规定超出本文档范围。

3.4.3. A2

如果 qop 参数的值为 "auth" 或未指定,则 A2 为:

      A2       = Method ":" request-uri

如果 qop 值为 "auth-int",则 A2 为:

      A2       = Method ":" request-uri ":" H(entity-body)

3.4.4. 用户名哈希

为保护用户名从客户端到服务器的传输,服务器SHOULD 在 WWW-Authenticate 头字段中设置 userhash 参数为 "true"。

如果客户端支持 userhash 参数,并且 WWW-Authenticate 头字段中的 userhash 参数被设置为 "true",则客户端MUST 在完成其他哈希计算后计算用户名的哈希,并在 Authorization 头字段中包含 userhash 参数且其值为 "true"。如果客户端未提供作为哈希值的用户名或未包含值为 "true" 的 userhash 参数,服务器MAY 拒绝请求。

客户端用于哈希用户名的操作如下,使用与哈希凭证相同的算法:

   username = H( unq(username) ":" unq(realm) )

3.4.5. Parameter Values and Quoted-String

注意,许多参数的值,例如 username 的值,定义为 "quoted-string"。然而,"unq" 表示法指出在构造字符串 A1 时会移除外层的引号。因此,如果 Authorization 头字段包含下列字段

   username="Mufasa", realm="myhost@example.com"

且用户 Mufasa 的密码为 "Circle Of Life",那么 H(A1) 将是 H(Mufasa:myhost@example.com:Circle Of Life),在摘要字符串中不包含引号。

除非该空白存在于被带引号的字符串或参与摘要的实体主体中,否则任何被摘要函数 H() 作用的字符串都不允许有空白。例如,上述示例中的字符串 A1 必须为

   Mufasa:myhost@example.com:Circle Of Life

冒号两侧不得有空白,但密码值中单词之间的空白应保留。同样,H() 所摘要的其他字符串在其用来分隔字段的冒号两侧也不得有空白,除非该空白在被带引号的字符串或被摘要的实体主体中本来就存在。

另外注意,如果应用了完整性保护(即 qop=auth-int),则 H(entity-body) 是实体主体的哈希,而不是消息主体 —— 它在发送方应用任何传输编码之前计算,并在接收方移除传输编码之后计算。请注意,这包括 multipart 边界以及任何 multipart 内容类型中每个部分内嵌的头字段。

3.4.6. Various Considerations

“Method” 值是 HTTP 请求方法,使用 US-ASCII 字母表示,按 RFC7230 第 3.1.1 节 的规定。 “request-target” 值是请求行中的 request-target,亦按 RFC7230 第 3.1.1 节 指定。该值 MAY 是 "*"、一个 "absolute-URI",或一个 "absolute-path"(参见 RFC7230 第 2.7 节),但它 MUST 与 request-target 一致。特别地,如果 request-target 是 "absolute-URI",则该值 MUST 为 "absolute-URI"。cnonce 值是客户端选择的值,其目的是防止选择明文攻击。

认证服务器MUST 确认由 uri 参数指定的资源与 Request-Line 中指定的资源相同;如果不同,服务器SHOULD 返回 400 Bad Request 错误。(由于这可能是攻击的迹象,服务器实现者可能希望记录此类错误。)在该字段中重复请求 URL 的信息的目的是处理中间代理可能更改客户端 Request-Line 的情况。该更改(尽管在语义上应等价)可能导致与客户端计算的摘要不同。

实现者应注意经身份验证的事务如何与共享缓存交互(参见 [RFC7234])。

3.5. The Authentication-Info and Proxy-Authentication-Info Header Fields

Authentication-Info 头字段和 Proxy-Authentication-Info 头字段(参见 [RFC7615])是通用字段,服务器MAY 用它们来传达与客户端响应成功认证有关的一些信息。

Digest 认证方案MAY 在确认请求中添加 Authentication-Info 头字段,并包含下列参数:

nextnonce

  • nextnonce 参数的值是服务器希望客户端在将来认证响应中使用的 nonce。服务器MAY 在 Authentication-Info 头字段中发送 nextnonce 字段,以实现一次性 nonce 或以其他方式更改 nonce。如果存在 nextnonce 字段,客户端SHOULD 在构造下一次请求的 Authorization 头字段时使用它。客户端不使用可能会导致服务器以 "stale=true" 要求重新认证。
    • 服务器实现者SHOULD 仔细考虑使用该机制的性能影响;如果每个响应都包含一个MUST 在下一次请求上使用的 nextnonce 参数,则流水线请求将不再可能。应权衡允许在有限时间内使用旧 nonce 以支持请求流水线的性能与安全权衡。使用 nc 参数可以在不严重影响流水线的情况下保留大部分使用新服务器 nonce 的安全优势。

qop

  • 指示服务器在响应中应用的 "quality of protection" 选项。值 "auth" 表示认证;值 "auth-int" 表示带完整性保护的认证。服务器SHOULD 在响应中对 qop 参数使用与客户端在相应请求中发送的相同值。

rspauth

  • 可选的响应摘要(rspauth 参数)支持相互认证 —— 服务器证明它知道用户的秘密,并且在 qop=auth-int 情况下还提供对响应的有限完整性保护。rspauth 的计算方式与 Authorization 头字段中的 response 相同,只是当请求的 Authorization 头字段中 qop 为 "auth" 或未指定时,A2 为
  •       A2       = ":" request-uri
    
    ,若为 qop=auth-int,则 A2 为
          A2       = ":" request-uri ":" H(entity-body)
    

cnonce and nc

  • 此消息所对应的客户端请求所用的 cnoncencMUST 与该请求使用的值相同。如果响应中指定了 qop=authqop=auth-int,则 rspauthcnoncenc 参数MUST 出现。

Authentication-Info 头字段允许出现在通过分块传输编码(chunked transfer coding)传输的 HTTP 消息的 trailer 中。

出于历史原因,发送方MUST 仅为下列参数生成带引号字符串语法:nextnoncerspauthcnonce

出于历史原因,发送方MUST NOT 为下列参数生成带引号字符串语法:qopnc

出于历史原因,ncMUST 恰好为 8 个十六进制数字。

3.6. Digest Operation

在接收到 Authorization 头字段后,服务器MAY 通过查找与提交的 username 对应的密码来检查其有效性。然后,服务器MUST 执行与客户端相同的摘要操作(例如 MD5、SHA-256),并将结果与给定的 response 值进行比较。

注意,HTTP 服务器实际上不需要知道用户的明文密码。只要服务器可获得 H(A1),就可以验证 Authorization 头字段的有效性。

客户端对某个保护空间的 WWW-Authenticate 挑战的响应启动了与该保护空间的认证会话。认证会话持续到客户端从保护空间内的任意服务器接收到另一个 WWW-Authenticate 挑战为止。客户端SHOULD 记住与认证会话关联的 username、密码、nonce、nonce count 和 opaque 值,以便在该保护空间内的将来请求中构造 Authorization 头字段。Authorization 头字段MAY 被预先包含;这样可以提高服务器效率并避免额外的认证往返。服务器MAY 选择接受旧的 Authorization 头字段信息,即使其中包含的 nonce 可能不是新鲜的。或者,服务器MAY 返回一个带有新 nonce 值的 401 响应(在 WWW-Authenticate 头字段中),使客户端重试该请求;如果响应中指定了 "stale=true",服务器就告诉客户端重试时使用新 nonce,但不需要重新提示用户名和密码。

因为客户端被要求在会话期间返回服务器提供的 opaque 参数的值,所以 opaque 数据可以用来传输认证会话状态信息。(注意,任何此类用法也可以更简单且更安全地通过将状态包含到 nonce 中来完成。)例如,服务器可以负责对实际上位于另一台服务器上的内容进行认证。它可以在首次 401 响应中包含一个 domain 参数,其值包含指向第二台服务器上的 URI,以及一个 opaque 参数,其值包含状态信息。客户端将重试请求,此时服务器可能会以 HTTP 重定向(参见 RFC7231 第 6.4 节)响应,指向第二台服务器上的 URI。客户端会跟随重定向并传递包含该 opaque 数据的 Authorization 头字段。

代理在 Digest 访问认证方案中MUST 完全透明。也就是说,代理MUST 不改动地转发 WWW-Authenticate、Authentication-Info 和 Authorization 头字段。如果代理希望在将请求转发到服务器之前对客户端进行认证,则可以使用下文 第 3.8 节 中描述的 Proxy-Authenticate 和 Proxy-Authorization 头字段来完成。

3.7. Security Protocol Negotiation

对于服务器而言,了解客户端能够处理哪些安全方案是有用的。

服务器可能希望强制使用 Digest 作为其认证方法,即使服务器不知道客户端是否支持它。若服务器仅指定客户端无法处理的认证方案,鼓励客户端优雅失败。

当服务器收到访问资源的请求时,服务器可能通过返回 "401 Unauthorized" 响应并包含一个或多个 WWW-Authenticate 头字段来对客户端发起挑战。如果服务器返回多个挑战,则每个挑战MUST 使用不同的摘要算法。服务器MUST 按偏好顺序将这些挑战添加到响应中,从最优先的算法开始,随后是较不优先的算法。

本规范定义以下算法:

  • SHA2-256(实现为强制性)
  • SHA2-512/256(作为备选算法)
  • MD5(用于向后兼容)。

当客户端收到第一个挑战时,除非本地策略另有规定,它SHOULD 使用它所支持的第一个挑战。

3.8. Proxy-Authenticate and Proxy-Authorization

Digest 认证方案也可以用于对代理、代理之间或代理对源服务器进行认证,方法是使用 Proxy-Authenticate 和 Proxy-Authorization 头字段。这些头字段是 HTTP/1.1 规范中第 4.3 和第 4.4 节所指定的 Proxy-Authenticate 和 Proxy-Authorization 头字段的实例,其行为受该处描述的限制。针对代理认证的事务与前述描述的非常类似。当收到需要认证的请求时,代理/服务器MUST 发出 "407 Proxy Authentication Required" 响应,并带有 "Proxy-Authenticate" 头字段。Proxy-Authenticate 头字段中使用的 digest-challenge 与上文第 3.3 节 中为 WWW-Authenticate 头字段定义的相同。

然后客户端/代理MUST 重新发出请求,并在请求中包含 Proxy-Authorization 头字段,其参数按上文第 3.4 节 对 Authorization 头字段规定的方式指定。

在随后的响应中,服务器会发送 Proxy-Authentication-Info,参数与 Authentication-Info 头字段相同。

注意,原则上,客户端可能被要求同时对代理和终端服务器进行认证,但不会在同一响应中同时出现。

3.9. Examples

3.9.1. Example with SHA-256 and MD5

下面的示例假设通过 GET 请求从服务器请求一个受保护的文档。该文档的 URI 为 "http://www.example.org/dir/index.html"。客户端和服务器都知道该文档的用户名是 "Mufasa",密码是 "Circle of Life"(单词之间有一个空格)。

客户端首次请求该文档时未发送 Authorization 头字段,因此服务器响应:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest
    realm="http-auth@example.org",
    qop="auth, auth-int",
    algorithm=SHA-256,
    nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
    opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
WWW-Authenticate: Digest
    realm="http-auth@example.org",
    qop="auth, auth-int",
    algorithm=MD5,
    nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
    opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"

客户端可以提示用户输入用户名和密码,然后如果客户端选择 MD5 摘要,它将用下列 Authorization 头字段对新请求做出响应:

Authorization: Digest username="Mufasa",
    realm="http-auth@example.org",
    uri="/dir/index.html",
    algorithm=MD5,
    nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
    nc=00000001,
    cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ",
    qop=auth,
    response="8ca523f5e9506fed4657c9700eebdbec",
    opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"

如果客户端选择使用 SHA-256 算法计算 response,则客户端会用下列 Authorization 头字段对新请求做出响应:

Authorization: Digest username="Mufasa",
    realm="http-auth@example.org",
    uri="/dir/index.html",
    algorithm=SHA-256,
    nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
    nc=00000001,
    cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ",
    qop=auth,
    response="753927fa0e85d155564e2e272a28d1802ca10daf449
       6794697cf8db5856cb6c1",
    opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"

3.9.2. Example with SHA-512-256, Charset, and Userhash

下面的示例假设通过 GET 请求从服务器请求一个受保护的文档。请求的 URI 为 "http://api.example.org/doe.json"。客户端和服务器都知道用户名的 userhash,支持 UTF-8 字符编码,并使用 SHA-512-256 算法。请求的用户名是 "Jason Doe" 的一个变体,其中 'a' 实际上是 Unicode 代码点 U+00E4("LATIN SMALL LETTER A WITH DIAERESIS"),第一个 'o' 是 Unicode 代码点 U+00F8("LATIN SMALL LETTER O WITH STROKE"),其使用 UTF-8 编码时的八位字节序列为:

   J  U+00E4 s  U+00F8 n      D  o  e
   4A C3A4   73 C3B8   6E 20 44  6F 65

密码为 "Secret, or not?"。

客户端首次请求该文档时未发送 Authorization 头字段,因此服务器响应:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest
    realm="api@example.org",
    qop="auth",
    algorithm=SHA-512-256,
    nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK",
    opaque="HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS",
    charset=UTF-8,
    userhash=true

客户端可以提示用户输入凭据并发送带有下列 Authorization 头字段的新请求:

Authorization: Digest
    username="488869477bf257147b804c45308cd62ac4e25eb717
       b12b298c79e62dcea254ec",
    realm="api@example.org",
    uri="/doe.json",
    algorithm=SHA-512-256,
    nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK",
    nc=00000001,
    cnonce="NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v",
    qop=auth,
    response="ae66e67d6b427bd3f120414a82e4acff38e8ecd9101d
       6c861229025f607a79dd",
    opaque="HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS",
    userhash=true

如果客户端因任何原因无法提供哈希用户名,客户端可以尝试使用下列 Authorization 头字段:

Authorization: Digest
    username*=UTF-8''J%C3%A4s%C3%B8n%20Doe,
    realm="api@example.org",
    uri="/doe.json",
    algorithm=SHA-512-256,
    nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK",
    nc=00000001,
    cnonce="NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v",
    qop=auth,
    response="ae66e67d6b427bd3f120414a82e4acff38e8ecd9101d
       6c861229025f607a79dd",
    opaque="HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS",
    userhash=false

4. 国际化注意事项

在挑战中,服务器 SHOULD 使用不区分大小写的 "charset" 认证参数来表达它们期望用户代理在生成 A1(见 第 3.4.2 节)和用户名哈希(见 第 3.4.4 节)时使用的字符编码。

唯一允许的值为 "UTF-8",匹配时不区分大小写(参见 RFC2978 第 2.3 节)。它表示服务器期望将用户名和密码转换为 Unicode 规范化形式 C("NFC",参见 RFC5198 第 3 节),然后使用 UTF-8 字符编码方案(参见 [RFC3629])将其编码为八位字节。

对于用户名,接收方 MUST 支持 [RFC7613] 第 3.3 节中定义的 "UsernameCasePreserved" 配置文件所定义的所有字符,但不能包含冒号(":")字符。

对于密码,接收方 MUST 支持 [RFC7613] 第 4.2 节中定义的 "OpaqueString" 配置文件所定义的所有字符。

如果用户代理不支持服务器所指示的编码,它可以使请求失败。

当用户名无法以哈希形式发送且包含非 ASCII 字符时,客户端可以改为包含 username* 参数(使用 [RFC5987] 中定义的值编码)。

5. 安全性考虑

5.1. 局限性

当使用人类易记的密码时,HTTP 摘要认证易受字典攻击。与对任何广泛使用的算法的密码学攻击相比,这类攻击要容易得多,包括那些已不再被视为安全的算法。换言之,算法的可替换性并不会使这种用法更安全。

因此,摘要认证 SHOULD 仅与具有合理熵(例如 128 位或更多)的密码一起使用。此类密码通常不能被人类记住,但可用于自动化的 Web 服务。

如果使用摘要认证,SHOULD 在像 HTTPS 这样的安全通道上进行(参见 [RFC2818])。

5.2. 密码存储

摘要认证要求认证代理(通常是服务器)在与给定 realm 关联的“密码文件”中存储一些从用户名和密码导出的数据。通常,这可能包含由用户名和 H(A1) 组成的对,其中 H(A1) 是如上所述对用户名、realm 和密码进行摘要得到的值。

其安全含义是,如果该密码文件被泄露,则攻击者将立即能够访问使用该 realm 的服务器上的文档。与标准 UNIX 密码文件不同,为了访问与该文件关联的服务器 realm 中的文档,不需要对这些信息进行解密。另一方面,要获取用户的密码则需要解密,或更可能的进行暴力破解攻击。这就是为何 realm 是存储在密码文件中的摘要数据的一部分。这意味着如果一个 Digest 认证密码文件被泄露,并不会自动危及具有相同用户名和密码的其他文件(尽管会暴露它们遭受暴力破解的风险)。

由此产生两个重要的安全后果。首先,该密码文件必须像包含未加密密码一样受到保护,因为就访问其 realm 中的文档而言,其效果等同于包含明文密码。

第二个后果是 realm 字符串 SHOULD 在任何单个用户可能使用的所有 realm 中保持唯一。特别地,realm 字符串SHOULD 包含执行认证的主机名。客户端无法对服务器进行认证是摘要认证的一个弱点。

5.3. 使用摘要认证的客户端认证

与基于公钥的机制相比,例如,摘要认证并不提供强认证机制。

然而,它显著强于例如 CRAM-MD5(曾被建议用于 LDAP(见 [RFC4513])和 IMAP/POP(见 [RFC2195]))。它旨在替代更弱且更危险的 Basic 机制。

摘要认证除了保护实际的用户名和密码之外,不提供机密性保护。请求和响应的其余部分对窃听者仍然可见。

摘要认证仅为消息的任一方向提供有限的完整性保护。如果使用 "qop=auth-int" 机制,则用于计算 WWW-Authenticate 和 Authorization 头字段 response 参数值的那些消息部分(见第 3.2 节)将受到保护。但大多数头字段及其值仍可能被中间人修改。

许多对安全 HTTP 事务的需求不能通过摘要认证来满足。对于这些需求,TLS 更为合适。特别地,摘要认证不能用于任何需要机密性保护的事务。尽管如此,摘要认证在许多场景下仍然有用且适当。

5.4. 有限使用的 nonce 值

摘要方案使用服务器指定的 nonce 来为 response 值的生成提供种子(如第 3.4.1 节 所述)。如第 3.3 节 中示例 nonce 所示,服务器可以构造 nonce,使其 MAY 仅可被特定客户端、针对特定资源、在有限时间或使用次数内或受其他限制地使用。这样可以加强对重放攻击(参见第 5.5 节)等的防护。然而应注意,所选的生成和校验 nonce 的方法也会带来性能和资源方面的影响。例如,服务器 MAY 选择仅允许每个 nonce 值被使用一次,通过记录最近发布的每个 nonce 是否已被返回并在每个响应的 Authentication-Info 头字段中发送 next-nonce 参数来实现。这可以防止即时重放攻击,但代价高昂,因为需要检查 nonce 值;更重要的是,这会导致任何流水线请求的认证失败(大概返回 stale nonce 指示)。同样,将请求特定元素(如资源的 ETag 值)并入 nonce,会将 nonce 的使用限制为该资源的该版本,同时也会破坏流水线。因此,对于有副作用的方法,这种做法 MAY 是有用的,但对于没有副作用的方法,其性能代价可能无法接受。

5.5. 重放攻击

针对摘要认证的重放攻击通常对简单的 GET 请求毫无意义,因为窃听者已经看到了通过重放能获取的唯一期望文档。这是因为请求的文档 URI 被包含在客户端请求的摘要中,服务器仅会交付该文档。相比之下,在 Basic 认证下,一旦窃听者获得了用户的密码,使用该密码保护的任何文档对其都是开放的。

因此,在某些场景下有必要防范重放攻击。良好的 Digest 实现可以通过多种方式做到这一点。服务器创建的 nonce 值是实现相关的,但如果它包含对客户端 IP、时间戳、资源 ETag 和服务器私密密钥的摘要(如上所建议),那么重放攻击并非易事。攻击者必须让服务器相信请求来自一个虚假的 IP 地址,并且必须使服务器向与其认为不同的地址交付文档。攻击只能在时间戳过期之前成功。将客户端 IP 与时间戳放入 nonce 并进行摘要,允许实施一个无需在事务间保持状态的实现。

对于不能容忍任何重放攻击的应用,服务器可以使用一次性 nonce 值,该值不允许第二次使用。这要求服务器记住哪些 nonce 值已被使用直到 nonce 时间戳(以及用其构建的摘要)过期,但它可有效防止重放攻击。

实现必须特别关注对 POST 和 PUT 请求的重放攻击可能性。除非服务器采用一次性或其他有限使用的 nonce 并/或坚持使用 "qop=auth-int" 的完整性保护,否则攻击者可能会使用伪造的数据或其它消息主体重放来自成功请求的有效凭据。即便使用完整性保护,大多数头字段的元数据也未被保护。适当的 nonce 生成与校验在某种程度上能防护对先前使用的有效凭据的重放,但请参见第 5.8 节

5.6. 由多重认证方案产生的弱点

HTTP/1.1 服务器MAY 在 401 响应中返回多个挑战,每个挑战MAY 使用不同的 auth-scheme。用户代理MUST 选择其理解的最强的 auth-scheme,并基于该挑战向用户请求凭据。

当服务器通过 WWW-Authenticate 头字段提供多种认证方案时,最终认证的强度仅与最弱认证方案的强度一样好。有关利用多重认证方案的特定攻击场景,请参见下面的第 5.7 节

5.7. 在线字典攻击

如果攻击者能够窃听,则它可以针对所窃听到的任何 nonce/response 对,用常用词列表测试它们。这样的列表通常远小于可能的所有密码数。对列表中每个密码计算 response 的成本对于每个挑战只需支付一次。

服务器可以通过不允许用户选择字典中存在的密码来缓解此类攻击。

5.8. 中间人攻击

摘要认证易受中间人(MITM)攻击,例如来自恶意或被攻陷的代理。这显然会带来窃听的所有问题,同时也为攻击者提供其他机会。

一种可能的中间人攻击是向可选方案集合中加入一个弱认证方案,希望客户端使用暴露用户凭据(例如密码)的方案。因此,客户端SHOULD 总是使用其理解的最强方案。

更恶劣的 MITM 攻击是移除所有提供的选择,替换为仅请求 Basic 认证的挑战,然后利用从 Basic 认证中获得的明文凭据去使用服务器原先请求的更强方案向源服务器认证。一种特别险恶的方式是向轻信的用户提供一个“免费”的代理缓存服务以实施此类攻击。

用户代理应考虑在凭据请求时显示一个可视指示,说明将使用何种认证方案,或记住服务器曾要求的最强认证方案并在要使用更弱方案时发出警告。也可以将用户代理配置为一般或针对特定站点要求使用 Digest 认证。

另外,恶意代理可能会欺骗客户端发出攻击者希望的请求,而不是客户端本意的请求。当然,这仍然比对 Basic 认证的类似攻击要难得多。

5.9. 选择明文攻击

在摘要认证中,MITM 或恶意服务器可以任意选择客户端用来计算 response 的 nonce。这被称为“选择明文”攻击。能够选择 nonce 会使密码分析更容易。

然而,目前尚无针对摘要所用单向函数的已知选择明文分析方法。

对抗此类攻击的措施是客户端使用 cnonce 参数;这允许客户端以非攻击者所选的方式改变哈希输入。

5.10. 预计算字典攻击

在摘要认证中,如果攻击者能执行选择明文攻击,则攻击者可以为许多常见词对其选择的 nonce 预计算 response,并存储一个 response/密码 字典。这样的预计算通常可以并行在多台机器上完成。然后它可以使用选择明文攻击获取与该挑战对应的 response 并在字典中查找密码。即便大多数密码不在字典中,也可能查到一些密码。由于攻击者可选挑战,对列表中每个密码计算 response 的成本可以摊薄到发现许多密码上。一个包含 1 亿 密码/response 对的字典大约需要 3.2 GB 磁盘存储。

对抗该攻击的措施是客户端使用 cnonce 参数。

5.11. 批量暴力破解攻击

在摘要认证中,MITM 可以执行选择明文攻击并收集来自许多用户针对同一 nonce 的 response。然后它可以在一次对密码空间的遍历中找到所有会生成这些 nonce/response 对中任一对的密码子集。它还将找到第一个密码的时间缩短为所收集 nonce/response 对数量的倒数倍。对此密码空间的搜索通常可以并行在多台机器上完成,即便单台机器也能非常快地搜索大子集——有报告称在几小时内搜索所有长度不超过 6 的字母密码。

对抗该攻击的措施是客户端使用 cnonce 参数。

5.12. 参数随机性

该协议的安全性在很大程度上依赖于随机选择参数(如客户端和服务器 nonce 等)的随机性。这些值应由强随机源或适当种子化的伪随机源生成(参见 [RFC4086])。

5.13. 摘要

按现代密码学标准,摘要认证是弱的。但对于很多用途,它作为替代 Basic 认证仍然有价值。它弥补了 Basic 认证的一些但不是全部弱点。其强度会随实现而异。特别是 nonce 的结构(依赖于服务器实现)可能影响实施重放攻击的难易程度。不同的服务器选项是合适的,例如一些实现可能愿意接受一次性 nonce 或摘要带来的服务器开销以消除重放的可能性;其他实现可能满足于如上推荐的 nonce,例如限制为单一 IP 地址和单一 ETag,或具有有限寿命。

结论是,任何合规实现从密码学角度都相对较弱,但相较于 Basic 认证,任何合规实现都要强得多。

6. IANA 注意事项

6.1. 用于 HTTP 摘要认证的哈希算法

本规范在现有的 "Hypertext Transfer Protocol (HTTP) Digest Algorithm Values" 类别下创建了一个名为 "Hash Algorithms for HTTP Digest Authentication" 的新 IANA 注册表。该注册表列出可在 HTTP 摘要认证中使用的哈希算法。

在注册新的哈希算法时,必须提供下列信息:

Hash Algorithm

  • 哈希算法的文本名称。

Digest Size

  • 该算法输出的大小(以位为单位)。

Reference

  • 指向将算法添加到该注册表的规范的参考。

该注册表的更新策略为 Specification Required(参见 [RFC5226])。

初始注册表包含以下条目:

Hash Algorithm Digest Size Reference
"MD5" 128 RFC 7616
"SHA-512-256" 256 RFC 7616
"SHA-256" 256 RFC 7616

注册表中定义的每一种算法可能都有一个 "-sess" 变体,例如 MD5-sess、SHA-256-sess 等。

为澄清现有 "HTTP Digest Algorithm Values" 注册表的用途并避免与新注册表混淆,IANA 已向现有的 "HTTP Digest Algorithm Values" 注册表添加了如下描述:

  • 该注册表列出可用于创建 HTTP 消息主体摘要的算法,如 RFC 3230 所述。

6.2. 摘要方案注册

本规范更新了 "Hypertext Transfer Protocol (HTTP) Authentication Scheme Registry" 中 Digest 方案的现有条目,并为该条目添加了指向本规范的新参考。

  • Authentication Scheme Name: Digest
  • Pointer to specification text: RFC 7616

7. 参考文献

7.1. 规范性参考文献

[RFC2119]
Bradner, S., “Key words for use in RFCs to Indicate Requirement Levels”, BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, <http://www.rfc-editor.org/info/rfc2119>.
[RFC2978]
Freed, N. and J. Postel, “IANA Charset Registration Procedures”, BCP 19, RFC 2978, DOI 10.17487/RFC2978, October 2000, <http://www.rfc-editor.org/info/rfc2978>.
[RFC3629]
Yergeau, F., “UTF-8, a transformation format of ISO 10646”, STD 63, RFC 3629, DOI 10.17487/RFC3629, November 2003, <http://www.rfc-editor.org/info/rfc3629>.
[RFC3986]
Berners-Lee, T., Fielding, R., and L. Masinter, “Uniform Resource Identifier (URI): Generic Syntax”, STD 66, RFC 3986, DOI 10.17487/RFC3986, January 2005, <http://www.rfc-editor.org/info/rfc3986>.
[RFC4086]
Eastlake 3rd, D., Schiller, J., and S. Crocker, “Randomness Requirements for Security”, BCP 106, RFC 4086, DOI 10.17487/RFC4086, June 2005, <http://www.rfc-editor.org/info/rfc4086>.
[RFC5198]
Klensin, J. and M. Padlipsky, “Unicode Format for Network Interchange”, RFC 5198, DOI 10.17487/RFC5198, March 2008, <http://www.rfc-editor.org/info/rfc5198>.
[RFC5234]
Crocker, D., Ed. and P. Overell, “Augmented BNF for Syntax Specifications: ABNF”, STD 68, RFC 5234, DOI 10.17487/RFC5234, January 2008, <http://www.rfc-editor.org/info/rfc5234>.
[RFC5987]
Reschke, J., “Character Set and Language Encoding for Hypertext Transfer Protocol (HTTP) Header Field Parameters”, RFC 5987, DOI 10.17487/RFC5987, August 2010, <http://www.rfc-editor.org/info/rfc5987>.
[RFC6454]
Barth, A., “The Web Origin Concept”, RFC 6454, DOI 10.17487/RFC6454, December 2011, <http://www.rfc-editor.org/info/rfc6454>.
[RFC7230]
Fielding, R., Ed. and J. Reschke, Ed., “Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing”, RFC 7230, DOI 10.17487/RFC7230, June 2014, <http://www.rfc-editor.org/info/rfc7230>.
[RFC7231]
Fielding, R., Ed. and J. Reschke, Ed., “Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content”, RFC 7231, DOI 10.17487/RFC7231, June 2014, <http://www.rfc-editor.org/info/rfc7231>.
[RFC7234]
Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., “Hypertext Transfer Protocol (HTTP/1.1): Caching”, RFC 7234, DOI 10.17487/RFC7234, June 2014, <http://www.rfc-editor.org/info/rfc7234>.
[RFC7235]
Fielding, R., Ed. and J. Reschke, Ed., “Hypertext Transfer Protocol (HTTP/1.1): Authentication”, RFC 7235, DOI 10.17487/RFC7235, June 2014, <http://www.rfc-editor.org/info/rfc7235>.
[RFC7613]
Saint-Andre, P. and A. Melnikov, “Preparation, Enforcement, and Comparison of Internationalized Strings Representing Usernames and Passwords”, RFC 7613, DOI 10.17487/RFC7613, August 2015, <http://www.rfc-editor.org/info/rfc7613>.
[RFC7615]
Reschke, J., “HTTP Authentication-Info and Proxy-Authentication-Info Response Header Fields”, RFC 7615, DOI 10.17487/RFC7615, September 2015, <http://www.rfc-editor.org/info/rfc7615>.

Appendix A. 来自 RFC 2617 的变更

本文档引入了以下变更:

  • 增加对两种新算法的支持:将 SHA2-256 设为必须实现,SHA2-512/256 作为备选,并定义了正确的算法协商。文档保留对 MD5 算法的支持,但仅用于向后兼容。
  • 引入用户名哈希功能及其相关参数,主要用于隐私考虑。
  • 增加若干影响 A1 计算及用户名和密码编码的国际化注意事项。
  • 引入新的 IANA 注册表 “Hash Algorithms for HTTP Digest Authentication”,列出可用于 HTTP 摘要认证的哈希算法。
  • 弃用与 RFC 2069 的向后兼容性。

Appendix B. 致谢

为了完整描述 Digest 机制及其操作,本文大量借用自 [RFC2617] 的文本。本文作者感谢 John Franks、Phillip M. Hallam-Baker, Jeffery L. Hostetler, Scott D. Lawrence, Paul J. Leach, Ari Luotonen, 以及 Lawrence C. Stewart 为该规范所做的工作。

特别感谢 Julian Reschke 对本文档的多次审阅、评论、建议和提供的文本。

作者感谢 Stephen Farrell、Yoav Nir、Phillip Hallam-Baker、Manu Sporny、Paul Hoffman、Yaron Sheffer、Sean Turner、Geoff Baskwill、Eric Cooper、Bjoern Hoehrmann、Martin Durst、Peter Saint-Andre、Michael Sweet、Daniel Stenberg、Brett Tate、Paul Leach、Ilari Liusvaara、Gary Mort、Alexey Melnikov、Benjamin Kaduk、Kathleen Moriarty、Francis Dupont、Hilarie Orman 和 Ben Campbell 的细致审阅与意见。

作者感谢 Jonathan Stoke、Nico Williams、Harry Halpin 和 Phil Hunt 在邮件列表讨论本文档各方面时提供的意见。

作者感谢 Paul Kyzivat 和 Dale Worley 对本文档某些方面进行的细致审阅和反馈。

作者感谢 Barry Leiba 在注册表相关工作上的帮助。

作者地址

Rifaat Shekh-Yusef (editor)
Avaya
250 Sidney Street
Belleville, Ontario
Canada
Phone: +1-613-967-5267
EMail: rifaat.ietf@gmail.com
David Ahrens
Independent
California
United States
EMail: ahrensdc@gmail.com
Sophie Bremer
Netzkonform
Germany
EMail: sophie.bremer@netzkonform.de