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