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