LCOV - differential code coverage report
Current view: top level - proxy/src - scram.rs (source / functions) Coverage Total Hit UBC CBC
Current: cd44433dd675caa99df17a61b18949c8387e2242.info Lines: 93.1 % 72 67 5 67
Current Date: 2024-01-09 02:06:09 Functions: 100.0 % 13 13 13
Baseline: 66c52a629a0f4a503e193045e0df4c77139e344b.info
Baseline Date: 2024-01-08 15:34:46

           TLA  Line data    Source code
       1                 : //! Salted Challenge Response Authentication Mechanism.
       2                 : //!
       3                 : //! RFC: <https://datatracker.ietf.org/doc/html/rfc5802>.
       4                 : //!
       5                 : //! Reference implementation:
       6                 : //! * <https://github.com/postgres/postgres/blob/94226d4506e66d6e7cbf4b391f1e7393c1962841/src/backend/libpq/auth-scram.c>
       7                 : //! * <https://github.com/postgres/postgres/blob/94226d4506e66d6e7cbf4b391f1e7393c1962841/src/interfaces/libpq/fe-auth-scram.c>
       8                 : 
       9                 : mod exchange;
      10                 : mod key;
      11                 : mod messages;
      12                 : mod secret;
      13                 : mod signature;
      14                 : 
      15                 : #[cfg(any(test, doc))]
      16                 : mod password;
      17                 : 
      18                 : pub use exchange::{exchange, Exchange};
      19                 : pub use key::ScramKey;
      20                 : pub use secret::ServerSecret;
      21                 : 
      22                 : use hmac::{Hmac, Mac};
      23                 : use sha2::{Digest, Sha256};
      24                 : 
      25                 : const SCRAM_SHA_256: &str = "SCRAM-SHA-256";
      26                 : const SCRAM_SHA_256_PLUS: &str = "SCRAM-SHA-256-PLUS";
      27                 : 
      28                 : /// A list of supported SCRAM methods.
      29                 : pub const METHODS: &[&str] = &[SCRAM_SHA_256_PLUS, SCRAM_SHA_256];
      30                 : pub const METHODS_WITHOUT_PLUS: &[&str] = &[SCRAM_SHA_256];
      31                 : 
      32                 : /// Decode base64 into array without any heap allocations
      33 CBC         288 : fn base64_decode_array<const N: usize>(input: impl AsRef<[u8]>) -> Option<[u8; N]> {
      34             288 :     let mut bytes = [0u8; N];
      35                 : 
      36             288 :     let size = base64::decode_config_slice(input, base64::STANDARD, &mut bytes).ok()?;
      37             288 :     if size != N {
      38 UBC           0 :         return None;
      39 CBC         288 :     }
      40             288 : 
      41             288 :     Some(bytes)
      42             288 : }
      43                 : 
      44                 : /// This function essentially is `Hmac(sha256, key, input)`.
      45                 : /// Further reading: <https://datatracker.ietf.org/doc/html/rfc2104>.
      46            4208 : fn hmac_sha256<'a>(key: &[u8], parts: impl IntoIterator<Item = &'a [u8]>) -> [u8; 32] {
      47            4208 :     let mut mac = Hmac::<Sha256>::new_from_slice(key).expect("bad key size");
      48            4553 :     parts.into_iter().for_each(|s| mac.update(s));
      49            4208 : 
      50            4208 :     mac.finalize().into_bytes().into()
      51            4208 : }
      52                 : 
      53              60 : fn sha256<'a>(parts: impl IntoIterator<Item = &'a [u8]>) -> [u8; 32] {
      54              60 :     let mut hasher = Sha256::new();
      55              62 :     parts.into_iter().for_each(|s| hasher.update(s));
      56              60 : 
      57              60 :     hasher.finalize().into()
      58              60 : }
      59                 : 
      60                 : #[cfg(test)]
      61                 : mod tests {
      62                 :     use crate::sasl::{Mechanism, Step};
      63                 : 
      64                 :     use super::{password::SaltedPassword, Exchange, ServerSecret};
      65                 : 
      66               1 :     #[test]
      67               1 :     fn happy_path() {
      68               1 :         let iterations = 4096;
      69               1 :         let salt_base64 = "QSXCR+Q6sek8bf92";
      70               1 :         let pw = SaltedPassword::new(
      71               1 :             b"pencil",
      72               1 :             base64::decode(salt_base64).unwrap().as_slice(),
      73               1 :             iterations,
      74               1 :         );
      75               1 : 
      76               1 :         let secret = ServerSecret {
      77               1 :             iterations,
      78               1 :             salt_base64: salt_base64.to_owned(),
      79               1 :             stored_key: pw.client_key().sha256(),
      80               1 :             server_key: pw.server_key(),
      81               1 :             doomed: false,
      82               1 :         };
      83               1 :         const NONCE: [u8; 18] = [
      84               1 :             1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
      85               1 :         ];
      86               1 :         let mut exchange = Exchange::new(
      87               1 :             &secret,
      88               1 :             || NONCE,
      89               1 :             crate::config::TlsServerEndPoint::Undefined,
      90               1 :         );
      91               1 : 
      92               1 :         let client_first = "n,,n=user,r=rOprNGfwEbeRWgbNEkqO";
      93               1 :         let client_final = "c=biws,r=rOprNGfwEbeRWgbNEkqOAQIDBAUGBwgJCgsMDQ4PEBES,p=rw1r5Kph5ThxmaUBC2GAQ6MfXbPnNkFiTIvdb/Rear0=";
      94               1 :         let server_first =
      95               1 :             "r=rOprNGfwEbeRWgbNEkqOAQIDBAUGBwgJCgsMDQ4PEBES,s=QSXCR+Q6sek8bf92,i=4096";
      96               1 :         let server_final = "v=qtUDIofVnIhM7tKn93EQUUt5vgMOldcDVu1HC+OH0o0=";
      97                 : 
      98               1 :         exchange = match exchange.exchange(client_first).unwrap() {
      99               1 :             Step::Continue(exchange, message) => {
     100               1 :                 assert_eq!(message, server_first);
     101               1 :                 exchange
     102                 :             }
     103 UBC           0 :             Step::Success(_, _) => panic!("expected continue, got success"),
     104               0 :             Step::Failure(f) => panic!("{f}"),
     105                 :         };
     106                 : 
     107 CBC           1 :         let key = match exchange.exchange(client_final).unwrap() {
     108               1 :             Step::Success(key, message) => {
     109               1 :                 assert_eq!(message, server_final);
     110               1 :                 key
     111                 :             }
     112 UBC           0 :             Step::Continue(_, _) => panic!("expected success, got continue"),
     113               0 :             Step::Failure(f) => panic!("{f}"),
     114                 :         };
     115                 : 
     116 CBC           1 :         assert_eq!(
     117               1 :             key.as_bytes(),
     118               1 :             [
     119               1 :                 74, 103, 1, 132, 12, 31, 200, 48, 28, 54, 82, 232, 207, 12, 138, 189, 40, 32, 134,
     120               1 :                 27, 125, 170, 232, 35, 171, 167, 166, 41, 70, 228, 182, 112,
     121               1 :             ]
     122               1 :         );
     123               1 :     }
     124                 : }
        

Generated by: LCOV version 2.1-beta