LCOV - code coverage report
Current view: top level - libs/proxy/postgres-protocol2/src/password - mod.rs (source / functions) Coverage Total Hit
Test: 5445d246133daeceb0507e6cc0797ab7c1c70cb8.info Lines: 92.7 % 41 38
Test Date: 2025-03-12 18:05:02 Functions: 100.0 % 4 4

            Line data    Source code
       1              : //! Functions to encrypt a password in the client.
       2              : //!
       3              : //! This is intended to be used by client applications that wish to
       4              : //! send commands like `ALTER USER joe PASSWORD 'pwd'`. The password
       5              : //! need not be sent in cleartext if it is encrypted on the client
       6              : //! side. This is good because it ensures the cleartext password won't
       7              : //! end up in logs pg_stat displays, etc.
       8              : 
       9              : use hmac::{Hmac, Mac};
      10              : use rand::RngCore;
      11              : use sha2::digest::FixedOutput;
      12              : use sha2::{Digest, Sha256};
      13              : 
      14              : use crate::authentication::sasl;
      15              : 
      16              : #[cfg(test)]
      17              : mod test;
      18              : 
      19              : const SCRAM_DEFAULT_ITERATIONS: u32 = 4096;
      20              : const SCRAM_DEFAULT_SALT_LEN: usize = 16;
      21              : 
      22              : /// Hash password using SCRAM-SHA-256 with a randomly-generated
      23              : /// salt.
      24              : ///
      25              : /// The client may assume the returned string doesn't contain any
      26              : /// special characters that would require escaping in an SQL command.
      27           16 : pub async fn scram_sha_256(password: &[u8]) -> String {
      28           16 :     let mut salt: [u8; SCRAM_DEFAULT_SALT_LEN] = [0; SCRAM_DEFAULT_SALT_LEN];
      29           16 :     let mut rng = rand::thread_rng();
      30           16 :     rng.fill_bytes(&mut salt);
      31           16 :     scram_sha_256_salt(password, salt).await
      32            0 : }
      33              : 
      34              : // Internal implementation of scram_sha_256 with a caller-provided
      35              : // salt. This is useful for testing.
      36           17 : pub(crate) async fn scram_sha_256_salt(
      37           17 :     password: &[u8],
      38           17 :     salt: [u8; SCRAM_DEFAULT_SALT_LEN],
      39           17 : ) -> String {
      40              :     // Prepare the password, per [RFC
      41              :     // 4013](https://tools.ietf.org/html/rfc4013), if possible.
      42              :     //
      43              :     // Postgres treats passwords as byte strings (without embedded NUL
      44              :     // bytes), but SASL expects passwords to be valid UTF-8.
      45              :     //
      46              :     // Follow the behavior of libpq's PQencryptPasswordConn(), and
      47              :     // also the backend. If the password is not valid UTF-8, or if it
      48              :     // contains prohibited characters (such as non-ASCII whitespace),
      49              :     // just skip the SASLprep step and use the original byte
      50              :     // sequence.
      51           17 :     let prepared: Vec<u8> = match std::str::from_utf8(password) {
      52           17 :         Ok(password_str) => {
      53           17 :             match stringprep::saslprep(password_str) {
      54           17 :                 Ok(p) => p.into_owned().into_bytes(),
      55              :                 // contains invalid characters; skip saslprep
      56            0 :                 Err(_) => Vec::from(password),
      57              :             }
      58              :         }
      59              :         // not valid UTF-8; skip saslprep
      60            0 :         Err(_) => Vec::from(password),
      61              :     };
      62              : 
      63              :     // salt password
      64           17 :     let salted_password = sasl::hi(&prepared, &salt, SCRAM_DEFAULT_ITERATIONS).await;
      65              : 
      66              :     // client key
      67           17 :     let mut hmac = Hmac::<Sha256>::new_from_slice(&salted_password)
      68           17 :         .expect("HMAC is able to accept all key sizes");
      69           17 :     hmac.update(b"Client Key");
      70           17 :     let client_key = hmac.finalize().into_bytes();
      71           17 : 
      72           17 :     // stored key
      73           17 :     let mut hash = Sha256::default();
      74           17 :     hash.update(client_key.as_slice());
      75           17 :     let stored_key = hash.finalize_fixed();
      76           17 : 
      77           17 :     // server key
      78           17 :     let mut hmac = Hmac::<Sha256>::new_from_slice(&salted_password)
      79           17 :         .expect("HMAC is able to accept all key sizes");
      80           17 :     hmac.update(b"Server Key");
      81           17 :     let server_key = hmac.finalize().into_bytes();
      82           17 : 
      83           17 :     format!(
      84           17 :         "SCRAM-SHA-256${}:{}${}:{}",
      85           17 :         SCRAM_DEFAULT_ITERATIONS,
      86           17 :         base64::encode(salt),
      87           17 :         base64::encode(stored_key),
      88           17 :         base64::encode(server_key)
      89           17 :     )
      90           17 : }
        

Generated by: LCOV version 2.1-beta