LCOV - code coverage report
Current view: top level - libs/proxy/postgres-protocol2/src/password - mod.rs (source / functions) Coverage Total Hit
Test: 07bee600374ccd486c69370d0972d9035964fe68.info Lines: 95.1 % 41 39
Test Date: 2025-02-20 13:11: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 crate::authentication::sasl;
      10              : use hmac::{Hmac, Mac};
      11              : use rand::RngCore;
      12              : use sha2::digest::FixedOutput;
      13              : use sha2::{Digest, Sha256};
      14              : 
      15              : #[cfg(test)]
      16              : mod test;
      17              : 
      18              : const SCRAM_DEFAULT_ITERATIONS: u32 = 4096;
      19              : const SCRAM_DEFAULT_SALT_LEN: usize = 16;
      20              : 
      21              : /// Hash password using SCRAM-SHA-256 with a randomly-generated
      22              : /// salt.
      23              : ///
      24              : /// The client may assume the returned string doesn't contain any
      25              : /// special characters that would require escaping in an SQL command.
      26           16 : pub async fn scram_sha_256(password: &[u8]) -> String {
      27           16 :     let mut salt: [u8; SCRAM_DEFAULT_SALT_LEN] = [0; SCRAM_DEFAULT_SALT_LEN];
      28           16 :     let mut rng = rand::thread_rng();
      29           16 :     rng.fill_bytes(&mut salt);
      30           16 :     scram_sha_256_salt(password, salt).await
      31           16 : }
      32              : 
      33              : // Internal implementation of scram_sha_256 with a caller-provided
      34              : // salt. This is useful for testing.
      35           17 : pub(crate) async fn scram_sha_256_salt(
      36           17 :     password: &[u8],
      37           17 :     salt: [u8; SCRAM_DEFAULT_SALT_LEN],
      38           17 : ) -> String {
      39              :     // Prepare the password, per [RFC
      40              :     // 4013](https://tools.ietf.org/html/rfc4013), if possible.
      41              :     //
      42              :     // Postgres treats passwords as byte strings (without embedded NUL
      43              :     // bytes), but SASL expects passwords to be valid UTF-8.
      44              :     //
      45              :     // Follow the behavior of libpq's PQencryptPasswordConn(), and
      46              :     // also the backend. If the password is not valid UTF-8, or if it
      47              :     // contains prohibited characters (such as non-ASCII whitespace),
      48              :     // just skip the SASLprep step and use the original byte
      49              :     // sequence.
      50           17 :     let prepared: Vec<u8> = match std::str::from_utf8(password) {
      51           17 :         Ok(password_str) => {
      52           17 :             match stringprep::saslprep(password_str) {
      53           17 :                 Ok(p) => p.into_owned().into_bytes(),
      54              :                 // contains invalid characters; skip saslprep
      55            0 :                 Err(_) => Vec::from(password),
      56              :             }
      57              :         }
      58              :         // not valid UTF-8; skip saslprep
      59            0 :         Err(_) => Vec::from(password),
      60              :     };
      61              : 
      62              :     // salt password
      63           17 :     let salted_password = sasl::hi(&prepared, &salt, SCRAM_DEFAULT_ITERATIONS).await;
      64              : 
      65              :     // client key
      66           17 :     let mut hmac = Hmac::<Sha256>::new_from_slice(&salted_password)
      67           17 :         .expect("HMAC is able to accept all key sizes");
      68           17 :     hmac.update(b"Client Key");
      69           17 :     let client_key = hmac.finalize().into_bytes();
      70           17 : 
      71           17 :     // stored key
      72           17 :     let mut hash = Sha256::default();
      73           17 :     hash.update(client_key.as_slice());
      74           17 :     let stored_key = hash.finalize_fixed();
      75           17 : 
      76           17 :     // server key
      77           17 :     let mut hmac = Hmac::<Sha256>::new_from_slice(&salted_password)
      78           17 :         .expect("HMAC is able to accept all key sizes");
      79           17 :     hmac.update(b"Server Key");
      80           17 :     let server_key = hmac.finalize().into_bytes();
      81           17 : 
      82           17 :     format!(
      83           17 :         "SCRAM-SHA-256${}:{}${}:{}",
      84           17 :         SCRAM_DEFAULT_ITERATIONS,
      85           17 :         base64::encode(salt),
      86           17 :         base64::encode(stored_key),
      87           17 :         base64::encode(server_key)
      88           17 :     )
      89           17 : }
        

Generated by: LCOV version 2.1-beta