PKCE (Proof Key for Code Exchange)代码交换的证明密钥

使用授权码授权的 OAuth 2.0 公共客户端容易受到授权码截取攻击。PKCE 是来减轻这种威胁的技术。

公共客户端指的是无法维持其秘钥机密性的客户端。比如手机 APP、桌面应用或者纯浏览器应用(不含服务器,秘钥可网站源码放在一起的),这些应用很可能受到攻击进而导致秘钥的泄露(比如通过反编译、浏览器控制台查看网站的源码等手段)。

授权码授权的流程

 +----------+
 | Resource |
 |   Owner  |
 |          |
 +----------+
      ^
      |
     (B)
 +----|-----+          Client Identifier      +---------------+
 |         -+----(A)-- & Redirection URI ---->|               |
 |  User-   |                                 | Authorization |
 |  Agent  -+----(B)-- User authenticates --->|     Server    |
 |          |                                 |               |
 |         -+----(C)-- Authorization Code ---<|               |
 +-|----|---+                                 +---------------+
   |    |                                         ^      v
  (A)  (C)                                        |      |
   |    |                                         |      |
   ^    v                                         |      |
 +---------+                                      |      |
 |         |>---(D)-- Authorization Code ---------'      |
 |  Client |          & Redirection URI                  |
 |         |                                             |
 |         |<---(E)----- Access Token -------------------'
 +---------+       (w/ Optional Refresh Token)
  • (A)客户端将资源所有者引导到授权端点进行授权,参数有客户端标识 client_id、请求范围 scope、本地状态 state 和重定向URI redirect_uri 等,授权完成后授权服务器将浏览器重定向回该 URL。redirect_uri 可以是自定义的 schame,比如 app-name://xxx,也可以是本地启动一个的服务器,比如 http://localhost:{port}/xxx

  • (B) 资源拥有者进行登录、授权。

  • (C)授权服务器将浏览器重定向回(在请求时或客户端注册时)提供的重定向 URI,并且带有授权码 code 和之前客户端提供的本地状态 state

  • (D)客户端使用授权码 code 向授权服务器的令牌端点请求访问令牌,必须包含用于获得授权码的重定向 URI 来用于验证。

  • (E)授权服务器验证客户端的身份,并用接受到的重定向 URI 与(C)中的重定向 URI 验证是否匹配,最终授予访问令牌与可选的刷新令牌。

授权码截取攻击

授权码拦截攻击发送在上述流程中的 (C)中,虽然 OAuth 2.0 规定授权服务器的授权端点、令牌端点都必须是传输层安全(TLS)的。但是授权完成后的重定向地址,可能不是传输层安全的,例如 app-name://xxxhttp://localhost:{port}/xxx。攻击者可以拦截授权端点返回的授权代码。同时公共客户端无法维持其秘钥的机密性,可以假设攻击者能拿到 client_idclient_secret 等。这时攻击者已经拿到了调用令牌端点的所有参数:code、redirect_uri、client_id、client_secret,可以使用它们来获得访问令牌。

PKCE 协议流程

                                                 +-------------------+
                                                 |   Authz Server    |
       +--------+                                | +---------------+ |
       |        |--(A)- Authorization Request ---->|               | |
       |        |       + t(code_verifier), t_m  | | Authorization | |
       |        |                                | |    Endpoint   | |
       |        |<-(B)---- Authorization Code -----|               | |
       |        |                                | +---------------+ |
       | Client |                                |                   |
       |        |                                | +---------------+ |
       |        |--(C)-- Access Token Request ---->|               | |
       |        |          + code_verifier       | |    Token      | |
       |        |                                | |   Endpoint    | |
       |        |<-(D)------ Access Token ---------|               | |
       +--------+                                | +---------------+ |
                                                 +-------------------+
  • A. 客户端生成一个随机字符串作为 code_verifier,使用 code_challenge_method t_m 将 code_verifier 转换成 code_challenge t(code_verifier)。然后按照授权码授权的流程中的(A)向授权端点发起请求,只是多了两个参数 code_challengecode_challenge_method

  • B. 授权服务器需要将 code_challengecode_challenge_methodcodeclient_id 关联并保存起来,其他与授权码授权的流程中的(B)(C)类似,授权成功后将 code 给到客户端。

  • C. 客户端向令牌端点请求访问令牌。除了授权码授权的流程中的(D)的参数外,还要包含 A 中生成的 code_verifier

  • D. 授权服务器根据令牌端点的请求参数查询出 B 中存储的 code_challengecode_challenge_method,执行转换 code_challenge_method(code_verifier) 得到 code_challenge',如果 code_challengecode_challenge' 不相等则拒绝请求。

经过上面的流程后,即使攻击者能拦截到 B 的 code,也无法使用其兑换访问令牌,因为攻击者没有 code_verifier

协议细节

1 客户端创建 Code Verifier

code_verifier 是一个高熵加密的随机字符串,由 [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" 构成,最小长度为 43 个字符,最大长度为 128 个字符。

2 客户端创建 Code Challenge

以下是两种 code_challenge_method 下的 code_verifier 转换成 code_challenge 的公式:

plain
code_challenge = code_verifier

S256
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

如果客户端能使用 “S256”,则必须使用 “S256”,因为服务器强制使用 “S256”。只有在客户端不支持 “S256”,且通过外带配置知道服务端支持 “plain” 时,才允许客户端使用 “plain”。

3 客户端通过授权请求发送 Code Challenge

客户端使用以下附加参数将 code challenge 作为 OAuth 2.0 授权请求的一部分发送:

code_challenge
必需。 Code challenge.

code_challenge_method
可选,,若未在请求中指定,默认为 “plain”。 可选 “S256” 或 ”plain”。

4 服务器返回 code

当服务器在授权响应中发出授权码时,它必须将 code_challengecode_challenge_method 的值与授权码关联起来,以便以后可以验证。

通常,code_challengecode_challenge_method 值以加密形式存储在 code 本身中,但也可以存储在与该 code 关联的服务器上。 服务器不得以其他实体可以提取的形式在客户端请求中包含 code_challenge 值。

若客户端未在授权请求中发送 code_challenge,或者客户端发送了不支持的 code_challenge_method,授权端点必须返回授权错误响应,error 的值为 invalid_requesterror_descriptionerror_uri 应说明错误的性质。

5 客户端发送 Code 和 Code Verifier 到令牌端点

客户端向令牌端点发送访问令牌请求时,除了 OAuth 2.0 访问令牌请求中定义的参数外,它还会发送以下参数:

code_verifier
必需. Code verifier

6 服务器在返回访问令牌之前验证 code_verifier

令牌端点收到请求后,服务器会现通过先前关联的 code_challenge_methodcode_verifier 转换为 code_challenge,然后将新的 code_challenge 与先前关联的 code_challenge 比较。

如果 code_challenge_method 为 S256:

BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) == code_challenge

如果 code_challenge_method 为 plain:

code_verifier == code_challenge

如果两个值不相等,则返回 invalid_grant 的响应。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值