signstar_crypto/
passphrase.rs

1//! Passphrase handling.
2
3use std::{fmt::Display, str::FromStr};
4
5use secrecy::{ExposeSecret, SecretString};
6use serde::{Deserialize, Serialize};
7
8/// An error that may occur when operating on users.
9#[derive(Debug, thiserror::Error)]
10pub enum Error {
11    /// Unable to convert string slice to Passphrase
12    #[error("Unable to convert string to passphrase")]
13    Passphrase,
14}
15
16/// A secret passphrase
17///
18/// The passphrase is held by a [`SecretString`], which guarantees zeroing of memory on
19/// destruct.
20#[derive(Clone, Debug, Default, Deserialize)]
21pub struct Passphrase(SecretString);
22
23impl Passphrase {
24    /// Creates a new [`Passphrase`] from owned [`String`]
25    ///
26    /// # Examples
27    /// ```
28    /// use signstar_crypto::passphrase::Passphrase;
29    ///
30    /// let passphrase = Passphrase::new("passphrase".to_string());
31    /// ```
32    pub fn new(passphrase: String) -> Self {
33        Self(SecretString::new(passphrase.into()))
34    }
35
36    /// Exposes the secret passphrase as owned [`String`]
37    pub fn expose_owned(&self) -> String {
38        self.0.expose_secret().to_owned()
39    }
40
41    /// Exposes the secret passphrase as borrowed [`str`]
42    pub fn expose_borrowed(&self) -> &str {
43        self.0.expose_secret()
44    }
45}
46
47impl Display for Passphrase {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        write!(f, "[REDACTED]")
50    }
51}
52
53impl FromStr for Passphrase {
54    type Err = Error;
55
56    fn from_str(s: &str) -> Result<Self, Self::Err> {
57        Ok(Self(SecretString::from(s.to_string())))
58    }
59}
60
61impl Serialize for Passphrase {
62    /// Serializes a [`Passphrase`].
63    ///
64    /// # Warning
65    ///
66    /// This may be used to write a passphrase to file!
67    /// Take precautions so that passphrases can not leak to the environment.
68    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
69    where
70        S: serde::Serializer,
71    {
72        self.0.expose_secret().serialize(serializer)
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use testresult::TestResult;
79
80    use super::*;
81
82    #[test]
83    fn passphrase_display() -> TestResult {
84        let passphrase = Passphrase::new("a-secret-passphrase".to_string());
85        assert_eq!(format!("{passphrase}"), "[REDACTED]");
86        Ok(())
87    }
88}