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