LCOV - code coverage report
Current view: top level - proxy/src/scram - secret.rs (source / functions) Coverage Total Hit
Test: 8ac049b474321fdc72ddcb56d7165153a1a900e8.info Lines: 98.7 % 79 78
Test Date: 2023-09-06 10:18:01 Functions: 100.0 % 7 7

            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           25 :     pub fn parse(input: &str) -> Option<Self> {
      24              :         // SCRAM-SHA-256$<iterations>:<salt>$<storedkey>:<serverkey>
      25           25 :         let s = input.strip_prefix("SCRAM-SHA-256$")?;
      26           25 :         let (params, keys) = s.split_once('$')?;
      27              : 
      28           25 :         let ((iterations, salt), (stored_key, server_key)) =
      29           25 :             params.split_once(':').zip(keys.split_once(':'))?;
      30              : 
      31           25 :         let secret = ServerSecret {
      32           25 :             iterations: iterations.parse().ok()?,
      33           25 :             salt_base64: salt.to_owned(),
      34           25 :             stored_key: base64_decode_array(stored_key)?.into(),
      35           25 :             server_key: base64_decode_array(server_key)?.into(),
      36           25 :             doomed: false,
      37           25 :         };
      38           25 : 
      39           25 :         Some(secret)
      40           25 :     }
      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            0 :             return None;
      65            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              : }
        

Generated by: LCOV version 2.1-beta