LCOV - differential code coverage report
Current view: top level - proxy/src/scram - exchange.rs (source / functions) Coverage Total Hit UBC CBC
Current: f6946e90941b557c917ac98cd5a7e9506d180f3e.info Lines: 82.9 % 76 63 13 63
Current Date: 2023-10-19 02:04:12 Functions: 40.0 % 5 2 3 2
Baseline: c8637f37369098875162f194f92736355783b050.info
Baseline Date: 2023-10-18 20:25:20

           TLA  Line data    Source code
       1                 : //! Implementation of the SCRAM authentication algorithm.
       2                 : 
       3                 : use super::messages::{
       4                 :     ClientFinalMessage, ClientFirstMessage, OwnedServerFirstMessage, SCRAM_RAW_NONCE_LEN,
       5                 : };
       6                 : use super::secret::ServerSecret;
       7                 : use super::signature::SignatureBuilder;
       8                 : use crate::sasl::{self, ChannelBinding, Error as SaslError};
       9                 : 
      10                 : /// The only channel binding mode we currently support.
      11                 : struct TlsServerEndPoint;
      12                 : 
      13                 : impl std::fmt::Display for TlsServerEndPoint {
      14 UBC           0 :     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
      15               0 :         write!(f, "tls-server-end-point")
      16               0 :     }
      17                 : }
      18                 : 
      19                 : impl std::str::FromStr for TlsServerEndPoint {
      20                 :     type Err = sasl::Error;
      21                 : 
      22               0 :     fn from_str(s: &str) -> Result<Self, Self::Err> {
      23               0 :         match s {
      24               0 :             "tls-server-end-point" => Ok(TlsServerEndPoint),
      25               0 :             _ => Err(sasl::Error::ChannelBindingBadMethod(s.into())),
      26                 :         }
      27               0 :     }
      28                 : }
      29                 : 
      30                 : enum ExchangeState {
      31                 :     /// Waiting for [`ClientFirstMessage`].
      32                 :     Initial,
      33                 :     /// Waiting for [`ClientFinalMessage`].
      34                 :     SaltSent {
      35                 :         cbind_flag: ChannelBinding<TlsServerEndPoint>,
      36                 :         client_first_message_bare: String,
      37                 :         server_first_message: OwnedServerFirstMessage,
      38                 :     },
      39                 : }
      40                 : 
      41                 : /// Server's side of SCRAM auth algorithm.
      42                 : pub struct Exchange<'a> {
      43                 :     state: ExchangeState,
      44                 :     secret: &'a ServerSecret,
      45                 :     nonce: fn() -> [u8; SCRAM_RAW_NONCE_LEN],
      46                 :     cert_digest: Option<&'a [u8]>,
      47                 : }
      48                 : 
      49                 : impl<'a> Exchange<'a> {
      50 CBC          32 :     pub fn new(
      51              32 :         secret: &'a ServerSecret,
      52              32 :         nonce: fn() -> [u8; SCRAM_RAW_NONCE_LEN],
      53              32 :         cert_digest: Option<&'a [u8]>,
      54              32 :     ) -> Self {
      55              32 :         Self {
      56              32 :             state: ExchangeState::Initial,
      57              32 :             secret,
      58              32 :             nonce,
      59              32 :             cert_digest,
      60              32 :         }
      61              32 :     }
      62                 : }
      63                 : 
      64                 : impl sasl::Mechanism for Exchange<'_> {
      65                 :     type Output = super::ScramKey;
      66                 : 
      67              64 :     fn exchange(mut self, input: &str) -> sasl::Result<sasl::Step<Self, Self::Output>> {
      68              64 :         use {sasl::Step::*, ExchangeState::*};
      69              64 :         match &self.state {
      70                 :             Initial => {
      71              32 :                 let client_first_message = ClientFirstMessage::parse(input)
      72              32 :                     .ok_or(SaslError::BadClientMessage("invalid client-first-message"))?;
      73                 : 
      74              32 :                 let server_first_message = client_first_message.build_server_first_message(
      75              32 :                     &(self.nonce)(),
      76              32 :                     &self.secret.salt_base64,
      77              32 :                     self.secret.iterations,
      78              32 :                 );
      79              32 :                 let msg = server_first_message.as_str().to_owned();
      80                 : 
      81              32 :                 self.state = SaltSent {
      82              32 :                     cbind_flag: client_first_message.cbind_flag.and_then(str::parse)?,
      83              32 :                     client_first_message_bare: client_first_message.bare.to_owned(),
      84              32 :                     server_first_message,
      85              32 :                 };
      86              32 : 
      87              32 :                 Ok(Continue(self, msg))
      88                 :             }
      89                 :             SaltSent {
      90              32 :                 cbind_flag,
      91              32 :                 client_first_message_bare,
      92              32 :                 server_first_message,
      93                 :             } => {
      94              32 :                 let client_final_message = ClientFinalMessage::parse(input)
      95              32 :                     .ok_or(SaslError::BadClientMessage("invalid client-final-message"))?;
      96                 : 
      97              32 :                 let channel_binding = cbind_flag.encode(|_| {
      98 UBC           0 :                     self.cert_digest
      99               0 :                         .map(base64::encode)
     100               0 :                         .ok_or(SaslError::ChannelBindingFailed("no cert digest provided"))
     101 CBC          32 :                 })?;
     102                 : 
     103                 :                 // This might've been caused by a MITM attack
     104              32 :                 if client_final_message.channel_binding != channel_binding {
     105 UBC           0 :                     return Err(SaslError::ChannelBindingFailed("data mismatch"));
     106 CBC          32 :                 }
     107              32 : 
     108              32 :                 if client_final_message.nonce != server_first_message.nonce() {
     109 UBC           0 :                     return Err(SaslError::BadClientMessage("combined nonce doesn't match"));
     110 CBC          32 :                 }
     111              32 : 
     112              32 :                 let signature_builder = SignatureBuilder {
     113              32 :                     client_first_message_bare,
     114              32 :                     server_first_message: server_first_message.as_str(),
     115              32 :                     client_final_message_without_proof: client_final_message.without_proof,
     116              32 :                 };
     117              32 : 
     118              32 :                 let client_key = signature_builder
     119              32 :                     .build(&self.secret.stored_key)
     120              32 :                     .derive_client_key(&client_final_message.proof);
     121              32 : 
     122              32 :                 // Auth fails either if keys don't match or it's pre-determined to fail.
     123              32 :                 if client_key.sha256() != self.secret.stored_key || self.secret.doomed {
     124               4 :                     return Ok(Failure("password doesn't match"));
     125              28 :                 }
     126              28 : 
     127              28 :                 let msg = client_final_message
     128              28 :                     .build_server_final_message(signature_builder, &self.secret.server_key);
     129              28 : 
     130              28 :                 Ok(Success(client_key, msg))
     131                 :             }
     132                 :         }
     133              64 :     }
     134                 : }
        

Generated by: LCOV version 2.1-beta