1use signstar_crypto::{
4 Error as SignstarCryptoError,
5 signer::{
6 error::Error as SignstarCryptoSignerError,
7 traits::{RawPublicKey, RawSigningKey},
8 },
9};
10use yubihsm::{Connector, UsbConfig, asymmetric::Algorithm, client::Client, device::SerialNumber};
11
12use crate::{Credentials, Error, object::Id};
13
14pub struct YubiHsm2SigningKey {
16 yubihsm: Client,
17 key_id: Id,
18}
19
20impl YubiHsm2SigningKey {
21 #[cfg(feature = "_yubihsm2-mockhsm")]
36 pub fn mock(key_id: Id, credentials: &Credentials) -> Result<Self, Error> {
37 use signstar_crypto::{
38 openpgp::{OpenPgpKeyUsageFlags, OpenPgpUserId, OpenPgpVersion},
39 signer::openpgp::{Timestamp, add_certificate},
40 traits::UserWithPassphrase as _,
41 };
42 use yubihsm::{
43 Capability,
44 Connector,
45 Credentials as YubiCredentials,
46 Domain,
47 asymmetric::Algorithm,
48 authentication,
49 client::Client,
50 opaque,
51 };
52
53 let connector = Connector::mockhsm();
54 let client =
55 Client::open(connector, Default::default(), true).map_err(|source| Error::Client {
56 context: "connecting to mockhsm",
57 source,
58 })?;
59 let auth_key = authentication::Key::derive_from_password(
60 credentials.passphrase().expose_borrowed().as_bytes(),
61 );
62 let domain = Domain::DOM1;
63 client
64 .put_authentication_key(
65 credentials.id.into(),
66 Default::default(),
67 domain,
68 Capability::empty(),
69 Capability::SIGN_EDDSA,
70 authentication::Algorithm::YubicoAes,
71 auth_key.clone(),
72 )
73 .map_err(|source| Error::Client {
74 context: "putting authentication key",
75 source,
76 })?;
77
78 let client = Client::open(
79 client.connector().clone(),
80 YubiCredentials::new(credentials.id.into(), auth_key),
81 true,
82 )
83 .map_err(|source| Error::Client {
84 context: "connecting to mockhsm",
85 source,
86 })?;
87
88 client
89 .generate_asymmetric_key(
90 key_id.into(),
91 Default::default(),
92 domain,
93 Capability::SIGN_EDDSA,
94 Algorithm::Ed25519,
95 )
96 .map_err(|source| Error::Client {
97 context: "generating asymmetric key",
98 source,
99 })?;
100
101 let mut flags = OpenPgpKeyUsageFlags::default();
102 flags.set_sign();
103
104 let signer = Self {
105 yubihsm: client,
106 key_id,
107 };
108
109 let cert = add_certificate(
110 &signer,
111 flags,
112 OpenPgpUserId::new("Test".to_owned()).expect("static user ID to be valid"),
113 Timestamp::now(),
114 OpenPgpVersion::V4,
115 )
116 .map_err(|source| Error::CertificateGeneration {
117 context: "generating OpenPGP certificate",
118 source,
119 })?;
120
121 signer
122 .yubihsm
123 .put_opaque(
124 key_id.into(),
125 Default::default(),
126 domain,
127 Capability::empty(),
128 opaque::Algorithm::Data,
129 cert,
130 )
131 .map_err(|source| Error::Client {
132 context: "putting generated certificate on the device",
133 source,
134 })?;
135
136 Ok(signer)
137 }
138
139 pub fn new_with_serial_number(
149 serial_number: SerialNumber,
150 key_id: Id,
151 credentials: &Credentials,
152 ) -> Result<Self, Error> {
153 let connector = Connector::usb(&UsbConfig {
154 serial: Some(serial_number),
155 timeout_ms: UsbConfig::DEFAULT_TIMEOUT_MILLIS,
156 });
157 let client =
158 Client::open(connector, credentials.into(), true).map_err(|source| Error::Client {
159 context: "connecting to a hardware device",
160 source,
161 })?;
162 Ok(Self {
163 yubihsm: client,
164 key_id,
165 })
166 }
167}
168
169impl std::fmt::Debug for YubiHsm2SigningKey {
170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171 f.debug_struct("YubiHsm2SigningKey")
172 .field("key_id", &self.key_id)
173 .finish()
174 }
175}
176
177impl RawSigningKey for YubiHsm2SigningKey {
178 fn key_id(&self) -> String {
180 self.key_id.to_string()
181 }
182
183 fn sign(&self, digest: &[u8]) -> Result<Vec<Vec<u8>>, SignstarCryptoError> {
193 let sig = self
194 .yubihsm
195 .sign_ed25519(self.key_id.into(), digest)
196 .map_err(|e| SignstarCryptoSignerError::Hsm {
197 context: "calling yubihsm::sign_ed25519",
198 source: Box::new(e),
199 })?;
200
201 Ok(vec![sig.r_bytes().into(), sig.s_bytes().into()])
202 }
203
204 fn certificate(&self) -> Result<Option<Vec<u8>>, SignstarCryptoError> {
215 Ok(Some(self.yubihsm.get_opaque(self.key_id.into()).map_err(
216 |e| SignstarCryptoSignerError::Hsm {
217 context: "retrieving the certificate for a signing key held in a YubiHSM2",
218 source: Box::new(e),
219 },
220 )?))
221 }
222
223 fn public(&self) -> Result<RawPublicKey, SignstarCryptoError> {
234 let pk = self
235 .yubihsm
236 .get_public_key(self.key_id.into())
237 .map_err(|e| SignstarCryptoSignerError::Hsm {
238 context: "retrieving the public key for a signing key held in a YubiHSM2",
239 source: Box::new(e),
240 })?;
241 if pk.algorithm != Algorithm::Ed25519 {
242 return Err(SignstarCryptoSignerError::InvalidPublicKeyData {
243 context: format!("algorithm of the HSM key {:?} is unsupported", pk.algorithm),
244 }
245 .into());
246 }
247 Ok(RawPublicKey::Ed25519(pk.bytes))
248 }
249}