use anyhow::{Context, Result};
use async_trait::async_trait;
use cryptoki::context::{CInitializeArgs, Pkcs11};
use cryptoki::object::{Attribute, AttributeType, ObjectHandle};
use cryptoki::session::{Session, UserType};
use cryptoki::mechanism::Mechanism;
use cryptoki::types::AuthPin;
use cryptoki::slot::Slot;
use std::sync::Arc;
use zeroize::Zeroize;
use super::CryptoProvider;
pub struct Pkcs11CryptoProvider {
pkcs11: Arc<Pkcs11>,
slot: Slot,
pin: String,
key_label: String,
private_key_handle: ObjectHandle,
public_key: Vec<u8>,
key_id: String,
context: Vec<u8>,
mac_base_key: [u8; 32],
}
impl Pkcs11CryptoProvider {
pub async fn new(
module_path: &str,
slot: u64,
pin: &str,
key_label: &str,
key_id: String,
context: Vec<u8>,
) -> Result<Self> {
let pkcs11 = Pkcs11::new(module_path)
.context("Failed to load PKCS#11 module")?;
pkcs11.initialize(CInitializeArgs::OsThreads)
.context("Failed to initialize PKCS#11")?;
let pkcs11 = Arc::new(pkcs11);
let slot_id = Slot::try_from(slot)
.context("Invalid slot number")?;
let session = pkcs11.open_rw_session(slot_id)
.context("Failed to open HSM session")?;
let auth_pin = AuthPin::new(pin.to_string());
session.login(UserType::User, Some(&auth_pin))
.context("Failed to authenticate with HSM")?;
let private_key_handle = Self::find_key_by_label(&session, key_label, true)
.context("Failed to find private key in HSM")?;
let public_key_handle = Self::find_key_by_label(&session, key_label, false)
.context("Failed to find public key in HSM")?;
let public_key = Self::extract_public_key(&session, public_key_handle)
.context("Failed to extract public key from HSM")?;
let mac_base_key = Self::derive_mac_base_key(&session, private_key_handle)
.context("Failed to derive MAC base key from HSM")?;
let _ = session.logout();
drop(session);
Ok(Self {
pkcs11,
slot: slot_id,
pin: pin.to_string(),
key_label: key_label.to_string(),
private_key_handle,
public_key,
key_id,
context,
mac_base_key,
})
}
fn find_key_by_label(
session: &Session,
label: &str,
is_private: bool,
) -> Result<ObjectHandle> {
let class = if is_private {
cryptoki::object::ObjectClass::PRIVATE_KEY
} else {
cryptoki::object::ObjectClass::PUBLIC_KEY
};
let template = vec![
Attribute::Class(class),
Attribute::Label(label.as_bytes().to_vec()),
];
let objects = session.find_objects(&template)
.context("HSM key search failed")?;
objects.first()
.copied()
.ok_or_else(|| anyhow::anyhow!("Key '{}' not found in HSM", label))
}
fn extract_public_key(
session: &Session,
public_key_handle: ObjectHandle,
) -> Result<Vec<u8>> {
let attributes = session.get_attributes(
public_key_handle,
&[AttributeType::EcPoint],
).context("Failed to read public key from HSM")?;
let ec_point = attributes.first()
.and_then(|attr| {
if let Attribute::EcPoint(point) = attr {
Some(point.clone())
} else {
None
}
})
.ok_or_else(|| anyhow::anyhow!("EC_POINT attribute not found"))?;
if ec_point.len() < 65 {
anyhow::bail!("Invalid EC_POINT length: {}", ec_point.len());
}
let point_data = if ec_point[0] == 0x04 && ec_point[1] == 0x41 {
&ec_point[2..] } else {
&ec_point[..]
};
if point_data.len() != 65 || point_data[0] != 0x04 {
anyhow::bail!("Expected uncompressed P-256 point (65 bytes)");
}
let x = &point_data[1..33];
let y = &point_data[33..65];
let prefix = if y[31] & 1 == 0 { 0x02 } else { 0x03 };
let mut compressed = vec![prefix];
compressed.extend_from_slice(x);
Ok(compressed)
}
fn derive_mac_base_key(
session: &Session,
private_key_handle: ObjectHandle,
) -> Result<[u8; 32]> {
let message = b"freebird-mac-base-key-derivation-v1";
use sha2::{Sha256, Digest};
let mut hasher = Sha256::new();
hasher.update(message);
let digest = hasher.finalize();
let mechanism = Mechanism::Ecdsa;
let signature = session.sign(&mechanism, private_key_handle, &digest)
.context("Failed to sign with HSM for MAC derivation")?;
let mut hasher = Sha256::new();
hasher.update(&signature);
let base_key = hasher.finalize();
let mut result = [0u8; 32];
result.copy_from_slice(&base_key);
Ok(result)
}
fn voprf_evaluate_internal(
&self,
_blinded: &[u8],
) -> Result<Vec<u8>> {
anyhow::bail!(
"PKCS#11 VOPRF evaluation not yet implemented. \
HSM-native scalar multiplication requires vendor-specific extensions. \
Consider using SoftwareCryptoProvider for now."
)
}
}
#[async_trait]
impl CryptoProvider for Pkcs11CryptoProvider {
async fn voprf_evaluate(&self, blinded: &[u8]) -> Result<Vec<u8>> {
self.voprf_evaluate_internal(blinded)
}
async fn derive_mac_key(
&self,
issuer_id: &str,
kid: &str,
epoch: u32,
) -> Result<[u8; 32]> {
Ok(crate::derive_mac_key_v2(
&self.mac_base_key,
issuer_id,
kid,
epoch,
))
}
fn public_key(&self) -> &[u8] {
&self.public_key
}
fn key_id(&self) -> &str {
&self.key_id
}
fn context(&self) -> &[u8] {
&self.context
}
}
impl Drop for Pkcs11CryptoProvider {
fn drop(&mut self) {
self.mac_base_key.zeroize();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
#[ignore] async fn test_pkcs11_provider_creation() {
let result = Pkcs11CryptoProvider::new(
"/usr/lib/softhsm/libsofthsm2.so",
0,
"1234",
"test-key",
"key-001".to_string(),
b"test-context".to_vec(),
).await;
assert!(result.is_err() || result.is_ok());
}
}