signstar_yubihsm2/
connection.rs1use 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#[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 #[cfg(feature = "_yubihsm2-mockhsm")]
18 Mock,
19
20 Usb {
25 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 #[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 #[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 #[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 #[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}