signstar_yubihsm2/
signer.rs1use signstar_crypto::{
4 Error as SignstarCryptoError,
5 signer::{
6 error::Error as SignstarCryptoSignerError,
7 traits::{RawPublicKey, RawSigningKey},
8 },
9};
10use yubihsm::{
11 Connector,
12 HttpConfig,
13 UsbConfig,
14 asymmetric::Algorithm,
15 client::Client,
16 device::SerialNumber,
17 object::Id,
18};
19
20use crate::{
21 Credentials,
22 Error,
23 connection::{DEFAULT_HTTP_TIMEOUT_MILLIS, YubiHsmUrl},
24};
25
26pub struct YubiHsm2SigningKey {
28 yubihsm: Client,
29 key_id: Id,
30}
31
32impl YubiHsm2SigningKey {
33 pub fn new(client: Client, id: Id) -> Self {
35 Self {
36 yubihsm: client,
37 key_id: id,
38 }
39 }
40
41 pub fn client(&self) -> &Client {
43 &self.yubihsm
44 }
45
46 pub fn close_session(&self) -> Result<(), crate::Error> {
52 self.yubihsm
53 .close_session()
54 .map_err(|source| crate::Error::Client {
55 context: "closing the session for a YubiHSM signing key implementation",
56 source,
57 })
58 }
59
60 #[cfg(feature = "_yubihsm2-mockhsm")]
75 pub fn mock(key_id: Id, credentials: &Credentials) -> Result<Self, Error> {
76 use signstar_crypto::traits::UserWithPassphrase as _;
77 use yubihsm::{Credentials as YubiCredentials, authentication};
78
79 use crate::mock::setup_single_key_in_mockhsm;
80
81 let connector = setup_single_key_in_mockhsm(key_id, credentials)?;
82
83 let auth_key = authentication::Key::derive_from_password(
84 credentials.passphrase().expose_borrowed().as_bytes(),
85 );
86 let client = Client::open(
87 connector,
88 YubiCredentials::new(credentials.id(), auth_key),
89 true,
90 )
91 .map_err(|source| Error::Client {
92 context: "connecting to mockhsm",
93 source,
94 })?;
95 Ok(Self::new(client, key_id))
96 }
97
98 pub fn new_remote(
106 url: YubiHsmUrl,
107 key_id: Id,
108 credentials: &Credentials,
109 ) -> Result<Self, Error> {
110 let connector = Connector::http(&HttpConfig {
111 addr: url.host().into(),
112 port: url.port(),
113 tls: url.scheme() == "https",
114 timeout_ms: DEFAULT_HTTP_TIMEOUT_MILLIS,
115 });
116 let client =
117 Client::open(connector, credentials.into(), true).map_err(|source| Error::Client {
118 context: "connecting to a hardware device",
119 source,
120 })?;
121 Ok(Self {
122 yubihsm: client,
123 key_id,
124 })
125 }
126
127 pub fn new_with_serial_number(
137 serial_number: SerialNumber,
138 key_id: Id,
139 credentials: &Credentials,
140 ) -> Result<Self, Error> {
141 let connector = Connector::usb(&UsbConfig {
142 serial: Some(serial_number),
143 timeout_ms: UsbConfig::DEFAULT_TIMEOUT_MILLIS,
144 });
145 let client =
146 Client::open(connector, credentials.into(), true).map_err(|source| Error::Client {
147 context: "connecting to a hardware device",
148 source,
149 })?;
150 Ok(Self {
151 yubihsm: client,
152 key_id,
153 })
154 }
155}
156
157impl std::fmt::Debug for YubiHsm2SigningKey {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 f.debug_struct("YubiHsm2SigningKey")
160 .field("key_id", &self.key_id)
161 .finish()
162 }
163}
164
165impl RawSigningKey for YubiHsm2SigningKey {
166 fn key_id(&self) -> String {
168 self.key_id.to_string()
169 }
170
171 fn sign(&self, digest: &[u8]) -> Result<Vec<Vec<u8>>, SignstarCryptoError> {
181 let sig = self
182 .yubihsm
183 .sign_ed25519(self.key_id, digest)
184 .map_err(|e| SignstarCryptoSignerError::Hsm {
185 context: "calling yubihsm::sign_ed25519",
186 source: Box::new(e),
187 })?;
188
189 Ok(vec![sig.r_bytes().into(), sig.s_bytes().into()])
190 }
191
192 fn certificate(&self) -> Result<Option<Vec<u8>>, SignstarCryptoError> {
203 Ok(Some(self.yubihsm.get_opaque(self.key_id).map_err(|e| {
204 SignstarCryptoSignerError::Hsm {
205 context: "retrieving the certificate for a signing key held in a YubiHSM2",
206 source: Box::new(e),
207 }
208 })?))
209 }
210
211 fn public(&self) -> Result<RawPublicKey, SignstarCryptoError> {
222 let pk = self.yubihsm.get_public_key(self.key_id).map_err(|e| {
223 SignstarCryptoSignerError::Hsm {
224 context: "retrieving the public key for a signing key held in a YubiHSM2",
225 source: Box::new(e),
226 }
227 })?;
228 if pk.algorithm != Algorithm::Ed25519 {
229 return Err(SignstarCryptoSignerError::InvalidPublicKeyData {
230 context: format!("algorithm of the HSM key {:?} is unsupported", pk.algorithm),
231 }
232 .into());
233 }
234 Ok(RawPublicKey::Ed25519(pk.bytes))
235 }
236}