scram_rs/scram.rs
1/*-
2 * Scram-rs - a SCRAM authentification authorization library
3 *
4 * Copyright (C) 2021 Aleksandr Morozov
5 * Copyright (C) 2025 Aleksandr Morozov
6 *
7 * The syslog-rs crate can be redistributed and/or modified
8 * under the terms of either of the following licenses:
9 *
10 * 1. the Mozilla Public License Version 2.0 (the “MPL”) OR
11 *
12 * 2. The MIT License (MIT)
13 *
14 * 3. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
15 */
16
17
18 #[cfg(feature = "std")]
19use std::fmt;
20#[cfg(not(feature = "std"))]
21use core::fmt;
22
23#[cfg(feature = "std")]
24use std::mem;
25#[cfg(not(feature = "std"))]
26use core::mem;
27
28#[cfg(not(feature = "std"))]
29use alloc::string::String;
30#[cfg(not(feature = "std"))]
31use alloc::string::ToString;
32
33use base64::Engine;
34use base64::engine::general_purpose;
35
36use crate::{scram_ierror, ScramRuntimeError};
37
38use super::scram_error::{ScramResult, ScramErrorCode};
39use super::scram_common::ScramCommon;
40
41pub use super::scram_sync;
42
43#[cfg(feature = "with_async")]
44pub use super::scram_async;
45
46
47
48/// A state encoder for Scram Client.
49/// There is an internal state machine which automatically changes the
50/// state. The timeout and other things must be implemented separatly.
51/// The state from internal state machine is not exposed. If needed can
52/// be maintained separatly.
53///
54/// A [ScramResultServer::Error] indicates an error i.e internal, protocol
55/// violation or other. If the user program does not maintain its own
56/// signalling (i.e encapsulation or error reporting like postgresql)
57/// then the error message can be extracted and sent. The error will be
58/// handled by the scram library.
59#[derive(Debug)]
60pub enum ScramResultServer
61{
62 /// A data to send back
63 Data(String),
64
65 /// - Error during processing. Use the same method `encode_base64` to
66 /// extract message and send back to client.
67 /// - Authentification error which will be indicated as `VerificationError`.
68 Error(ScramRuntimeError),
69
70 /// A final message to send and continue, auth was successfull.
71 Final(String)
72}
73
74impl ScramResultServer
75{
76 /// Tells if result does not contain error.
77 pub
78 fn is_ok(&self) -> bool
79 {
80 match *self
81 {
82 Self::Error(_) => return false,
83 _ => return true
84 }
85 }
86
87 /// Tells if result is error.
88 pub
89 fn is_err(&self) -> bool
90 {
91 return !self.is_ok();
92 }
93
94 /// Extracts error.
95 pub
96 fn err(self) -> Option<ScramRuntimeError>
97 {
98 let Self::Error(err) = self
99 else
100 {
101 return None;
102 };
103
104 return Some(err);
105 }
106
107 /// Extracts error.
108 pub
109 fn get_err(&self) -> Option<&ScramRuntimeError>
110 {
111 let Self::Error(err) = self
112 else
113 {
114 return None;
115 };
116
117 return Some(err);
118 }
119
120 /// Encodes the raw result to base64.
121 pub
122 fn encode_base64(&self) -> String
123 {
124 match *self
125 {
126 Self::Data(ref raw_data) =>
127 general_purpose::STANDARD.encode(raw_data),
128 Self::Error(ref err) =>
129 general_purpose::STANDARD.encode(err.serv_err_value()),
130 Self::Final(ref raw_data) =>
131 general_purpose::STANDARD.encode(raw_data),
132 }
133 }
134
135 /// Returns the ref [str] to raw output.
136 pub
137 fn get_raw_output(&self) -> &str
138 {
139 match *self
140 {
141 Self::Data(ref raw_data) => raw_data.as_str(),
142 Self::Error(ref err) => err.serv_err_value(),
143 Self::Final(ref raw_data) => raw_data.as_str(),
144 }
145 }
146}
147
148/// A state encoder for Scram Client.
149/// There is an internal state machine which automatically changes the
150/// state. The timeout and other things must be implemented separatly.
151/// The state from internal state machine is not exposed. If needed can
152/// be maintained separatly.
153#[derive(PartialEq, Eq, Clone, Debug)]
154pub enum ScramResultClient
155{
156 /// A response was composed and stored in raw format
157 Output( String ),
158
159 /// Final stage, no more parsing is required, auth was successful.
160 /// If auth failed an error will be thrown.
161 Completed
162}
163
164impl fmt::Display for ScramResultClient
165{
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
167 {
168 match self
169 {
170 Self::Output(s) =>
171 write!(f, "{}", s),
172 Self::Completed =>
173 write!(f, "[COMPLETED]")
174 }
175 }
176}
177
178impl ScramResultClient
179{
180 /// Tells if the current status is [ScramResultClient::Output]
181 pub
182 fn is_output(&self) -> bool
183 {
184 return
185 mem::discriminant(self) == mem::discriminant(&ScramResultClient::Output( String::new() ));
186 }
187
188 /// Tells is current status is [ScramResultClient::Completed].
189 pub
190 fn is_final(&self) -> bool
191 {
192 return
193 mem::discriminant(self) == mem::discriminant(&ScramResultClient::Completed);
194 }
195
196 /// Unwraps the result which should be sent to server. The result is raw and
197 /// not encoded to base64!
198 ///
199 /// # Returns
200 ///
201 /// * [ScramResult]
202 /// - Ok() with payload (raw output)
203 /// - Err(e) [ScramErrorCode::AuthSeqCompleted] if called on state
204 /// [ScramResultClient::Completed].
205 pub
206 fn unwrap_output(self) -> ScramResult<String>
207 {
208 match self
209 {
210 ScramResultClient::Output(output) =>
211 return Ok(output),
212 ScramResultClient::Completed =>
213 scram_ierror!(ScramErrorCode::AuthSeqCompleted, "completed, nothing to extract"),
214 }
215 }
216
217 /// Unwraps the result which should be sent to server and returns a ref
218 /// [str]. The result is raw and not encoded to base64!
219 ///
220 /// # Returns
221 ///
222 /// * [ScramResult]
223 /// - Ok() with payload (raw output)
224 /// - Err(e) [ScramErrorCode::AuthSeqCompleted] if called on state
225 /// [ScramResultClient::Completed].
226 pub
227 fn get_output(&self) -> ScramResult<&str>
228 {
229 match self
230 {
231 ScramResultClient::Output(output) =>
232 return Ok(output.as_str()),
233 ScramResultClient::Completed =>
234 scram_ierror!(ScramErrorCode::AuthSeqCompleted, "completed, nothing to extract"),
235 }
236 }
237
238 /// Encodes the output to base64.
239 ///
240 /// # Returns
241 ///
242 /// * [ScramResult]
243 /// - Ok() with payload (encoded to base64 output)
244 /// - Err(e) [ScramErrorCode::AuthSeqCompleted] if called on state
245 /// [ScramResultClient::Completed].
246 pub
247 fn encode_output_base64(&self) -> ScramResult<String>
248 {
249 match self
250 {
251 ScramResultClient::Output(output) =>
252 return Ok(general_purpose::STANDARD.encode(output)),
253 ScramResultClient::Completed =>
254 scram_ierror!(ScramErrorCode::AuthSeqCompleted, "completed, nothing to extract"),
255 }
256 }
257}
258
259/// A SCRAM nonce initialization and customization.
260/// Use implemented functions, don't use enum fields directly.
261///
262/// * '0' - a base64 encoded nonce.
263#[derive(Debug, Clone, PartialEq, Eq)]
264pub struct ScramNonce(String);
265
266impl ScramNonce
267{
268 #[cfg(feature = "with_capi")]
269 pub(crate)
270 fn empty() -> Self
271 {
272 return ScramNonce(String::new());
273 }
274
275 /// Initialize ScramNonce so the data will be autogenerated
276 pub
277 fn none() -> ScramResult<Self>
278 {
279 let sn =
280 ScramNonce(
281 general_purpose::STANDARD.encode(
282 ScramCommon::sc_random(ScramCommon::SCRAM_RAW_NONCE_LEN)?
283 )
284 );
285
286 return Ok(sn);
287 }
288
289 /// Initialize ScramNonce with plain data.
290 pub
291 fn plain(p: &[u8]) -> ScramResult<ScramNonce>
292 {
293 if p.len() > ScramCommon::SCRAM_RAW_NONCE_LEN
294 {
295 scram_ierror!(
296 ScramErrorCode::InternalError,
297 "nonce length is > {}, actual: '{}'",
298 ScramCommon::SCRAM_RAW_NONCE_LEN, p.len()
299 );
300 }
301
302 let sn =
303 ScramNonce(general_purpose::STANDARD.encode(p));
304
305 return Ok(sn);
306 }
307
308 /// Initialize ScramNonce with base64 encoded nonce.
309 pub
310 fn base64(b: &str) -> ScramResult<ScramNonce>
311 {
312 if b.len() == 0
313 {
314 scram_ierror!(ScramErrorCode::InternalError, "base64 nonce length is 0");
315 }
316 /*
317 let _ = general_purpose::STANDARD
318 .decode(b)
319 .map_err(|e|
320 scram_ierror_map!(ScramErrorCode::InternalError, "base64 decode: {}", e)
321 )?;
322 */
323 let sn = ScramNonce(b.to_string());
324
325 return Ok(sn);
326 }
327
328 /// Consumes the nonce from inner.
329 pub
330 fn get_nonce(self) -> String
331 {
332 return self.0;
333 }
334}