1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! This defines data types related to the configuration for sensitive
//! information handling.

use tracing::error;

use serde::{Deserialize, Serialize};
use utilities::crypto::error::CryptoError;
use zeroize::{Zeroize, ZeroizeOnDrop};

/// The [`SensitiveInfoConfig`] type is used to configure how we handle
/// sensitive information (cryptographic keys, passwords, etc.) when using
/// `Display` and `Debug`.
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
pub struct SensitiveInfoConfig {
    /// A boolean indicating whether sensitive information should be redacted.
    /// If set to true, the sensitive information will be redacted (hidden
    /// or masked).
    /// If set to false, the sensitive information will not
    /// be redacted.
    redact_sensitive_info: bool,
}

/// Conversion from `Vec<u8>` to [`SensitiveInfoConfig`]
impl TryFrom<Vec<u8>> for SensitiveInfoConfig {
    type Error = CryptoError;

    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
        let config: SensitiveInfoConfig =
            bincode::deserialize(&bytes).map_err(|_| CryptoError::ConversionError)?;
        Ok(config)
    }
}

/// Conversion from [`SensitiveInfoConfig`] to `Vec<u8>`
impl TryFrom<SensitiveInfoConfig> for Vec<u8> {
    type Error = CryptoError;

    fn try_from(config: SensitiveInfoConfig) -> Result<Self, Self::Error> {
        let encoded: Vec<u8> =
            bincode::serialize(&config).map_err(|_| CryptoError::ConversionError)?;
        Ok(encoded)
    }
}

impl SensitiveInfoConfig {
    /// Tis is the label used to redact sensitive information in the library
    /// output
    const REDACTED_INFO_LABEL: &'static str = "***REDACTED***";

    /// Constructor for [`SensitiveInfoConfig`] type.
    /// The default is to redact sensitive information in the output.
    // pub fn new() -> Self {

    //     Self {redact_sensitive_info: true}
    // }

    /// Constructs a new instance of the type [`SensitiveInfoConfig`]
    ///
    /// # Arguments
    ///
    /// * `redact_sensitive_info`   - A boolean indicating whether to redact
    ///   sensitive information.
    ///                             - When set to true, any sensitive
    ///                               information handled by instances using
    ///                               this configuration will be redacted
    ///                               (hidden or masked).
    ///                             - When set to false, the sensitive
    ///                               information will not be redacted.
    ///
    /// # Returns
    ///
    /// * Returns a new instance of [`SensitiveInfoConfig`], initialized with
    ///   the provided `redact_sensitive_info` flag.
    ///
    /// # Example
    ///
    /// ```rust
    /// use lock_keeper::infrastructure::sensitive_info::SensitiveInfoConfig;
    /// let redact_sensitive_info = true;
    /// let config = SensitiveInfoConfig::new(redact_sensitive_info);
    /// ```
    pub fn new(redact_sensitive_info: bool) -> Self {
        Self {
            redact_sensitive_info,
        }
    }

    /// Returns a label that is used in place of sensitive information.
    pub fn redacted_label(self) -> String {
        SensitiveInfoConfig::REDACTED_INFO_LABEL.to_string()
    }

    /// Redact sensitive information.
    pub fn redact(&mut self) {
        self.redact_sensitive_info = true;
    }

    /// Unredact sensitive information.
    pub fn unredact(&mut self) {
        self.redact_sensitive_info = false;
    }

    /// Returns the status of the sensitive information redaction.
    pub fn is_redacted(&self) -> bool {
        self.redact_sensitive_info
    }
}

/// Checks if sensitive information is properly redacted in Debug and Display
/// outputs.
///
/// This function takes a generic `sensitive_info` object and a `config` object
/// which provides the redaction configuration.
///
/// The `sensitive_info` object should implement the `Debug` and `Display`
/// traits so that it can be properly formatted for output.
///
/// The function checks if the redaction is correctly applied based on the
/// configuration and the build type (Release or Debug).
///
/// # Arguments
///
/// * `sensitive_info` - A reference to an object of any type that implements
///   `Debug` and `Display`.
/// * `config` - A reference to a [`SensitiveInfoConfig`] object which provides
///   the redaction configuration.
///
/// # Returns
///
/// * `Ok(())` - If the sensitive information is correctly redacted in both
///   Debug and Display outputs.
/// * `Err(CryptoError::SensitiveInfoCheckFailed)` - If the sensitive
///   information is not correctly redacted.
///
/// # Panics
///
/// This function does not panic. However, the caller should handle the [`Err`]
/// result appropriately.
pub fn sensitive_info_check<T: std::fmt::Debug + std::fmt::Display>(
    sensitive_info: &T,
    config: &SensitiveInfoConfig,
) -> Result<(), CryptoError> {
    // create formatted strings for Debug and Display traits
    let debug_format_sensitive_info = format!("{:?}", sensitive_info);
    let display_format_sensitive_info = format!("{}", sensitive_info);

    // should_be_redacted...
    //      if this is a Release build OR if the redacted config flag is set to true
    let should_be_redacted = !cfg!(debug_assertions) || (config.is_redacted());

    // check if output contains the redacted tag
    let is_debug_redacted = debug_format_sensitive_info.contains(&config.clone().redacted_label());
    let is_display_redacted =
        display_format_sensitive_info.contains(&config.clone().redacted_label());

    // check if the redacted tag is applied correctly for Debug and Display traits.
    if is_debug_redacted != should_be_redacted {
        error!(
            "Unexpected debug output: {}",
            if should_be_redacted {
                "Found UN-REDACTED info!!!"
            } else {
                "Found REDACTED info."
            }
        );

        return Err(CryptoError::SensitiveInfoCheckFailed);
    }

    if is_display_redacted != should_be_redacted {
        error!(
            "Unexpected display output: {}",
            if should_be_redacted {
                "Found UN-REDACTED info!!!"
            } else {
                "Found REDACTED info."
            }
        );

        return Err(CryptoError::SensitiveInfoCheckFailed);
    }

    Ok(())
}