1use signstar_crypto::{
4 Error as SignstarCryptoError,
5 signer::{
6 error::Error as SignstarCryptoSignerError,
7 traits::{RawPublicKey, RawSigningKey},
8 },
9};
10use yubihsm::{
11 Connector,
12 UsbConfig,
13 asymmetric::Algorithm,
14 client::Client,
15 device::SerialNumber,
16 object::Id,
17};
18
19use crate::{Credentials, Error};
20
21pub struct YubiHsm2SigningKey {
23 yubihsm: Client,
24 key_id: Id,
25}
26
27impl YubiHsm2SigningKey {
28 pub fn new(client: Client, id: Id) -> Self {
30 Self {
31 yubihsm: client,
32 key_id: id,
33 }
34 }
35
36 pub fn close_session(&self) -> Result<(), crate::Error> {
42 self.yubihsm
43 .close_session()
44 .map_err(|source| crate::Error::Client {
45 context: "closing the session for a YubiHSM signing key implementation",
46 source,
47 })
48 }
49
50 #[cfg(feature = "_yubihsm2-mockhsm")]
65 pub fn mock(key_id: Id, credentials: &Credentials) -> Result<Self, Error> {
66 use signstar_crypto::{
67 openpgp::{OpenPgpKeyUsageFlags, OpenPgpUserId, OpenPgpVersion},
68 signer::openpgp::{Timestamp, generate_certificate},
69 traits::UserWithPassphrase as _,
70 };
71 use yubihsm::{
72 Capability,
73 Connector,
74 Credentials as YubiCredentials,
75 Domain,
76 asymmetric::Algorithm,
77 authentication,
78 client::Client,
79 opaque,
80 };
81
82 let connector = Connector::mockhsm();
83 let client =
84 Client::open(connector, Default::default(), true).map_err(|source| Error::Client {
85 context: "connecting to mockhsm",
86 source,
87 })?;
88 let auth_key = authentication::Key::derive_from_password(
89 credentials.passphrase().expose_borrowed().as_bytes(),
90 );
91 let domain = Domain::DOM1;
92 client
93 .put_authentication_key(
94 credentials.id(),
95 Default::default(),
96 domain,
97 Capability::empty(),
98 Capability::SIGN_EDDSA,
99 authentication::Algorithm::YubicoAes,
100 auth_key.clone(),
101 )
102 .map_err(|source| Error::Client {
103 context: "putting authentication key",
104 source,
105 })?;
106
107 let client = Client::open(
108 client.connector().clone(),
109 YubiCredentials::new(credentials.id(), auth_key),
110 true,
111 )
112 .map_err(|source| Error::Client {
113 context: "connecting to mockhsm",
114 source,
115 })?;
116
117 client
118 .generate_asymmetric_key(
119 key_id,
120 Default::default(),
121 domain,
122 Capability::SIGN_EDDSA,
123 Algorithm::Ed25519,
124 )
125 .map_err(|source| Error::Client {
126 context: "generating asymmetric key",
127 source,
128 })?;
129
130 let mut flags = OpenPgpKeyUsageFlags::default();
131 flags.set_sign();
132
133 let signer = Self {
134 yubihsm: client,
135 key_id,
136 };
137
138 let cert = generate_certificate(
139 &signer,
140 flags,
141 &[OpenPgpUserId::new("Test".to_owned()).expect("static user ID to be valid")],
142 Default::default(),
143 Timestamp::now(),
144 OpenPgpVersion::V4,
145 )
146 .map_err(|source| Error::CertificateGeneration {
147 context: "generating OpenPGP certificate",
148 source,
149 })?;
150
151 signer
152 .yubihsm
153 .put_opaque(
154 key_id,
155 Default::default(),
156 domain,
157 Capability::empty(),
158 opaque::Algorithm::Data,
159 cert,
160 )
161 .map_err(|source| Error::Client {
162 context: "putting generated certificate on the device",
163 source,
164 })?;
165
166 Ok(signer)
167 }
168
169 pub fn new_with_serial_number(
179 serial_number: SerialNumber,
180 key_id: Id,
181 credentials: &Credentials,
182 ) -> Result<Self, Error> {
183 let connector = Connector::usb(&UsbConfig {
184 serial: Some(serial_number),
185 timeout_ms: UsbConfig::DEFAULT_TIMEOUT_MILLIS,
186 });
187 let client =
188 Client::open(connector, credentials.into(), true).map_err(|source| Error::Client {
189 context: "connecting to a hardware device",
190 source,
191 })?;
192 Ok(Self {
193 yubihsm: client,
194 key_id,
195 })
196 }
197}
198
199impl std::fmt::Debug for YubiHsm2SigningKey {
200 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201 f.debug_struct("YubiHsm2SigningKey")
202 .field("key_id", &self.key_id)
203 .finish()
204 }
205}
206
207impl RawSigningKey for YubiHsm2SigningKey {
208 fn key_id(&self) -> String {
210 self.key_id.to_string()
211 }
212
213 fn sign(&self, digest: &[u8]) -> Result<Vec<Vec<u8>>, SignstarCryptoError> {
223 let sig = self
224 .yubihsm
225 .sign_ed25519(self.key_id, digest)
226 .map_err(|e| SignstarCryptoSignerError::Hsm {
227 context: "calling yubihsm::sign_ed25519",
228 source: Box::new(e),
229 })?;
230
231 Ok(vec![sig.r_bytes().into(), sig.s_bytes().into()])
232 }
233
234 fn certificate(&self) -> Result<Option<Vec<u8>>, SignstarCryptoError> {
245 Ok(Some(self.yubihsm.get_opaque(self.key_id).map_err(|e| {
246 SignstarCryptoSignerError::Hsm {
247 context: "retrieving the certificate for a signing key held in a YubiHSM2",
248 source: Box::new(e),
249 }
250 })?))
251 }
252
253 fn public(&self) -> Result<RawPublicKey, SignstarCryptoError> {
264 let pk = self.yubihsm.get_public_key(self.key_id).map_err(|e| {
265 SignstarCryptoSignerError::Hsm {
266 context: "retrieving the public key for a signing key held in a YubiHSM2",
267 source: Box::new(e),
268 }
269 })?;
270 if pk.algorithm != Algorithm::Ed25519 {
271 return Err(SignstarCryptoSignerError::InvalidPublicKeyData {
272 context: format!("algorithm of the HSM key {:?} is unsupported", pk.algorithm),
273 }
274 .into());
275 }
276 Ok(RawPublicKey::Ed25519(pk.bytes))
277 }
278}