LCOV - code coverage report
Current view: top level - proxy/src/scram - secret.rs (source / functions) Coverage Total Hit
Test: 20b6afc7b7f34578dcaab2b3acdaecfe91cd8bf1.info Lines: 100.0 % 48 48
Test Date: 2024-11-25 17:48:16 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(crate) struct ServerSecret {
      12              :     /// Number of iterations for `PBKDF2` function.
      13              :     pub(crate) iterations: u32,
      14              :     /// Salt used to hash user's password.
      15              :     pub(crate) salt_base64: String,
      16              :     /// Hashed `ClientKey`.
      17              :     pub(crate) stored_key: ScramKey,
      18              :     /// Used by client to verify server's signature.
      19              :     pub(crate) server_key: ScramKey,
      20              :     /// Should auth fail no matter what?
      21              :     /// This is exactly the case for mocked secrets.
      22              :     pub(crate) doomed: bool,
      23              : }
      24              : 
      25              : impl ServerSecret {
      26           18 :     pub(crate) fn parse(input: &str) -> Option<Self> {
      27              :         // SCRAM-SHA-256$<iterations>:<salt>$<storedkey>:<serverkey>
      28           18 :         let s = input.strip_prefix("SCRAM-SHA-256$")?;
      29           18 :         let (params, keys) = s.split_once('$')?;
      30              : 
      31           18 :         let ((iterations, salt), (stored_key, server_key)) =
      32           18 :             params.split_once(':').zip(keys.split_once(':'))?;
      33              : 
      34           18 :         let secret = ServerSecret {
      35           18 :             iterations: iterations.parse().ok()?,
      36           18 :             salt_base64: salt.to_owned(),
      37           18 :             stored_key: base64_decode_array(stored_key)?.into(),
      38           18 :             server_key: base64_decode_array(server_key)?.into(),
      39           18 :             doomed: false,
      40           18 :         };
      41           18 : 
      42           18 :         Some(secret)
      43           18 :     }
      44              : 
      45           12 :     pub(crate) fn is_password_invalid(&self, client_key: &ScramKey) -> Choice {
      46           12 :         // constant time to not leak partial key match
      47           12 :         client_key.sha256().ct_ne(&self.stored_key) | Choice::from(self.doomed as u8)
      48           12 :     }
      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            7 :     pub(crate) fn mock(nonce: [u8; 32]) -> Self {
      54            7 :         Self {
      55            7 :             // this doesn't reveal much information as we're going to use
      56            7 :             // iteration count 1 for our generated passwords going forward.
      57            7 :             // PG16 users can set iteration count=1 already today.
      58            7 :             iterations: 1,
      59            7 :             salt_base64: base64::encode(nonce),
      60            7 :             stored_key: ScramKey::default(),
      61            7 :             server_key: ScramKey::default(),
      62            7 :             doomed: true,
      63            7 :         }
      64            7 :     }
      65              : 
      66              :     /// Build a new server secret from the prerequisites.
      67              :     /// XXX: We only use this function in tests.
      68              :     #[cfg(test)]
      69           16 :     pub(crate) async fn build(password: &str) -> Option<Self> {
      70           48 :         Self::parse(&postgres_protocol::password::scram_sha_256(password.as_bytes()).await)
      71           16 :     }
      72              : }
      73              : 
      74              : #[cfg(test)]
      75              : mod tests {
      76              :     use super::*;
      77              : 
      78              :     #[test]
      79            1 :     fn parse_scram_secret() {
      80            1 :         let iterations = 4096;
      81            1 :         let salt = "+/tQQax7twvwTj64mjBsxQ==";
      82            1 :         let stored_key = "D5h6KTMBlUvDJk2Y8ELfC1Sjtc6k9YHjRyuRZyBNJns=";
      83            1 :         let server_key = "Pi3QHbcluX//NDfVkKlFl88GGzlJ5LkyPwcdlN/QBvI=";
      84            1 : 
      85            1 :         let secret = format!("SCRAM-SHA-256${iterations}:{salt}${stored_key}:{server_key}");
      86            1 : 
      87            1 :         let parsed = ServerSecret::parse(&secret).unwrap();
      88            1 :         assert_eq!(parsed.iterations, iterations);
      89            1 :         assert_eq!(parsed.salt_base64, salt);
      90              : 
      91            1 :         assert_eq!(base64::encode(parsed.stored_key), stored_key);
      92            1 :         assert_eq!(base64::encode(parsed.server_key), server_key);
      93            1 :     }
      94              : }
        

Generated by: LCOV version 2.1-beta