use super::{
ENVELOPE_PREFIX, PrivacyError, SecretString, configured_key, current_key, current_key_id,
decrypt_envelope, encrypt_with_context, parse_envelope,
};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::cell::Cell;
const SERDE_CONTEXT: &[u8] = b"rullst-orm:secret-string:serde";
thread_local! {
static REDACTION_DEPTH: Cell<usize> = const { Cell::new(0) };
}
struct RedactionScope;
impl RedactionScope {
fn enter() -> Self {
REDACTION_DEPTH.with(|depth| depth.set(depth.get().saturating_add(1)));
Self
}
}
impl Drop for RedactionScope {
fn drop(&mut self) {
REDACTION_DEPTH.with(|depth| depth.set(depth.get().saturating_sub(1)));
}
}
#[doc(hidden)]
pub fn with_redacted_secrets<R>(serialize: impl FnOnce() -> R) -> R {
let _scope = RedactionScope::enter();
serialize()
}
fn redacting() -> bool {
REDACTION_DEPTH.with(|depth| depth.get() > 0)
}
fn encrypt_serialized(plaintext: &str) -> Result<String, PrivacyError> {
let key_id = current_key_id()?;
let key = current_key()?;
encrypt_with_context(plaintext, &key, &key_id, SERDE_CONTEXT)
}
fn decrypt_serialized(encrypted: &str) -> Result<String, PrivacyError> {
let envelope = parse_envelope(encrypted)?;
let key = configured_key(envelope.key_id)?;
decrypt_envelope(&envelope, &key, SERDE_CONTEXT)
}
fn is_envelope(value: &str) -> bool {
value
.strip_prefix(ENVELOPE_PREFIX)
.is_some_and(|rest| rest.starts_with(':'))
}
impl Serialize for SecretString {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if redacting() {
return serializer.serialize_str(crate::audit::REDACTED_VALUE);
}
let envelope = encrypt_serialized(&self.0).map_err(serde::ser::Error::custom)?;
serializer.serialize_str(&envelope)
}
}
impl<'de> Deserialize<'de> for SecretString {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = String::deserialize(deserializer)?;
if !is_envelope(&value) {
return Ok(SecretString(value));
}
decrypt_serialized(&value)
.map(SecretString)
.map_err(serde::de::Error::custom)
}
}
const ENVELOPE_INPUT_ERROR: &str = "an encrypted RULLST envelope is not accepted as secret input";
pub fn deserialize_plaintext_secret<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<SecretString, D::Error> {
plaintext(String::deserialize(deserializer)?)
}
pub fn deserialize_optional_plaintext_secret<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<SecretString>, D::Error> {
Option::<String>::deserialize(deserializer)?
.map(plaintext)
.transpose()
}
fn plaintext<E: serde::de::Error>(value: String) -> Result<SecretString, E> {
if is_envelope(&value) {
return Err(E::custom(ENVELOPE_INPUT_ERROR));
}
Ok(SecretString(value))
}