TLA Line data Source code
1 : //! Tools for SCRAM server secret management.
2 :
3 : use super::base64_decode_array;
4 : use super::key::ScramKey;
5 :
6 : /// Server secret is produced from [password](super::password::SaltedPassword)
7 : /// and is used throughout the authentication process.
8 CBC 37 : #[derive(Clone)]
9 : pub struct ServerSecret {
10 : /// Number of iterations for `PBKDF2` function.
11 : pub iterations: u32,
12 : /// Salt used to hash user's password.
13 : pub salt_base64: String,
14 : /// Hashed `ClientKey`.
15 : pub stored_key: ScramKey,
16 : /// Used by client to verify server's signature.
17 : pub server_key: ScramKey,
18 : /// Should auth fail no matter what?
19 : /// This is exactly the case for mocked secrets.
20 : pub doomed: bool,
21 : }
22 :
23 : impl ServerSecret {
24 119 : pub fn parse(input: &str) -> Option<Self> {
25 : // SCRAM-SHA-256$<iterations>:<salt>$<storedkey>:<serverkey>
26 119 : let s = input.strip_prefix("SCRAM-SHA-256$")?;
27 119 : let (params, keys) = s.split_once('$')?;
28 :
29 119 : let ((iterations, salt), (stored_key, server_key)) =
30 119 : params.split_once(':').zip(keys.split_once(':'))?;
31 :
32 119 : let secret = ServerSecret {
33 119 : iterations: iterations.parse().ok()?,
34 119 : salt_base64: salt.to_owned(),
35 119 : stored_key: base64_decode_array(stored_key)?.into(),
36 119 : server_key: base64_decode_array(server_key)?.into(),
37 119 : doomed: false,
38 119 : };
39 119 :
40 119 : Some(secret)
41 119 : }
42 :
43 : /// To avoid revealing information to an attacker, we use a
44 : /// mocked server secret even if the user doesn't exist.
45 : /// See `auth-scram.c : mock_scram_secret` for details.
46 2 : pub fn mock(user: &str, nonce: [u8; 32]) -> Self {
47 2 : // Refer to `auth-scram.c : scram_mock_salt`.
48 2 : let mocked_salt = super::sha256([user.as_bytes(), &nonce]);
49 2 :
50 2 : Self {
51 2 : iterations: 4096,
52 2 : salt_base64: base64::encode(mocked_salt),
53 2 : stored_key: ScramKey::default(),
54 2 : server_key: ScramKey::default(),
55 2 : doomed: true,
56 2 : }
57 2 : }
58 :
59 : /// Build a new server secret from the prerequisites.
60 : /// XXX: We only use this function in tests.
61 : #[cfg(test)]
62 12 : pub fn build(password: &str, salt: &[u8], iterations: u32) -> Option<Self> {
63 12 : // TODO: implement proper password normalization required by the RFC
64 12 : if !password.is_ascii() {
65 UBC 0 : return None;
66 CBC 12 : }
67 12 :
68 12 : let password = super::password::SaltedPassword::new(password.as_bytes(), salt, iterations);
69 12 :
70 12 : Some(Self {
71 12 : iterations,
72 12 : salt_base64: base64::encode(salt),
73 12 : stored_key: password.client_key().sha256(),
74 12 : server_key: password.server_key(),
75 12 : doomed: false,
76 12 : })
77 12 : }
78 : }
79 :
80 : #[cfg(test)]
81 : mod tests {
82 : use super::*;
83 :
84 1 : #[test]
85 1 : fn parse_scram_secret() {
86 1 : let iterations = 4096;
87 1 : let salt = "+/tQQax7twvwTj64mjBsxQ==";
88 1 : let stored_key = "D5h6KTMBlUvDJk2Y8ELfC1Sjtc6k9YHjRyuRZyBNJns=";
89 1 : let server_key = "Pi3QHbcluX//NDfVkKlFl88GGzlJ5LkyPwcdlN/QBvI=";
90 1 :
91 1 : let secret = format!(
92 1 : "SCRAM-SHA-256${iterations}:{salt}${stored_key}:{server_key}",
93 1 : iterations = iterations,
94 1 : salt = salt,
95 1 : stored_key = stored_key,
96 1 : server_key = server_key,
97 1 : );
98 1 :
99 1 : let parsed = ServerSecret::parse(&secret).unwrap();
100 1 : assert_eq!(parsed.iterations, iterations);
101 1 : assert_eq!(parsed.salt_base64, salt);
102 :
103 1 : assert_eq!(base64::encode(parsed.stored_key), stored_key);
104 1 : assert_eq!(base64::encode(parsed.server_key), server_key);
105 1 : }
106 :
107 1 : #[test]
108 1 : fn build_scram_secret() {
109 1 : let salt = b"salt";
110 1 : let secret = ServerSecret::build("password", salt, 4096).unwrap();
111 1 : assert_eq!(secret.iterations, 4096);
112 1 : assert_eq!(secret.salt_base64, base64::encode(salt));
113 1 : assert_eq!(
114 1 : base64::encode(secret.stored_key.as_ref()),
115 1 : "lF4cRm/Jky763CN4HtxdHnjV4Q8AWTNlKvGmEFFU8IQ="
116 1 : );
117 1 : assert_eq!(
118 1 : base64::encode(secret.server_key.as_ref()),
119 1 : "ub8OgRsftnk2ccDMOt7ffHXNcikRkQkq1lh4xaAqrSw="
120 1 : );
121 1 : }
122 : }
|