use std::{ffi::CString, ptr};
use nss_sys::nspr::{PR_FALSE, PR_TRUE};
use nss_sys::{SECItemStr, SECItemType, SECStatus};
use super::ensure_nss_init;
use crate::crypto::{ErasedCertificateSigner, PrivateKeyError};
use crate::oids;
use super::ffi::{
PK11_Authenticate, PK11_FindSlotByName, PK11_FreeSlot, PK11_ListPrivKeysInSlot, PK11_Sign,
PK11_SignatureLen, SECITEM_FreeItem, SECKEYPrivateKeyList, SECKEYPrivateKeyListNode,
SECKEYPrivateKeyStr, SECKEY_ConvertToPublicKey, SECKEY_CopyPrivateKey,
SECKEY_DestroyPrivateKey, SECKEY_DestroyPrivateKeyList, SECKEY_DestroyPublicKey,
SECKEY_EncodeDERSubjectPublicKeyInfo, SECOidTag, SEC_SignData,
SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE,
SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_ED25519_SIGNATURE, SEC_OID_ML_DSA_44,
SEC_OID_ML_DSA_65, SEC_OID_ML_DSA_87, SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION,
};
unsafe fn take_first_key(list: *mut SECKEYPrivateKeyList) -> Option<*mut SECKEYPrivateKeyStr> {
let head = (*list).list.next as *const SECKEYPrivateKeyListNode;
let sentinel = std::ptr::addr_of!((*list).list) as *const _;
if std::ptr::eq(head as *const _, sentinel) {
return None; }
let key = SECKEY_CopyPrivateKey((*head).key);
if key.is_null() {
None
} else {
Some(key)
}
}
fn sig_alg_der_from_spki(spki_der: &[u8], hash_algo: &str) -> Option<Vec<u8>> {
use synta::{Decoder, Encoding};
let mut dec = Decoder::new(spki_der, Encoding::Der);
dec.read_tag().ok()?;
dec.read_length().ok()?.definite().ok()?;
let alg_start = dec.position();
dec.read_tag().ok()?;
let alg_content_len = dec.read_length().ok()?.definite().ok()?;
let alg_end = dec.position() + alg_content_len;
let mut alg_dec = Decoder::new(&spki_der[alg_start..alg_end], Encoding::Der);
alg_dec.read_tag().ok()?;
alg_dec.read_length().ok()?.definite().ok()?;
let key_oid: synta::ObjectIdentifier = alg_dec.decode().ok()?;
crate::signing_algorithm_der(&key_oid, hash_algo)
}
fn sig_alg_der_to_nss_tag(sig_alg_der: &[u8]) -> Option<SECOidTag> {
use crate::crypto::utils::split_alg_id;
let (oid, _, _) = split_alg_id(sig_alg_der, |_| ()).ok()?;
match oid.components() {
c if c == oids::ECDSA_WITH_SHA256 => Some(SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE),
c if c == oids::ECDSA_WITH_SHA384 => Some(SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE),
c if c == oids::ECDSA_WITH_SHA512 => Some(SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE),
c if c == oids::SHA256_WITH_RSA => Some(SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION),
c if c == oids::SHA384_WITH_RSA => Some(SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION),
c if c == oids::SHA512_WITH_RSA => Some(SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION),
c if c == oids::ED25519 => Some(SEC_OID_ED25519_SIGNATURE),
c if c == oids::ML_DSA_44 => Some(SEC_OID_ML_DSA_44),
c if c == oids::ML_DSA_65 => Some(SEC_OID_ML_DSA_65),
c if c == oids::ML_DSA_87 => Some(SEC_OID_ML_DSA_87),
_ => None,
}
}
fn do_hsm_sign(
object_label: &str,
pin: Option<&str>,
token_label: Option<&str>,
tbs_der: &[u8],
nss_alg_tag: SECOidTag,
) -> Result<Vec<u8>, super::signing::NssSignerError> {
use super::signing::NssSignerError;
if !ensure_nss_init() {
return Err(NssSignerError("NSS initialisation failed".to_string()));
}
let label = token_label.filter(|l| !l.is_empty()).ok_or_else(|| {
NssSignerError(
"NSS HSM signing requires a non-empty 'token=' attribute in the PKCS#11 URI"
.to_string(),
)
})?;
let label_c = CString::new(label)
.map_err(|e| NssSignerError(format!("token label contains NUL: {}", e)))?;
let slot = unsafe { PK11_FindSlotByName(label_c.as_ptr()) };
if slot.is_null() {
return Err(NssSignerError(format!(
"token '{}' not found in NSS",
label
)));
}
if let Some(pin) = pin {
let pin_c =
CString::new(pin).map_err(|e| NssSignerError(format!("PIN contains NUL: {}", e)))?;
unsafe { PK11_Authenticate(slot, PR_TRUE, pin_c.as_ptr()) };
}
let nickname_c = CString::new(object_label)
.map_err(|e| NssSignerError(format!("object label contains NUL: {}", e)))?;
let key_list =
unsafe { PK11_ListPrivKeysInSlot(slot, nickname_c.as_ptr() as *mut _, ptr::null()) };
unsafe { PK11_FreeSlot(slot) };
if key_list.is_null() {
return Err(NssSignerError(format!(
"PK11_ListPrivKeysInSlot returned NULL for key '{}' in token '{}'",
object_label, label
)));
}
let priv_key = unsafe { take_first_key(key_list) };
unsafe { SECKEY_DestroyPrivateKeyList(key_list) };
let priv_key = priv_key.ok_or_else(|| {
NssSignerError(format!(
"key '{}' not found in token '{}'; ensure the PKCS#11 module is registered",
object_label, label
))
})?;
let sig = if nss_alg_tag == SEC_OID_ED25519_SIGNATURE {
let sig_len = unsafe { PK11_SignatureLen(priv_key) };
if sig_len <= 0 {
unsafe { SECKEY_DestroyPrivateKey(priv_key) };
return Err(NssSignerError(
"PK11_SignatureLen returned non-positive value".to_string(),
));
}
let mut sig_buf = vec![0u8; sig_len as usize];
let msg_item = SECItemStr {
type_: SECItemType::siBuffer,
data: tbs_der.as_ptr() as *mut _,
len: tbs_der.len() as u32,
};
let mut sig_item = SECItemStr {
type_: SECItemType::siBuffer,
data: sig_buf.as_mut_ptr(),
len: sig_len as u32,
};
let status = unsafe { PK11_Sign(priv_key, &mut sig_item, &msg_item) };
unsafe { SECKEY_DestroyPrivateKey(priv_key) };
if status != SECStatus::SECSuccess {
return Err(NssSignerError("PK11_Sign (Ed25519 HSM) failed".to_string()));
}
sig_buf.truncate(sig_item.len as usize);
sig_buf
} else {
let mut sig_item = SECItemStr {
type_: SECItemType::siBuffer,
data: ptr::null_mut(),
len: 0,
};
let status = unsafe {
SEC_SignData(
&mut sig_item,
tbs_der.as_ptr(),
tbs_der.len() as std::ffi::c_int,
priv_key,
nss_alg_tag,
)
};
unsafe { SECKEY_DestroyPrivateKey(priv_key) };
if status != SECStatus::SECSuccess {
return Err(NssSignerError("SEC_SignData (HSM) failed".to_string()));
}
let sig =
unsafe { std::slice::from_raw_parts(sig_item.data, sig_item.len as usize).to_vec() };
unsafe { SECITEM_FreeItem(&mut sig_item, PR_FALSE) };
sig
};
Ok(sig)
}
pub(crate) struct NssHsmSigner {
object_label: String,
pin: Option<String>,
token_label: Option<String>,
sig_alg_der: Vec<u8>,
nss_alg_tag: SECOidTag,
}
pub(crate) fn nss_hsm_signer(
pkcs11: &crate::pkcs11_uri::Pkcs11Uri,
algorithm: &str,
spki_der: &[u8],
) -> Box<dyn ErasedCertificateSigner> {
let Some(object_label) = pkcs11.attrs.object.as_deref() else {
return Box::new(crate::nss_backend::NssUnsupportedSigner);
};
let Some(sig_alg_der) = sig_alg_der_from_spki(spki_der, algorithm) else {
return Box::new(crate::nss_backend::NssUnsupportedSigner);
};
let Some(nss_alg_tag) = sig_alg_der_to_nss_tag(&sig_alg_der) else {
return Box::new(crate::nss_backend::NssUnsupportedSigner);
};
Box::new(NssHsmSigner {
object_label: object_label.to_string(),
pin: pkcs11.attrs.pin_value.clone(),
token_label: pkcs11.attrs.token.clone(),
sig_alg_der,
nss_alg_tag,
})
}
impl ErasedCertificateSigner for NssHsmSigner {
fn signature_algorithm_der_erased(&self) -> Result<Vec<u8>, PrivateKeyError> {
Ok(self.sig_alg_der.clone())
}
fn sign_tbs_erased(&self, tbs_der: &[u8]) -> Result<Vec<u8>, PrivateKeyError> {
do_hsm_sign(
&self.object_label,
self.pin.as_deref(),
self.token_label.as_deref(),
tbs_der,
self.nss_alg_tag,
)
.map_err(PrivateKeyError::new)
}
}
pub(crate) fn priv_load_from_pkcs11_uri_nss(
uri: &str,
) -> Result<crate::crypto::BackendPrivateKey, super::signing::NssSignerError> {
use super::signing::NssSignerError;
if !ensure_nss_init() {
return Err(NssSignerError("NSS initialisation failed".to_string()));
}
let pkcs11 = crate::pkcs11_uri::Pkcs11Uri::parse(uri)
.ok_or_else(|| NssSignerError(format!("not a pkcs11: URI: {}", uri)))?;
let object_label = pkcs11
.attrs
.object
.as_deref()
.ok_or_else(|| NssSignerError("PKCS#11 URI missing 'object' attribute".to_string()))?
.to_string();
let token_label = pkcs11
.attrs
.token
.as_deref()
.filter(|l| !l.is_empty())
.ok_or_else(|| {
NssSignerError(
"PKCS#11 URI must include a non-empty 'token=' attribute for the NSS backend"
.to_string(),
)
})?;
let label_c = CString::new(token_label)
.map_err(|e| NssSignerError(format!("token label contains NUL: {}", e)))?;
let slot = unsafe { PK11_FindSlotByName(label_c.as_ptr()) };
if slot.is_null() {
return Err(NssSignerError(format!(
"token '{}' not found in NSS",
token_label
)));
}
if let Some(pin) = &pkcs11.attrs.pin_value {
let pin_c = CString::new(pin.as_str())
.map_err(|e| NssSignerError(format!("PIN contains NUL: {}", e)))?;
unsafe { PK11_Authenticate(slot, PR_TRUE, pin_c.as_ptr()) };
}
let nickname_c = CString::new(object_label.as_str())
.map_err(|e| NssSignerError(format!("object label contains NUL: {}", e)))?;
let key_list =
unsafe { PK11_ListPrivKeysInSlot(slot, nickname_c.as_ptr() as *mut _, ptr::null()) };
unsafe { PK11_FreeSlot(slot) };
if key_list.is_null() {
return Err(NssSignerError(format!(
"PK11_ListPrivKeysInSlot returned NULL for key '{}' in token '{}'",
object_label, token_label
)));
}
let priv_key = unsafe { take_first_key(key_list) };
unsafe { SECKEY_DestroyPrivateKeyList(key_list) };
let priv_key = priv_key.ok_or_else(|| {
NssSignerError(format!(
"key '{}' not found in token '{}'; ensure the PKCS#11 module is registered",
object_label, token_label
))
})?;
let pub_key = unsafe { SECKEY_ConvertToPublicKey(priv_key) };
if pub_key.is_null() {
unsafe { SECKEY_DestroyPrivateKey(priv_key) };
return Err(NssSignerError(
"SECKEY_ConvertToPublicKey returned NULL".to_string(),
));
}
let spki_item_ptr = unsafe { SECKEY_EncodeDERSubjectPublicKeyInfo(pub_key) };
if spki_item_ptr.is_null() {
unsafe { SECKEY_DestroyPublicKey(pub_key) };
unsafe { SECKEY_DestroyPrivateKey(priv_key) };
return Err(NssSignerError(
"SECKEY_EncodeDERSubjectPublicKeyInfo returned NULL".to_string(),
));
}
let spki_der = unsafe {
let item = &*spki_item_ptr;
std::slice::from_raw_parts(item.data, item.len as usize).to_vec()
};
unsafe { SECITEM_FreeItem(spki_item_ptr, PR_TRUE) };
unsafe { SECKEY_DestroyPublicKey(pub_key) };
unsafe { SECKEY_DestroyPrivateKey(priv_key) };
Ok(crate::crypto::BackendPrivateKey {
pkcs8_der: std::sync::OnceLock::new(),
spki_cache: Some(spki_der),
pkcs11: Some(pkcs11),
})
}