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