use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use std::borrow::{Borrow, BorrowMut};
use std::fmt;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize)]
#[serde(transparent)]
#[repr(transparent)]
pub struct Secret(pub String);
impl Secret {
#[inline]
pub fn expose(&self) -> &String {
&self.0
}
#[inline]
pub fn expose_mut(&mut self) -> &mut String {
&mut self.0
}
}
impl AsRef<str> for Secret {
#[inline]
fn as_ref(&self) -> &str {
&self.0
}
}
impl AsRef<[u8]> for Secret {
#[inline]
fn as_ref(&self) -> &[u8] {
AsRef::as_ref(&self.0)
}
}
impl AsRef<std::ffi::OsStr> for Secret {
#[inline]
fn as_ref(&self) -> &std::ffi::OsStr {
AsRef::as_ref(&self.0)
}
}
impl AsRef<std::path::Path> for Secret {
#[inline]
fn as_ref(&self) -> &std::path::Path {
AsRef::as_ref(&self.0)
}
}
impl AsMut<str> for Secret {
#[inline]
fn as_mut(&mut self) -> &mut str {
&mut self.0
}
}
impl Borrow<str> for Secret {
#[inline]
fn borrow(&self) -> &str {
&self.0
}
}
impl BorrowMut<str> for Secret {
#[inline]
fn borrow_mut(&mut self) -> &mut str {
&mut self.0
}
}
impl fmt::Debug for Secret {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Secret")
}
}
impl fmt::Display for Secret {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("REDACTED")
}
}
impl Serialize for Secret {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_none()
}
}
impl super::Settings for Secret {}
impl Zeroize for Secret {
#[inline]
fn zeroize(&mut self) {
self.0.zeroize();
}
}
impl Drop for Secret {
fn drop(&mut self) {
self.zeroize();
}
}
impl ZeroizeOnDrop for Secret {}
#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct RawSecret(pub Vec<u8>);
impl RawSecret {
#[inline]
pub fn expose(&self) -> &Vec<u8> {
&self.0
}
#[inline]
pub fn expose_mut(&mut self) -> &mut Vec<u8> {
&mut self.0
}
}
impl AsRef<[u8]> for RawSecret {
#[inline]
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl AsMut<[u8]> for RawSecret {
#[inline]
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
impl Borrow<[u8]> for RawSecret {
#[inline]
fn borrow(&self) -> &[u8] {
&self.0
}
}
impl BorrowMut<[u8]> for RawSecret {
#[inline]
fn borrow_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
impl fmt::Debug for RawSecret {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("SecretBytes")
}
}
impl fmt::Display for RawSecret {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("REDACTED")
}
}
impl Serialize for RawSecret {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_none()
}
}
impl<'de> Deserialize<'de> for RawSecret {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct BytesVisitor;
impl<'de> de::Visitor<'de> for BytesVisitor {
type Value = RawSecret;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a byte array")
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
Ok(RawSecret(v.as_bytes().to_vec()))
}
fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
Ok(RawSecret(v.into_bytes()))
}
fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
Ok(RawSecret(v.to_vec()))
}
fn visit_byte_buf<E: de::Error>(self, v: Vec<u8>) -> Result<Self::Value, E> {
Ok(RawSecret(v))
}
fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let mut bytes = Vec::with_capacity(seq.size_hint().unwrap_or(0));
while let Some(b) = seq.next_element()? {
bytes.push(b);
}
Ok(RawSecret(bytes))
}
}
deserializer.deserialize_byte_buf(BytesVisitor)
}
}
impl super::Settings for RawSecret {}
impl Zeroize for RawSecret {
fn zeroize(&mut self) {
self.0.clear();
self.0.spare_capacity_mut().zeroize();
}
}
impl Drop for RawSecret {
fn drop(&mut self) {
self.zeroize();
}
}
impl ZeroizeOnDrop for RawSecret {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn secret_is_redacted() {
const SECRET: &str = "SUPER_SECRET_VALUE";
let secret = Secret(SECRET.to_owned());
let raw_secret = RawSecret(SECRET.as_bytes().to_vec());
let formatted = format!(
"plain: {secret}\nplain dbg: {secret:?}\nraw: {raw_secret}\nraw dbg: {raw_secret:?}\n"
);
let leaks_plaintext = formatted.contains(SECRET);
let leaks_bytes = formatted.contains(&format!("{:?}", SECRET.as_bytes()));
assert!(
!leaks_plaintext && !leaks_bytes,
"(Raw)Secret leaked data:\n\n{formatted}"
);
}
}