Skip to main content

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}