LCOV - code coverage report
Current view: top level - proxy/src/scram - secret.rs (source / functions) Coverage Total Hit
Test: 32f4a56327bc9da697706839ed4836b2a00a408f.info Lines: 98.8 % 80 79
Test Date: 2024-02-07 07:37:29 Functions: 90.0 % 10 9

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

Generated by: LCOV version 2.1-beta