LCOV - differential code coverage report
Current view: top level - proxy/src/scram - secret.rs (source / functions) Coverage Total Hit UBC CBC
Current: cd44433dd675caa99df17a61b18949c8387e2242.info Lines: 98.8 % 80 79 1 79
Current Date: 2024-01-09 02:06:09 Functions: 100.0 % 8 8 8
Baseline: 66c52a629a0f4a503e193045e0df4c77139e344b.info
Baseline Date: 2024-01-08 15:34:46

           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                 : }
        

Generated by: LCOV version 2.1-beta