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 #[cfg(feature = "_yubihsm2-mockhsm")]
43 pub fn mock(key_id: Id, credentials: &Credentials) -> Result<Self, Error> {
44 use signstar_crypto::{
45 openpgp::{OpenPgpKeyUsageFlags, OpenPgpUserId, OpenPgpVersion},
46 signer::openpgp::{Timestamp, add_certificate},
47 traits::UserWithPassphrase as _,
48 };
49 use yubihsm::{
50 Capability,
51 Connector,
52 Credentials as YubiCredentials,
53 Domain,
54 asymmetric::Algorithm,
55 authentication,
56 client::Client,
57 opaque,
58 };
59
60 let connector = Connector::mockhsm();
61 let client =
62 Client::open(connector, Default::default(), true).map_err(|source| Error::Client {
63 context: "connecting to mockhsm",
64 source,
65 })?;
66 let auth_key = authentication::Key::derive_from_password(
67 credentials.passphrase().expose_borrowed().as_bytes(),
68 );
69 let domain = Domain::DOM1;
70 client
71 .put_authentication_key(
72 credentials.id(),
73 Default::default(),
74 domain,
75 Capability::empty(),
76 Capability::SIGN_EDDSA,
77 authentication::Algorithm::YubicoAes,
78 auth_key.clone(),
79 )
80 .map_err(|source| Error::Client {
81 context: "putting authentication key",
82 source,
83 })?;
84
85 let client = Client::open(
86 client.connector().clone(),
87 YubiCredentials::new(credentials.id(), auth_key),
88 true,
89 )
90 .map_err(|source| Error::Client {
91 context: "connecting to mockhsm",
92 source,
93 })?;
94
95 client
96 .generate_asymmetric_key(
97 key_id,
98 Default::default(),
99 domain,
100 Capability::SIGN_EDDSA,
101 Algorithm::Ed25519,
102 )
103 .map_err(|source| Error::Client {
104 context: "generating asymmetric key",
105 source,
106 })?;
107
108 let mut flags = OpenPgpKeyUsageFlags::default();
109 flags.set_sign();
110
111 let signer = Self {
112 yubihsm: client,
113 key_id,
114 };
115
116 let cert = add_certificate(
117 &signer,
118 flags,
119 &[OpenPgpUserId::new("Test".to_owned()).expect("static user ID to be valid")],
120 Timestamp::now(),
121 OpenPgpVersion::V4,
122 )
123 .map_err(|source| Error::CertificateGeneration {
124 context: "generating OpenPGP certificate",
125 source,
126 })?;
127
128 signer
129 .yubihsm
130 .put_opaque(
131 key_id,
132 Default::default(),
133 domain,
134 Capability::empty(),
135 opaque::Algorithm::Data,
136 cert,
137 )
138 .map_err(|source| Error::Client {
139 context: "putting generated certificate on the device",
140 source,
141 })?;
142
143 Ok(signer)
144 }
145
146 pub fn new_with_serial_number(
156 serial_number: SerialNumber,
157 key_id: Id,
158 credentials: &Credentials,
159 ) -> Result<Self, Error> {
160 let connector = Connector::usb(&UsbConfig {
161 serial: Some(serial_number),
162 timeout_ms: UsbConfig::DEFAULT_TIMEOUT_MILLIS,
163 });
164 let client =
165 Client::open(connector, credentials.into(), true).map_err(|source| Error::Client {
166 context: "connecting to a hardware device",
167 source,
168 })?;
169 Ok(Self {
170 yubihsm: client,
171 key_id,
172 })
173 }
174}
175
176impl std::fmt::Debug for YubiHsm2SigningKey {
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 f.debug_struct("YubiHsm2SigningKey")
179 .field("key_id", &self.key_id)
180 .finish()
181 }
182}
183
184impl RawSigningKey for YubiHsm2SigningKey {
185 fn key_id(&self) -> String {
187 self.key_id.to_string()
188 }
189
190 fn sign(&self, digest: &[u8]) -> Result<Vec<Vec<u8>>, SignstarCryptoError> {
200 let sig = self
201 .yubihsm
202 .sign_ed25519(self.key_id, digest)
203 .map_err(|e| SignstarCryptoSignerError::Hsm {
204 context: "calling yubihsm::sign_ed25519",
205 source: Box::new(e),
206 })?;
207
208 Ok(vec![sig.r_bytes().into(), sig.s_bytes().into()])
209 }
210
211 fn certificate(&self) -> Result<Option<Vec<u8>>, SignstarCryptoError> {
222 Ok(Some(self.yubihsm.get_opaque(self.key_id).map_err(|e| {
223 SignstarCryptoSignerError::Hsm {
224 context: "retrieving the certificate for a signing key held in a YubiHSM2",
225 source: Box::new(e),
226 }
227 })?))
228 }
229
230 fn public(&self) -> Result<RawPublicKey, SignstarCryptoError> {
241 let pk = self.yubihsm.get_public_key(self.key_id).map_err(|e| {
242 SignstarCryptoSignerError::Hsm {
243 context: "retrieving the public key for a signing key held in a YubiHSM2",
244 source: Box::new(e),
245 }
246 })?;
247 if pk.algorithm != Algorithm::Ed25519 {
248 return Err(SignstarCryptoSignerError::InvalidPublicKeyData {
249 context: format!("algorithm of the HSM key {:?} is unsupported", pk.algorithm),
250 }
251 .into());
252 }
253 Ok(RawPublicKey::Ed25519(pk.bytes))
254 }
255}