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 62 : #[derive(Clone, Eq, PartialEq, Debug)]
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 123 : pub fn parse(input: &str) -> Option<Self> {
25 : // SCRAM-SHA-256$<iterations>:<salt>$<storedkey>:<serverkey>
26 123 : let s = input.strip_prefix("SCRAM-SHA-256$")?;
27 123 : let (params, keys) = s.split_once('$')?;
28 :
29 123 : let ((iterations, salt), (stored_key, server_key)) =
30 123 : params.split_once(':').zip(keys.split_once(':'))?;
31 :
32 123 : let secret = ServerSecret {
33 123 : iterations: iterations.parse().ok()?,
34 123 : salt_base64: salt.to_owned(),
35 123 : stored_key: base64_decode_array(stored_key)?.into(),
36 123 : server_key: base64_decode_array(server_key)?.into(),
37 123 : doomed: false,
38 123 : };
39 123 :
40 123 : Some(secret)
41 123 : }
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 15 : pub fn mock(user: &str, nonce: [u8; 32]) -> Self {
47 15 : // Refer to `auth-scram.c : scram_mock_salt`.
48 15 : let mocked_salt = super::sha256([user.as_bytes(), &nonce]);
49 15 :
50 15 : Self {
51 15 : iterations: 4096,
52 15 : salt_base64: base64::encode(mocked_salt),
53 15 : stored_key: ScramKey::default(),
54 15 : server_key: ScramKey::default(),
55 15 : doomed: true,
56 15 : }
57 15 : }
58 :
59 : /// Build a new server secret from the prerequisites.
60 : /// XXX: We only use this function in tests.
61 : #[cfg(test)]
62 24 : pub fn build(password: &str, salt: &[u8], iterations: u32) -> Option<Self> {
63 24 : // TODO: implement proper password normalization required by the RFC
64 24 : if !password.is_ascii() {
65 0 : return None;
66 24 : }
67 24 :
68 24 : let password = super::password::SaltedPassword::new(password.as_bytes(), salt, iterations);
69 24 :
70 24 : Some(Self {
71 24 : iterations,
72 24 : salt_base64: base64::encode(salt),
73 24 : stored_key: password.client_key().sha256(),
74 24 : server_key: password.server_key(),
75 24 : doomed: false,
76 24 : })
77 24 : }
78 : }
79 :
80 : #[cfg(test)]
81 : mod tests {
82 : use super::*;
83 :
84 2 : #[test]
85 2 : fn parse_scram_secret() {
86 2 : let iterations = 4096;
87 2 : let salt = "+/tQQax7twvwTj64mjBsxQ==";
88 2 : let stored_key = "D5h6KTMBlUvDJk2Y8ELfC1Sjtc6k9YHjRyuRZyBNJns=";
89 2 : let server_key = "Pi3QHbcluX//NDfVkKlFl88GGzlJ5LkyPwcdlN/QBvI=";
90 2 :
91 2 : let secret = format!(
92 2 : "SCRAM-SHA-256${iterations}:{salt}${stored_key}:{server_key}",
93 2 : iterations = iterations,
94 2 : salt = salt,
95 2 : stored_key = stored_key,
96 2 : server_key = server_key,
97 2 : );
98 2 :
99 2 : let parsed = ServerSecret::parse(&secret).unwrap();
100 2 : assert_eq!(parsed.iterations, iterations);
101 2 : assert_eq!(parsed.salt_base64, salt);
102 :
103 2 : assert_eq!(base64::encode(parsed.stored_key), stored_key);
104 2 : assert_eq!(base64::encode(parsed.server_key), server_key);
105 2 : }
106 :
107 2 : #[test]
108 2 : fn build_scram_secret() {
109 2 : let salt = b"salt";
110 2 : let secret = ServerSecret::build("password", salt, 4096).unwrap();
111 2 : assert_eq!(secret.iterations, 4096);
112 2 : assert_eq!(secret.salt_base64, base64::encode(salt));
113 2 : assert_eq!(
114 2 : base64::encode(secret.stored_key.as_ref()),
115 2 : "lF4cRm/Jky763CN4HtxdHnjV4Q8AWTNlKvGmEFFU8IQ="
116 2 : );
117 2 : assert_eq!(
118 2 : base64::encode(secret.server_key.as_ref()),
119 2 : "ub8OgRsftnk2ccDMOt7ffHXNcikRkQkq1lh4xaAqrSw="
120 2 : );
121 2 : }
122 : }
|