Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/skade

vendor/iceberg-arrow59/src/sensitive.rs

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

//! This module contains types to keep sensitive data in memory.

use std::fmt;

use zeroize::Zeroizing;

/// A string-like type containing sensitive information such as passwords or tokens.
///
/// It is redacted from debug logs and automatically zeroized.
///
/// # Example
/// ```
/// use iceberg::sensitive::SensitiveString;
///
/// let sensitive_value = "my-pw-12345";
/// let sensitive_string = SensitiveString::from(sensitive_value.to_string());
///
/// // Not contained in debug logs.
/// assert!(!format!("{:?}", sensitive_string).contains(sensitive_value));
/// ```
///
/// # Display
/// [`SensitiveString`] does **not** implement [`std::fmt::Display`] to prevent bugs like:
///
/// ```compile_fail
/// # use iceberg::sensitive::SensitiveString;
/// // We don't want to send a redacted `Bearer: *****`.
/// let auth_header = format!("Bearer {}", SensitiveString::from("token".to_string()));
/// ```
///
/// Instead use an explicit [`SensitiveString::expose`] when you need it:
///
/// ```
/// # use iceberg::sensitive::SensitiveString;
/// let auth_header = format!(
///     "Bearer: {}",
///     SensitiveString::from("token".to_string()).expose()
/// );
/// ```
#[derive(Clone, PartialEq, Eq)]
pub struct SensitiveString(Zeroizing<String>);

impl SensitiveString {
    /// Returns the raw value of the sensitive string.
    pub fn expose(&self) -> &str {
        &self.0
    }

    /// Returns `true` if the string value is empty.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }
}

impl fmt::Debug for SensitiveString {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("SensitiveString([REDACTED])")
    }
}

impl From<String> for SensitiveString {
    fn from(value: String) -> Self {
        Self(Zeroizing::new(value))
    }
}

/// Wrapper for sensitive byte data (encryption keys, DEKs, etc.) that:
/// - Zeroizes memory on drop
/// - Redacts content in [`Debug`] and [`std::fmt::Display`] output
/// - Provides only `&[u8]` access via [`as_bytes()`](Self::as_bytes)
/// - Uses `Box<[u8]>` (immutable boxed slice) since key bytes never grow
///
/// Use this type for any struct field that holds plaintext key material.
/// Because its [`Debug`] impl always prints `[N bytes REDACTED]`, structs
/// containing `SensitiveBytes` can safely derive or implement `Debug`
/// without risk of leaking key material.
#[derive(Clone, PartialEq, Eq)]
pub struct SensitiveBytes(Zeroizing<Box<[u8]>>);

impl SensitiveBytes {
    /// Wraps the given bytes as sensitive material.
    pub fn new(bytes: impl Into<Box<[u8]>>) -> Self {
        Self(Zeroizing::new(bytes.into()))
    }

    /// Returns the underlying bytes.
    pub fn as_bytes(&self) -> &[u8] {
        &self.0
    }

    /// Returns the number of bytes.
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Returns `true` if the byte slice is empty.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }
}

impl fmt::Debug for SensitiveBytes {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "[{} bytes REDACTED]", self.0.len())
    }
}

impl fmt::Display for SensitiveBytes {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "[{} bytes REDACTED]", self.0.len())
    }
}

#[cfg(test)]
mod tests {
    use crate::sensitive::{SensitiveBytes, SensitiveString};

    #[test]
    fn test_sensitive_string_redacts_debug_value() {
        let sensitive_value = "my-pw-12346";

        let logged = format!("{:?}", SensitiveString::from(sensitive_value.to_string()));
        assert!(!logged.contains(sensitive_value));
    }

    #[test]
    fn test_sensitive_bytes_redacts_debug_value() {
        let sensitive_value = b"my-secret-bytes";

        let logged = format!("{:?}", SensitiveBytes::new(&sensitive_value[..]));
        assert!(
            !logged
                .as_bytes()
                .windows(sensitive_value.len())
                .any(|window| window == sensitive_value)
        );
    }

    #[test]
    fn test_sensitive_bytes_redacts_display_value() {
        let sensitive_value = b"my-secret-bytes";

        let logged = format!("{}", SensitiveBytes::new(&sensitive_value[..]));
        assert!(
            !logged
                .as_bytes()
                .windows(sensitive_value.len())
                .any(|window| window == sensitive_value)
        );
    }
}