use std::fmt;
use serde::Deserialize;
use super::net_handle;
#[derive(Clone)]
pub struct HsmAuth {
auth_key_id: u16,
password: String,
}
impl HsmAuth {
pub fn new(password: impl Into<String>) -> Self {
HsmAuth::with_key(1, password)
}
pub fn with_key(auth_key_id: u16, password: impl Into<String>) -> Self {
HsmAuth {
auth_key_id,
password: password.into(),
}
}
pub fn auth_key_id(&self) -> u16 {
self.auth_key_id
}
}
impl fmt::Debug for HsmAuth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HsmAuth")
.field("auth_key_id", &self.auth_key_id)
.field("password", &"<redacted>")
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[non_exhaustive]
pub struct HsmInfo {
pub firmware: String,
pub serial: u64,
pub log_used: u32,
pub log_size: u32,
#[serde(default)]
pub part_number: Option<String>,
#[serde(default)]
pub algorithms: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[non_exhaustive]
pub struct HsmObject {
pub id: u16,
#[serde(rename = "type")]
pub object_type: String,
#[serde(default)]
pub label: String,
#[serde(default)]
pub algorithm: Option<String>,
#[serde(default)]
pub domains: Vec<u8>,
#[serde(default)]
pub capabilities: Vec<String>,
#[serde(default)]
pub size: u32,
#[serde(default)]
pub sequence: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GenerateKey {
pub algorithm: String,
pub object_id: u16,
pub label: String,
pub domains: Vec<u8>,
pub capabilities: Vec<String>,
}
impl GenerateKey {
pub fn new(algorithm: impl Into<String>) -> Self {
GenerateKey {
algorithm: algorithm.into(),
object_id: 0,
label: String::new(),
domains: Vec::new(),
capabilities: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[non_exhaustive]
pub struct GeneratedKey {
pub id: u16,
pub public_key_pem: String,
}
pub(crate) mod ops {
use std::time::Duration;
use serde::Deserialize;
use serde_json::{json, Value};
use super::{GenerateKey, GeneratedKey, HsmAuth, HsmInfo, HsmObject};
use crate::error::{Error, Result};
use crate::nets::debug::{base64_decode, base64_encode};
use crate::wire::{net_command, unit, value_as, CommandResponse, Op, Timeout};
const ROLE: &str = "hsm";
fn budget() -> Timeout {
Timeout::After(Duration::from_secs(30))
}
fn with_auth(auth: &HsmAuth, mut params: Value) -> Value {
params["auth_key_id"] = json!(auth.auth_key_id);
params["password"] = json!(auth.password);
params
}
fn cmd<T>(
name: &str,
action: &str,
params: Value,
timeout: Timeout,
parse: fn(CommandResponse) -> Result<T>,
) -> Op<T> {
Op {
req: net_command(name, ROLE, action, params, timeout),
parse,
}
}
fn parse_string(resp: CommandResponse) -> Result<String> {
match resp.value {
Some(Value::String(s)) => Ok(s),
other => Err(Error::Decode(format!(
"expected string 'value', got {other:?}"
))),
}
}
#[derive(Deserialize)]
struct Signature {
signature: String,
}
fn parse_signature(resp: CommandResponse) -> Result<Vec<u8>> {
let sig: Signature = value_as(resp)?;
base64_decode(&sig.signature)
.ok_or_else(|| Error::Decode("'signature' is not valid base64".to_string()))
}
pub(crate) fn info(name: &str) -> Op<HsmInfo> {
cmd(name, "info", json!({}), budget(), value_as::<HsmInfo>)
}
pub(crate) fn blink(name: &str, auth: &HsmAuth, seconds: u8) -> Op<()> {
cmd(
name,
"blink",
with_auth(auth, json!({ "seconds": seconds })),
Timeout::After(Duration::from_secs(u64::from(seconds) + 30)),
unit,
)
}
pub(crate) fn objects(
name: &str,
auth: &HsmAuth,
object_type: Option<&str>,
label: Option<&str>,
) -> Op<Vec<HsmObject>> {
let mut params = json!({});
if let Some(t) = object_type {
params["type"] = json!(t);
}
if let Some(l) = label {
params["label"] = json!(l);
}
cmd(
name,
"objects",
with_auth(auth, params),
budget(),
value_as::<Vec<HsmObject>>,
)
}
pub(crate) fn delete(
name: &str,
auth: &HsmAuth,
object_id: u16,
object_type: Option<&str>,
) -> Op<()> {
let mut params = json!({ "object_id": object_id });
if let Some(t) = object_type {
params["object_type"] = json!(t);
}
cmd(name, "delete", with_auth(auth, params), budget(), unit)
}
pub(crate) fn generate(name: &str, auth: &HsmAuth, key: &GenerateKey) -> Op<GeneratedKey> {
let mut params = json!({
"algorithm": key.algorithm,
"object_id": key.object_id,
"label": key.label,
});
if !key.domains.is_empty() {
params["domains"] = json!(key.domains);
}
if !key.capabilities.is_empty() {
params["capabilities"] = json!(key.capabilities);
}
cmd(
name,
"generate",
with_auth(auth, params),
budget(),
value_as::<GeneratedKey>,
)
}
pub(crate) fn public_key(name: &str, auth: &HsmAuth, object_id: u16) -> Op<String> {
cmd(
name,
"pubkey",
with_auth(auth, json!({ "object_id": object_id })),
budget(),
parse_string,
)
}
pub(crate) fn sign(
name: &str,
auth: &HsmAuth,
object_id: u16,
data: &[u8],
mechanism: &str,
) -> Op<Vec<u8>> {
let params = json!({
"object_id": object_id,
"data": base64_encode(data),
"mechanism": mechanism,
});
cmd(
name,
"sign",
with_auth(auth, params),
budget(),
parse_signature,
)
}
pub(crate) fn reset(name: &str, auth: &HsmAuth) -> Op<()> {
cmd(
name,
"reset",
with_auth(auth, json!({})),
Timeout::After(Duration::from_secs(75)),
unit,
)
}
}
net_handle! {
sync: Hsm,
async: AsyncHsm,
methods: {
fn info() -> HsmInfo = ops::info;
fn blink(auth: &HsmAuth, seconds: u8) -> () = ops::blink;
fn objects(auth: &HsmAuth, object_type: Option<&str>, label: Option<&str>) -> Vec<HsmObject> = ops::objects;
fn delete(auth: &HsmAuth, object_id: u16, object_type: Option<&str>) -> () = ops::delete;
fn generate(auth: &HsmAuth, key: &GenerateKey) -> GeneratedKey = ops::generate;
fn public_key(auth: &HsmAuth, object_id: u16) -> String = ops::public_key;
fn sign(auth: &HsmAuth, object_id: u16, data: &[u8], mechanism: &str) -> Vec<u8> = ops::sign;
fn reset(auth: &HsmAuth) -> () = ops::reset;
}
}