Skip to main content

signstar_yubihsm2/
connection.rs

1use std::fmt::Debug;
2
3use log::{debug, error, warn};
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6use signstar_common::traits::BackendCheck;
7use yubihsm::{Client, Connector, Credentials, UsbConfig, client::ErrorKind};
8
9use crate::yubihsm::SerialNumber;
10
11/// A connection to a YubiHSM2.
12#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
13#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
14#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
15pub enum Connection {
16    /// Connection to a Mock HSM.
17    #[cfg(feature = "_yubihsm2-mockhsm")]
18    Mock,
19
20    /// Connection to a device over USB.
21    ///
22    /// Each YubiHSM2 is identified by a unique serial number.
23    /// This number is printed on the enclosure of the physical device.
24    Usb {
25        /// Serial number of the connected YubiHSM2.
26        serial_number: SerialNumber,
27    },
28}
29
30impl BackendCheck for Connection {
31    fn is_available(&self) -> bool {
32        let connector = match self {
33            #[cfg(feature = "_yubihsm2-mockhsm")]
34            Self::Mock => Connector::mockhsm(),
35            Self::Usb { serial_number } => Connector::usb(&UsbConfig {
36                serial: Some(*serial_number),
37                timeout_ms: 5000,
38            }),
39        };
40
41        if let Err(error) = connector.device_info() {
42            warn!(
43                "The YubiHSM2 connection {:?} is not available from this host: {error}",
44                self
45            );
46            return false;
47        }
48
49        debug!(
50            "The YubiHSM2 connection {:?} is available from this host.",
51            self
52        );
53
54        true
55    }
56
57    fn is_provisioned(&self) -> bool {
58        let connector = match self {
59            #[cfg(feature = "_yubihsm2-mockhsm")]
60            Self::Mock => Connector::mockhsm(),
61            Self::Usb { serial_number } => Connector::usb(&UsbConfig {
62                serial: Some(*serial_number),
63                timeout_ms: 5000,
64            }),
65        };
66
67        if let Err(error) = Client::open(connector, Credentials::default(), false) {
68            if error.kind() == &ErrorKind::AuthenticationError {
69                debug!(
70                    "Authentication against the YubiHSM2 backend {:?} using the default credentials failed, assuming it to be provisioned: {error}",
71                    self
72                );
73                return true;
74            }
75
76            error!(
77                "The connection to YubiHSM2 backend {:?} cannot be established due to an error: {error}",
78                self
79            );
80            return false;
81        }
82
83        warn!(
84            "Authentication against the YubiHSM2 backend {:?} using the default credentials succeeded, assuming it to be unprovisioned.",
85            self
86        );
87        false
88    }
89}
90
91impl From<&Connection> for Connector {
92    fn from(value: &Connection) -> Self {
93        match value {
94            #[cfg(feature = "_yubihsm2-mockhsm")]
95            Connection::Mock => Connector::mockhsm(),
96            Connection::Usb { serial_number } => Connector::usb(&UsbConfig {
97                serial: Some(*serial_number),
98                timeout_ms: UsbConfig::DEFAULT_TIMEOUT_MILLIS,
99            }),
100        }
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use log::LevelFilter;
107    use signstar_common::logging::setup_logging;
108    use testresult::TestResult;
109
110    use super::*;
111
112    /// Ensures, that [`Connection::is_available`] succeeds, when using [`Connection::Mock`].
113    #[cfg(feature = "_yubihsm2-mockhsm")]
114    #[test]
115    fn connection_is_available_succeeds_with_mockhsm() -> TestResult {
116        setup_logging(LevelFilter::Debug)?;
117        let connection = Connection::Mock;
118        assert!(connection.is_available());
119
120        Ok(())
121    }
122
123    /// Ensures, that [`Connection::is_available`] fails, when using a [`Connection::Usb`] with
124    /// serial number `0012345678`.
125    #[test]
126    fn connection_is_available_fails_with_hardware() -> TestResult {
127        setup_logging(LevelFilter::Debug)?;
128        let connection = Connection::Usb {
129            serial_number: "0012345678".parse()?,
130        };
131        assert!(!connection.is_available());
132
133        Ok(())
134    }
135
136    /// Ensures, that [`Connection::uses_default_credentials`] returns `false`, when using
137    /// a default [`Connection::Mock`].
138    #[cfg(feature = "_yubihsm2-mockhsm")]
139    #[test]
140    fn connection_uses_default_credentials_returns_false_with_mockhsm() -> TestResult {
141        setup_logging(LevelFilter::Debug)?;
142        let connection = Connection::Mock;
143        assert!(!connection.is_provisioned());
144
145        Ok(())
146    }
147
148    /// Ensures, that [`Connection::uses_default_credentials`] returns `false`, when using a
149    /// [`Connection::Usb`] with serial number `0012345678`.
150    #[test]
151    fn connection_uses_default_credentials_returns_false_with_hardware() -> TestResult {
152        setup_logging(LevelFilter::Debug)?;
153        let connection = Connection::Usb {
154            serial_number: "0012345678".parse()?,
155        };
156        assert!(!connection.is_provisioned());
157
158        Ok(())
159    }
160}