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 : }
|