1use std::fmt;
29
30use serde::Deserialize;
31
32use super::net_handle;
33
34#[derive(Clone)]
37pub struct HsmAuth {
38 auth_key_id: u16,
39 password: String,
40}
41
42impl HsmAuth {
43 pub fn new(password: impl Into<String>) -> Self {
45 HsmAuth::with_key(1, password)
46 }
47
48 pub fn with_key(auth_key_id: u16, password: impl Into<String>) -> Self {
50 HsmAuth {
51 auth_key_id,
52 password: password.into(),
53 }
54 }
55
56 pub fn auth_key_id(&self) -> u16 {
58 self.auth_key_id
59 }
60}
61
62impl fmt::Debug for HsmAuth {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 f.debug_struct("HsmAuth")
65 .field("auth_key_id", &self.auth_key_id)
66 .field("password", &"<redacted>")
67 .finish()
68 }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
73#[non_exhaustive]
74pub struct HsmInfo {
75 pub firmware: String,
77 pub serial: u64,
79 pub log_used: u32,
81 pub log_size: u32,
83 #[serde(default)]
85 pub part_number: Option<String>,
86 #[serde(default)]
88 pub algorithms: Vec<String>,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
93#[non_exhaustive]
94pub struct HsmObject {
95 pub id: u16,
97 #[serde(rename = "type")]
99 pub object_type: String,
100 #[serde(default)]
102 pub label: String,
103 #[serde(default)]
105 pub algorithm: Option<String>,
106 #[serde(default)]
108 pub domains: Vec<u8>,
109 #[serde(default)]
111 pub capabilities: Vec<String>,
112 #[serde(default)]
114 pub size: u32,
115 #[serde(default)]
117 pub sequence: u32,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct GenerateKey {
123 pub algorithm: String,
126 pub object_id: u16,
128 pub label: String,
130 pub domains: Vec<u8>,
132 pub capabilities: Vec<String>,
134}
135
136impl GenerateKey {
137 pub fn new(algorithm: impl Into<String>) -> Self {
139 GenerateKey {
140 algorithm: algorithm.into(),
141 object_id: 0,
142 label: String::new(),
143 domains: Vec::new(),
144 capabilities: Vec::new(),
145 }
146 }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
151#[non_exhaustive]
152pub struct GeneratedKey {
153 pub id: u16,
155 pub public_key_pem: String,
157}
158
159pub(crate) mod ops {
160 use std::time::Duration;
161
162 use serde::Deserialize;
163 use serde_json::{json, Value};
164
165 use super::{GenerateKey, GeneratedKey, HsmAuth, HsmInfo, HsmObject};
166 use crate::error::{Error, Result};
167 use crate::nets::debug::{base64_decode, base64_encode};
168 use crate::wire::{net_command, unit, value_as, CommandResponse, Op, Timeout};
169
170 const ROLE: &str = "hsm";
171
172 fn budget() -> Timeout {
174 Timeout::After(Duration::from_secs(30))
175 }
176
177 fn with_auth(auth: &HsmAuth, mut params: Value) -> Value {
179 params["auth_key_id"] = json!(auth.auth_key_id);
180 params["password"] = json!(auth.password);
181 params
182 }
183
184 fn cmd<T>(
185 name: &str,
186 action: &str,
187 params: Value,
188 timeout: Timeout,
189 parse: fn(CommandResponse) -> Result<T>,
190 ) -> Op<T> {
191 Op {
192 req: net_command(name, ROLE, action, params, timeout),
193 parse,
194 }
195 }
196
197 fn parse_string(resp: CommandResponse) -> Result<String> {
198 match resp.value {
199 Some(Value::String(s)) => Ok(s),
200 other => Err(Error::Decode(format!(
201 "expected string 'value', got {other:?}"
202 ))),
203 }
204 }
205
206 #[derive(Deserialize)]
207 struct Signature {
208 signature: String,
209 }
210
211 fn parse_signature(resp: CommandResponse) -> Result<Vec<u8>> {
212 let sig: Signature = value_as(resp)?;
213 base64_decode(&sig.signature)
214 .ok_or_else(|| Error::Decode("'signature' is not valid base64".to_string()))
215 }
216
217 pub(crate) fn info(name: &str) -> Op<HsmInfo> {
218 cmd(name, "info", json!({}), budget(), value_as::<HsmInfo>)
219 }
220
221 pub(crate) fn blink(name: &str, auth: &HsmAuth, seconds: u8) -> Op<()> {
222 cmd(
223 name,
224 "blink",
225 with_auth(auth, json!({ "seconds": seconds })),
226 Timeout::After(Duration::from_secs(u64::from(seconds) + 30)),
228 unit,
229 )
230 }
231
232 pub(crate) fn objects(
233 name: &str,
234 auth: &HsmAuth,
235 object_type: Option<&str>,
236 label: Option<&str>,
237 ) -> Op<Vec<HsmObject>> {
238 let mut params = json!({});
239 if let Some(t) = object_type {
240 params["type"] = json!(t);
241 }
242 if let Some(l) = label {
243 params["label"] = json!(l);
244 }
245 cmd(
246 name,
247 "objects",
248 with_auth(auth, params),
249 budget(),
250 value_as::<Vec<HsmObject>>,
251 )
252 }
253
254 pub(crate) fn delete(
255 name: &str,
256 auth: &HsmAuth,
257 object_id: u16,
258 object_type: Option<&str>,
259 ) -> Op<()> {
260 let mut params = json!({ "object_id": object_id });
261 if let Some(t) = object_type {
262 params["object_type"] = json!(t);
263 }
264 cmd(name, "delete", with_auth(auth, params), budget(), unit)
265 }
266
267 pub(crate) fn generate(name: &str, auth: &HsmAuth, key: &GenerateKey) -> Op<GeneratedKey> {
268 let mut params = json!({
269 "algorithm": key.algorithm,
270 "object_id": key.object_id,
271 "label": key.label,
272 });
273 if !key.domains.is_empty() {
274 params["domains"] = json!(key.domains);
275 }
276 if !key.capabilities.is_empty() {
277 params["capabilities"] = json!(key.capabilities);
278 }
279 cmd(
280 name,
281 "generate",
282 with_auth(auth, params),
283 budget(),
284 value_as::<GeneratedKey>,
285 )
286 }
287
288 pub(crate) fn public_key(name: &str, auth: &HsmAuth, object_id: u16) -> Op<String> {
289 cmd(
290 name,
291 "pubkey",
292 with_auth(auth, json!({ "object_id": object_id })),
293 budget(),
294 parse_string,
295 )
296 }
297
298 pub(crate) fn sign(
299 name: &str,
300 auth: &HsmAuth,
301 object_id: u16,
302 data: &[u8],
303 mechanism: &str,
304 ) -> Op<Vec<u8>> {
305 let params = json!({
306 "object_id": object_id,
307 "data": base64_encode(data),
308 "mechanism": mechanism,
309 });
310 cmd(
311 name,
312 "sign",
313 with_auth(auth, params),
314 budget(),
315 parse_signature,
316 )
317 }
318
319 pub(crate) fn reset(name: &str, auth: &HsmAuth) -> Op<()> {
320 cmd(
321 name,
322 "reset",
323 with_auth(auth, json!({})),
324 Timeout::After(Duration::from_secs(75)),
326 unit,
327 )
328 }
329}
330
331net_handle! {
332 sync: Hsm,
334 async: AsyncHsm,
335 methods: {
336 fn info() -> HsmInfo = ops::info;
339 fn blink(auth: &HsmAuth, seconds: u8) -> () = ops::blink;
341 fn objects(auth: &HsmAuth, object_type: Option<&str>, label: Option<&str>) -> Vec<HsmObject> = ops::objects;
344 fn delete(auth: &HsmAuth, object_id: u16, object_type: Option<&str>) -> () = ops::delete;
346 fn generate(auth: &HsmAuth, key: &GenerateKey) -> GeneratedKey = ops::generate;
349 fn public_key(auth: &HsmAuth, object_id: u16) -> String = ops::public_key;
351 fn sign(auth: &HsmAuth, object_id: u16, data: &[u8], mechanism: &str) -> Vec<u8> = ops::sign;
356 fn reset(auth: &HsmAuth) -> () = ops::reset;
360 }
361}