Skip to main content

sa_token_core/oauth2/
pkce.rs

1// Author: 金书记 | Author: Jin Shuji
2//! PKCE (RFC 7636): S256 and plain.
3//! PKCE(RFC 7636):S256 与 plain。
4
5use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8
9use crate::error::{SaTokenError, SaTokenResult};
10use crate::http_basic::ct_eq;
11
12/// code_challenge_method.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum CodeChallengeMethod {
16    /// SHA-256 then URL-safe Base64 (no pad). Recommended.
17    /// SHA-256 后 URL-safe Base64(无填充)。推荐。
18    S256,
19    /// Verifier equals challenge. Disallowed for public clients.
20    /// verifier 等于 challenge。公共客户端禁止。
21    Plain,
22}
23
24/// Challenge stored on the authorization code.
25/// 存在授权码上的挑战。
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct PkceChallenge {
28    /// Challenge string from the client.
29    /// 客户端提交的 challenge 串。
30    pub code_challenge: String,
31    /// Challenge method (S256 / plain).
32    /// 挑战方法(S256 / plain)。
33    pub code_challenge_method: CodeChallengeMethod,
34}
35
36impl PkceChallenge {
37    /// Build an S256 challenge from a code_verifier.
38    /// 由 code_verifier 构造 S256 challenge。
39    pub fn from_verifier_s256(code_verifier: &str) -> SaTokenResult<Self> {
40        Self::validate_verifier_len(code_verifier)?;
41        let digest = Sha256::digest(code_verifier.as_bytes());
42        Ok(Self {
43            code_challenge: URL_SAFE_NO_PAD.encode(digest),
44            code_challenge_method: CodeChallengeMethod::S256,
45        })
46    }
47
48    fn validate_verifier_len(code_verifier: &str) -> SaTokenResult<()> {
49        if !(43..=128).contains(&code_verifier.len()) {
50            return Err(SaTokenError::OAuth2PkceMismatch);
51        }
52        Ok(())
53    }
54
55    /// Verify token-endpoint `code_verifier`.
56    /// 校验 token 端提交的 `code_verifier`。
57    pub fn verify(&self, code_verifier: &str) -> SaTokenResult<()> {
58        Self::validate_verifier_len(code_verifier)?;
59        let computed = match self.code_challenge_method {
60            CodeChallengeMethod::S256 => {
61                let digest = Sha256::digest(code_verifier.as_bytes());
62                URL_SAFE_NO_PAD.encode(digest)
63            }
64            CodeChallengeMethod::Plain => code_verifier.to_string(),
65        };
66        if ct_eq(computed.as_bytes(), self.code_challenge.as_bytes()) {
67            Ok(())
68        } else {
69            Err(SaTokenError::OAuth2PkceMismatch)
70        }
71    }
72}