LCOV - code coverage report
Current view: top level - proxy/src/scram - secret.rs (source / functions) Coverage Total Hit
Test: e402c46de0a007db6b48dddbde450ddbb92e6ceb.info Lines: 100.0 % 54 54
Test Date: 2024-06-25 10:31:23 Functions: 100.0 % 6 6

            Line data    Source code
       1              : //! Tools for SCRAM server secret management.
       2              : 
       3              : use subtle::{Choice, ConstantTimeEq};
       4              : 
       5              : use super::base64_decode_array;
       6              : use super::key::ScramKey;
       7              : 
       8              : /// Server secret is produced from user's password,
       9              : /// and is used throughout the authentication process.
      10              : #[derive(Clone, Eq, PartialEq, Debug)]
      11              : pub struct ServerSecret {
      12              :     /// Number of iterations for `PBKDF2` function.
      13              :     pub iterations: u32,
      14              :     /// Salt used to hash user's password.
      15              :     pub salt_base64: String,
      16              :     /// Hashed `ClientKey`.
      17              :     pub stored_key: ScramKey,
      18              :     /// Used by client to verify server's signature.
      19              :     pub server_key: ScramKey,
      20              :     /// Should auth fail no matter what?
      21              :     /// This is exactly the case for mocked secrets.
      22              :     pub doomed: bool,
      23              : }
      24              : 
      25              : impl ServerSecret {
      26           36 :     pub fn parse(input: &str) -> Option<Self> {
      27              :         // SCRAM-SHA-256$<iterations>:<salt>$<storedkey>:<serverkey>
      28           36 :         let s = input.strip_prefix("SCRAM-SHA-256$")?;
      29           36 :         let (params, keys) = s.split_once('$')?;
      30              : 
      31           36 :         let ((iterations, salt), (stored_key, server_key)) =
      32           36 :             params.split_once(':').zip(keys.split_once(':'))?;
      33              : 
      34           36 :         let secret = ServerSecret {
      35           36 :             iterations: iterations.parse().ok()?,
      36           36 :             salt_base64: salt.to_owned(),
      37           36 :             stored_key: base64_decode_array(stored_key)?.into(),
      38           36 :             server_key: base64_decode_array(server_key)?.into(),
      39           36 :             doomed: false,
      40           36 :         };
      41           36 : 
      42           36 :         Some(secret)
      43           36 :     }
      44              : 
      45           24 :     pub fn is_password_invalid(&self, client_key: &ScramKey) -> Choice {
      46           24 :         // constant time to not leak partial key match
      47           24 :         client_key.sha256().ct_ne(&self.stored_key) | Choice::from(self.doomed as u8)
      48           24 :     }
      49              : 
      50              :     /// To avoid revealing information to an attacker, we use a
      51              :     /// mocked server secret even if the user doesn't exist.
      52              :     /// See `auth-scram.c : mock_scram_secret` for details.
      53           14 :     pub fn mock(nonce: [u8; 32]) -> Self {
      54           14 :         Self {
      55           14 :             // this doesn't reveal much information as we're going to use
      56           14 :             // iteration count 1 for our generated passwords going forward.
      57           14 :             // PG16 users can set iteration count=1 already today.
      58           14 :             iterations: 1,
      59           14 :             salt_base64: base64::encode(nonce),
      60           14 :             stored_key: ScramKey::default(),
      61           14 :             server_key: ScramKey::default(),
      62           14 :             doomed: true,
      63           14 :         }
      64           14 :     }
      65              : 
      66              :     /// Build a new server secret from the prerequisites.
      67              :     /// XXX: We only use this function in tests.
      68              :     #[cfg(test)]
      69           32 :     pub async fn build(password: &str) -> Option<Self> {
      70           96 :         Self::parse(&postgres_protocol::password::scram_sha_256(password.as_bytes()).await)
      71           32 :     }
      72              : }
      73              : 
      74              : #[cfg(test)]
      75              : mod tests {
      76              :     use super::*;
      77              : 
      78              :     #[test]
      79            2 :     fn parse_scram_secret() {
      80            2 :         let iterations = 4096;
      81            2 :         let salt = "+/tQQax7twvwTj64mjBsxQ==";
      82            2 :         let stored_key = "D5h6KTMBlUvDJk2Y8ELfC1Sjtc6k9YHjRyuRZyBNJns=";
      83            2 :         let server_key = "Pi3QHbcluX//NDfVkKlFl88GGzlJ5LkyPwcdlN/QBvI=";
      84            2 : 
      85            2 :         let secret = format!(
      86            2 :             "SCRAM-SHA-256${iterations}:{salt}${stored_key}:{server_key}",
      87            2 :             iterations = iterations,
      88            2 :             salt = salt,
      89            2 :             stored_key = stored_key,
      90            2 :             server_key = server_key,
      91            2 :         );
      92            2 : 
      93            2 :         let parsed = ServerSecret::parse(&secret).unwrap();
      94            2 :         assert_eq!(parsed.iterations, iterations);
      95            2 :         assert_eq!(parsed.salt_base64, salt);
      96              : 
      97            2 :         assert_eq!(base64::encode(parsed.stored_key), stored_key);
      98            2 :         assert_eq!(base64::encode(parsed.server_key), server_key);
      99            2 :     }
     100              : }
        

Generated by: LCOV version 2.1-beta