use wolfhsm_sys::{
wolfhsm_ecc_export_public_der, wolfhsm_ecc_make_key, wolfhsm_ecc_shared_secret,
wolfhsm_ecc_sign, wolfhsm_ecc_verify,
};
use crate::client::Client;
use crate::error::Error;
use crate::key::{with_key, KeyId};
const ECC_SECP256R1: core::ffi::c_int = 1;
const ECC_P256_SPKI_DER_LEN: usize = 91;
pub struct EccP256Key {
pub(crate) id: KeyId,
}
impl EccP256Key {
pub(crate) fn generate(client: &mut Client) -> Result<Self, Error> {
let mut key_id: u16 = KeyId::ERASED.0;
let rc = unsafe { wolfhsm_ecc_make_key(client.ctx_ptr(), ECC_SECP256R1, &mut key_id) };
Error::check(rc, "wolfhsm_ecc_make_key")?;
if key_id == KeyId::ERASED.0 {
return Err(Error::ProtocolError {
msg: "wolfhsm_ecc_make_key: server returned WH_KEYID_ERASED (0)",
});
}
Ok(EccP256Key { id: KeyId(key_id) })
}
pub fn sign_digest(&self, client: &mut Client, digest: &[u8]) -> Result<Vec<u8>, Error> {
let hash_len = u16::try_from(digest.len()).map_err(|_| Error::BadArgs {
msg: "digest exceeds u16::MAX bytes",
})?;
let mut buf = vec![0u8; 128];
let mut sig_len: u16 = 128;
let rc = unsafe {
wolfhsm_ecc_sign(
client.ctx_ptr(),
self.id.0,
digest.as_ptr(),
hash_len,
buf.as_mut_ptr(),
&mut sig_len,
)
};
Error::check(rc, "wolfhsm_ecc_sign")?;
if sig_len as usize > buf.len() {
return Err(Error::ProtocolError {
msg: "wolfhsm_ecc_sign: server returned sig_len > buffer length",
});
}
buf.truncate(sig_len as usize);
Ok(buf)
}
pub fn verify_digest(
&self,
client: &mut Client,
digest: &[u8],
sig: &[u8],
) -> Result<(), Error> {
let hash_len = u16::try_from(digest.len()).map_err(|_| Error::BadArgs {
msg: "digest exceeds u16::MAX bytes",
})?;
let sig_len = u16::try_from(sig.len()).map_err(|_| Error::BadArgs {
msg: "signature exceeds u16::MAX bytes",
})?;
let mut result: core::ffi::c_int = 0;
let rc = unsafe {
wolfhsm_ecc_verify(
client.ctx_ptr(),
self.id.0,
digest.as_ptr(),
hash_len,
sig.as_ptr(),
sig_len,
&mut result,
)
};
Error::check(rc, "wolfhsm_ecc_verify")?;
if result != 1 {
return Err(Error::InvalidSignature);
}
Ok(())
}
pub fn public_key_der(&self, client: &mut Client) -> Result<Vec<u8>, Error> {
let mut buf = vec![0u8; ECC_P256_SPKI_DER_LEN];
let mut out_len: u32 = ECC_P256_SPKI_DER_LEN as u32;
let rc = unsafe {
wolfhsm_ecc_export_public_der(
client.ctx_ptr(),
self.id.0,
buf.as_mut_ptr(),
&mut out_len,
)
};
Error::check(rc, "wolfhsm_ecc_export_public_der")?;
if out_len as usize > buf.len() {
return Err(Error::ProtocolError {
msg: "wolfhsm_ecc_export_public_der: server returned out_len > buffer length",
});
}
buf.truncate(out_len as usize);
Ok(buf)
}
pub fn ecdh(
&self,
client: &mut Client,
peer_public_der: &[u8],
) -> Result<Vec<u8>, Error> {
let peer_der_len =
u32::try_from(peer_public_der.len()).map_err(|_| Error::BadArgs {
msg: "peer public key exceeds u32::MAX bytes",
})?;
let mut buf = vec![0u8; 32];
let mut out_len: u32 = 32;
let rc = unsafe {
wolfhsm_ecc_shared_secret(
client.ctx_ptr(),
self.id.0,
peer_public_der.as_ptr(),
peer_der_len,
buf.as_mut_ptr(),
&mut out_len,
)
};
Error::check(rc, "wolfhsm_ecc_shared_secret")?;
if out_len as usize > buf.len() {
return Err(Error::ProtocolError {
msg: "wolfhsm_ecc_shared_secret: server returned out_len > buffer length",
});
}
buf.truncate(out_len as usize);
Ok(buf)
}
}
impl Drop for EccP256Key {
fn drop(&mut self) {
if self.id != KeyId::ERASED {
log::warn!(
"wolfhsm: EccP256Key (id={}) dropped without eviction — \
HSM cache slot leaked. Use with_ecc_p256_key().",
self.id.0
);
}
}
}
impl Client {
pub fn with_ecc_p256_key<F, R>(&mut self, f: F) -> Result<R, Error>
where
F: FnOnce(&EccP256Key, &mut Client) -> Result<R, Error>,
{
let key = EccP256Key::generate(self)?;
with_key!(key, self, f)
}
}
pub struct EccP256Signer<'a> {
key: &'a EccP256Key,
client: std::cell::RefCell<&'a mut Client>,
}
impl EccP256Key {
pub fn signer<'a>(&'a self, client: &'a mut Client) -> EccP256Signer<'a> {
EccP256Signer {
key: self,
client: std::cell::RefCell::new(client),
}
}
}
impl<'a> signature::Signer<p256::ecdsa::DerSignature> for EccP256Signer<'a> {
fn try_sign(&self, msg: &[u8]) -> Result<p256::ecdsa::DerSignature, signature::Error> {
use sha2::Digest as _;
let digest = sha2::Sha256::digest(msg);
let mut client = self.client.borrow_mut();
let sig_bytes = self
.key
.sign_digest(&mut **client, &digest)
.map_err(|_| signature::Error::new())?;
p256::ecdsa::DerSignature::from_bytes(&sig_bytes).map_err(|_| signature::Error::new())
}
}