Skip to main content

signstar_yubihsm2/
user.rs

1//! User handling for YubiHSM2 devices.
2
3use std::path::PathBuf;
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
8
9use crate::object::Id;
10
11/// Credentials for a YubiHSM2 device, that are backed by a UTF-8 encoded passphrase file.
12///
13/// Credentials are mapped to the authentication key ID and the passphrase file.
14/// The contents of the passphrase file are meant to be used as input to the key derivation function
15/// (KDF) for an authentication key.
16#[derive(Debug)]
17#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
18pub struct FileBackedCredentials {
19    pub(crate) id: Id,
20    passphrase_file: PathBuf,
21}
22
23impl TryFrom<&FileBackedCredentials> for yubihsm::Credentials {
24    type Error = crate::Error;
25
26    /// Creates a new [`yubihsm::Credentials`] from a [`FileBackedCredentials`].
27    ///
28    /// # Errors
29    ///
30    /// Returns an error if a [`Credentials`] cannot be created from the provided
31    /// [`FileBackedCredentials`].
32    fn try_from(value: &FileBackedCredentials) -> Result<Self, Self::Error> {
33        Ok(Self::from(&Credentials::try_from(value)?))
34    }
35}
36
37/// Credentials for a YubiHSM2 device.
38///
39/// Credentials are mapped to the authentication key ID and the passphrase used as key derivation
40/// function (KDF) for an authentication key.
41#[derive(Clone, Debug)]
42#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
43pub struct Credentials {
44    pub(crate) id: Id,
45    passphrase: Passphrase,
46}
47
48impl Credentials {
49    /// Creates a new [`Credentials`].
50    ///
51    /// # Examples
52    ///
53    /// ```
54    /// use signstar_crypto::passphrase::Passphrase;
55    /// use signstar_yubihsm2::Credentials;
56    ///
57    /// # fn main() -> testresult::TestResult {
58    /// let creds = Credentials::new("1".parse()?, "this-is-a-passphrase".parse()?);
59    /// # Ok(())
60    /// # }
61    /// ```
62    pub fn new(id: Id, passphrase: Passphrase) -> Self {
63        Self { id, passphrase }
64    }
65}
66
67impl UserWithPassphrase for Credentials {
68    fn user(&self) -> String {
69        self.id.to_string()
70    }
71
72    fn passphrase(&self) -> &Passphrase {
73        &self.passphrase
74    }
75}
76
77impl From<&Credentials> for yubihsm::Credentials {
78    fn from(value: &Credentials) -> Self {
79        Self::from_password(
80            value.id.into(),
81            value.passphrase.expose_borrowed().as_bytes(),
82        )
83    }
84}
85
86impl TryFrom<&FileBackedCredentials> for Credentials {
87    type Error = crate::Error;
88
89    /// Creates a new [`Credentials`] from a [`FileBackedCredentials`].
90    ///
91    /// # Errors
92    ///
93    /// Returns an error if a [`Passphrase`] cannot be read from the passphrase file path of the
94    /// provided [`FileBackedCredentials`].
95    fn try_from(value: &FileBackedCredentials) -> Result<Self, Self::Error> {
96        Ok(Credentials::new(
97            value.id,
98            Passphrase::try_from(value.passphrase_file.as_path())?,
99        ))
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use std::io::Write;
106
107    use tempfile::{NamedTempFile, TempDir};
108    use testresult::TestResult;
109
110    use super::*;
111
112    #[test]
113    fn credentials_user_with_passphrase() -> TestResult {
114        let credentials = Credentials::new("1".parse()?, Passphrase::generate(None));
115        assert_eq!(credentials.user(), "1");
116        assert_eq!(
117            credentials.passphrase().expose_borrowed().len(),
118            Passphrase::DEFAULT_LENGTH
119        );
120
121        Ok(())
122    }
123
124    /// Ensures, that a [`yubihsm::Credentials`] can be created from a [`FileBackedCredentials`].
125    #[test]
126    fn yubihsm_credentials_try_from_file_backed_credentials_succeeds() -> TestResult {
127        let temp_file = {
128            let mut temp_file = NamedTempFile::new()?;
129            temp_file.write_all("passphrase".as_bytes())?;
130            temp_file
131        };
132        let file_backed_credentials = FileBackedCredentials {
133            id: "1".parse()?,
134            passphrase_file: temp_file.path().to_path_buf(),
135        };
136        let _creds = yubihsm::Credentials::try_from(&file_backed_credentials)?;
137
138        Ok(())
139    }
140
141    /// Ensures, that a [`yubihsm::Credentials`] cannot be created from a [`FileBackedCredentials`]
142    /// tracking a directory.
143    #[test]
144    fn yubihsm_credentials_try_from_file_backed_credentials_fails_on_dir() -> TestResult {
145        let temp_file = TempDir::new()?;
146        let file_backed_credentials = FileBackedCredentials {
147            id: "1".parse()?,
148            passphrase_file: temp_file.path().to_path_buf(),
149        };
150        assert!(yubihsm::Credentials::try_from(&file_backed_credentials).is_err());
151
152        Ok(())
153    }
154}