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 user's password,
7 : /// and is used throughout the authentication process.
8 24 : #[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 30 : pub fn parse(input: &str) -> Option<Self> {
25 : // SCRAM-SHA-256$<iterations>:<salt>$<storedkey>:<serverkey>
26 30 : let s = input.strip_prefix("SCRAM-SHA-256$")?;
27 30 : let (params, keys) = s.split_once('$')?;
28 :
29 30 : let ((iterations, salt), (stored_key, server_key)) =
30 30 : params.split_once(':').zip(keys.split_once(':'))?;
31 :
32 30 : let secret = ServerSecret {
33 30 : iterations: iterations.parse().ok()?,
34 30 : salt_base64: salt.to_owned(),
35 30 : stored_key: base64_decode_array(stored_key)?.into(),
36 30 : server_key: base64_decode_array(server_key)?.into(),
37 30 : doomed: false,
38 30 : };
39 30 :
40 30 : Some(secret)
41 30 : }
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 14 : pub fn mock(user: &str, nonce: [u8; 32]) -> Self {
47 14 : // Refer to `auth-scram.c : scram_mock_salt`.
48 14 : let mocked_salt = super::sha256([user.as_bytes(), &nonce]);
49 14 :
50 14 : Self {
51 14 : iterations: 4096,
52 14 : salt_base64: base64::encode(mocked_salt),
53 14 : stored_key: ScramKey::default(),
54 14 : server_key: ScramKey::default(),
55 14 : doomed: true,
56 14 : }
57 14 : }
58 :
59 : /// Build a new server secret from the prerequisites.
60 : /// XXX: We only use this function in tests.
61 : #[cfg(test)]
62 26 : pub fn build(password: &str) -> Option<Self> {
63 26 : Self::parse(&postgres_protocol::password::scram_sha_256(
64 26 : password.as_bytes(),
65 26 : ))
66 26 : }
67 : }
68 :
69 : #[cfg(test)]
70 : mod tests {
71 : use super::*;
72 :
73 2 : #[test]
74 2 : fn parse_scram_secret() {
75 2 : let iterations = 4096;
76 2 : let salt = "+/tQQax7twvwTj64mjBsxQ==";
77 2 : let stored_key = "D5h6KTMBlUvDJk2Y8ELfC1Sjtc6k9YHjRyuRZyBNJns=";
78 2 : let server_key = "Pi3QHbcluX//NDfVkKlFl88GGzlJ5LkyPwcdlN/QBvI=";
79 2 :
80 2 : let secret = format!(
81 2 : "SCRAM-SHA-256${iterations}:{salt}${stored_key}:{server_key}",
82 2 : iterations = iterations,
83 2 : salt = salt,
84 2 : stored_key = stored_key,
85 2 : server_key = server_key,
86 2 : );
87 2 :
88 2 : let parsed = ServerSecret::parse(&secret).unwrap();
89 2 : assert_eq!(parsed.iterations, iterations);
90 2 : assert_eq!(parsed.salt_base64, salt);
91 :
92 2 : assert_eq!(base64::encode(parsed.stored_key), stored_key);
93 2 : assert_eq!(base64::encode(parsed.server_key), server_key);
94 2 : }
95 : }
|