use std::ptr;
use nss_sys::nspr::{PR_FALSE, PR_TRUE};
use nss_sys::{SECAlgorithmIDStr, SECItemStr, SECItemType, SECStatus};
use synta::ToDer;
use crate::crypto::composite_mldsa::{
build_m_prime_from_hash, composite_oid_components, composite_spec, encode_composite_pkcs8,
encode_composite_spki, extract_spki_bitstring_payload, pkcs8_private_key_content,
split_composite_privkey, split_composite_sig, split_composite_spki_content, CompHash,
CompositeMlDsaSpec, TradAlg,
};
use crate::crypto::{BackendPrivateKey, ErasedCertificateSigner, PrivateKeyError};
use crate::oids;
use super::ensure_nss_init;
use super::ffi::SECOidTag;
use super::ffi::{
CkEddsaParams, CkRsaPkcsPssParams, CkSignAdditionalContext, PK11_FreeSlot,
PK11_GetInternalSlot, PK11_HashBuf, PK11_ImportDERPrivateKeyInfoAndReturnKey,
PK11_SignWithMechanism, PK11_SignatureLen, PK11_Verify, PK11_VerifyWithMechanism,
SECKEY_DecodeDERSubjectPublicKeyInfo, SECKEY_DestroyPrivateKey, SECKEY_DestroyPublicKey,
SECKEY_DestroySubjectPublicKeyInfo, SECKEY_ExtractPublicKey, VFY_VerifyDataWithAlgorithmID,
CKG_MGF1_SHA256, CKG_MGF1_SHA512, CKH_HEDGE_PREFERRED, CKM_EDDSA, CKM_ML_DSA, CKM_SHA256,
CKM_SHA256_RSA_PKCS_PSS, CKM_SHA512, CKM_SHA512_RSA_PKCS_PSS, KU_DIGITAL_SIGNATURE,
SEC_OID_SHA256, SEC_OID_SHA512,
};
use super::ffi::{
SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE,
SEC_OID_ED25519_SIGNATURE, SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION,
};
use super::signing::{do_nss_sign, NssSignerError};
#[derive(Debug)]
pub(super) struct CompositeNssError(pub String);
impl std::fmt::Display for CompositeNssError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for CompositeNssError {}
impl From<String> for CompositeNssError {
fn from(s: String) -> Self {
CompositeNssError(s)
}
}
fn nss_hash_tbs(tbs_der: &[u8], hash: CompHash) -> Result<Vec<u8>, CompositeNssError> {
if !ensure_nss_init() {
return Err(CompositeNssError("NSS initialisation failed".to_string()));
}
let (nss_tag, out_len) = match hash {
CompHash::Sha256 => (SEC_OID_SHA256, 32usize),
CompHash::Sha512 => (SEC_OID_SHA512, 64usize),
CompHash::Shake256_64 => {
return Err(CompositeNssError(
"SHAKE256 is not supported in the NSS composite path (sub_arc 51)".to_string(),
));
}
};
let mut out = vec![0u8; out_len];
let in_len = i32::try_from(tbs_der.len())
.map_err(|_| CompositeNssError("PK11_HashBuf: input exceeds i32::MAX bytes".to_string()))?;
let status = unsafe { PK11_HashBuf(nss_tag, out.as_mut_ptr(), tbs_der.as_ptr(), in_len) };
if status == SECStatus::SECSuccess {
Ok(out)
} else {
let nss_err = unsafe { nss_sys::nspr::PR_GetError() };
let hash_name = match hash {
CompHash::Sha256 => "SHA-256",
CompHash::Sha512 => "SHA-512",
CompHash::Shake256_64 => "SHAKE256",
};
Err(CompositeNssError(format!(
"PK11_HashBuf failed for {hash_name} on {}-byte input (NSS error {nss_err})",
tbs_der.len()
)))
}
}
fn nss_build_m_prime(
tbs_der: &[u8],
spec: &CompositeMlDsaSpec,
) -> Result<Vec<u8>, CompositeNssError> {
let hash = nss_hash_tbs(tbs_der, spec.hash)?;
Ok(build_m_prime_from_hash(&hash, spec))
}
fn encode_standalone_spki(
oid_comps: &[u32],
alg_params: Option<synta::types::constructed::Element<'_>>,
raw_pk: &[u8],
) -> Result<Vec<u8>, String> {
use synta::tag::{Tag, TAG_SEQUENCE};
use synta::types::string::BitStringRef;
use synta::{Encoder, Encoding, ObjectIdentifier};
let oid = ObjectIdentifier::new(oid_comps).map_err(|e| format!("invalid OID: {e}"))?;
let pk_bit =
BitStringRef::new(raw_pk, 0).map_err(|e| format!("BIT STRING encoding failed: {e}"))?;
(|| -> synta::Result<Vec<u8>> {
let mut enc = Encoder::new(Encoding::Der);
enc.start_constructed_no_guard(Tag::universal_constructed(TAG_SEQUENCE))?;
enc.start_constructed_no_guard(Tag::universal_constructed(TAG_SEQUENCE))?;
enc.encode(&oid)?;
if let Some(params) = alg_params {
enc.encode(¶ms)?;
}
enc.end_constructed()?;
enc.encode(&pk_bit)?;
enc.end_constructed()?;
enc.finish()
})()
.map_err(|e| format!("SPKI DER encoding failed: {e}"))
}
fn encode_standalone_pkcs8(
oid_comps: &[u32],
alg_params: Option<synta::types::constructed::Element<'_>>,
privkey_content: &[u8],
) -> Result<Vec<u8>, String> {
use synta::types::string::OctetStringRef;
use synta::ObjectIdentifier;
let oid = ObjectIdentifier::new(oid_comps).map_err(|e| format!("invalid OID: {e}"))?;
let alg = crate::AlgorithmIdentifier {
algorithm: oid,
parameters: alg_params,
};
let pki = crate::pkcs8_types::OneAsymmetricKey {
version: synta::Integer::from_i64(0),
private_key_algorithm: alg,
private_key: OctetStringRef::new(privkey_content),
attributes: None,
public_key: None,
};
pki.to_der()
.map_err(|e| format!("PKCS#8 DER encoding failed: {e}"))
}
fn ec_alg_params(curve: &str) -> Result<synta::types::constructed::Element<'static>, String> {
use synta::{types::constructed::Element, ObjectIdentifier};
let comps: &[u32] = match curve {
"P-256" => oids::EC_CURVE_P256,
"P-384" => oids::EC_CURVE_P384,
"P-521" => oids::EC_CURVE_P521,
other => return Err(format!("unsupported EC curve for NSS composite: {other}")),
};
let oid = ObjectIdentifier::new(comps).map_err(|e| format!("invalid curve OID: {e}"))?;
Ok(Element::ObjectIdentifier(oid))
}
fn encode_trad_spki(trad_alg: &TradAlg, raw_pk: &[u8]) -> Result<Vec<u8>, String> {
use synta::{Element, Null};
match trad_alg {
TradAlg::RsaPss { .. } | TradAlg::RsaPkcs15 { .. } => {
encode_standalone_spki(oids::RSA_ENCRYPTION, Some(Element::Null(Null)), raw_pk)
}
TradAlg::Ec { curve, .. } => {
let params = ec_alg_params(curve)?;
encode_standalone_spki(oids::EC_PUBLIC_KEY, Some(params), raw_pk)
}
TradAlg::Ed25519 => encode_standalone_spki(oids::ED25519, None, raw_pk),
TradAlg::Ed448 => encode_standalone_spki(oids::ED448, None, raw_pk),
}
}
fn encode_trad_pkcs8(trad_alg: &TradAlg, trad_sk: &[u8]) -> Result<Vec<u8>, String> {
use synta::{Element, Null};
match trad_alg {
TradAlg::RsaPss { .. } | TradAlg::RsaPkcs15 { .. } => {
encode_standalone_pkcs8(oids::RSA_ENCRYPTION, Some(Element::Null(Null)), trad_sk)
}
TradAlg::Ec { curve, .. } => {
let params = ec_alg_params(curve)?;
encode_standalone_pkcs8(oids::EC_PUBLIC_KEY, Some(params), trad_sk)
}
TradAlg::Ed25519 => encode_standalone_pkcs8(oids::ED25519, None, trad_sk),
TradAlg::Ed448 => encode_standalone_pkcs8(oids::ED448, None, trad_sk),
}
}
fn build_rsa_pss_params_der(hash: &str) -> Result<Vec<u8>, String> {
use synta::tag::{Tag, TAG_SEQUENCE};
use synta::{Encoder, Encoding, Integer, ObjectIdentifier};
let (hash_oid_comps, salt_len): (&[u32], i64) = match hash {
"sha256" => (oids::ID_SHA256, 32),
"sha512" => (oids::ID_SHA512, 64),
other => return Err(format!("unsupported hash for RSA-PSS parameters: {other}")),
};
let hash_oid =
ObjectIdentifier::new(hash_oid_comps).map_err(|e| format!("invalid hash OID: {e}"))?;
let mgf1_oid =
ObjectIdentifier::new(oids::MGF1).map_err(|e| format!("invalid MGF1 OID: {e}"))?;
(|| -> synta::Result<Vec<u8>> {
let mut enc = Encoder::new(Encoding::Der);
enc.start_constructed_no_guard(Tag::universal_constructed(TAG_SEQUENCE))?;
enc.start_constructed_no_guard(Tag::context_specific_constructed(0))?;
enc.start_constructed_no_guard(Tag::universal_constructed(TAG_SEQUENCE))?;
enc.encode(&hash_oid)?;
enc.end_constructed()?;
enc.end_constructed()?;
enc.start_constructed_no_guard(Tag::context_specific_constructed(1))?;
enc.start_constructed_no_guard(Tag::universal_constructed(TAG_SEQUENCE))?;
enc.encode(&mgf1_oid)?;
enc.start_constructed_no_guard(Tag::universal_constructed(TAG_SEQUENCE))?;
enc.encode(&hash_oid)?;
enc.end_constructed()?;
enc.end_constructed()?;
enc.end_constructed()?;
enc.start_constructed_no_guard(Tag::context_specific_constructed(2))?;
enc.encode(&Integer::from_i64(salt_len))?;
enc.end_constructed()?;
enc.start_constructed_no_guard(Tag::context_specific_constructed(3))?;
enc.encode(&Integer::from_i64(1))?;
enc.end_constructed()?;
enc.end_constructed()?;
enc.finish()
})()
.map_err(|e| format!("RSA-PSS params DER encoding failed: {e}"))
}
fn encode_oid_der(oid_comps: &[u32]) -> Result<Vec<u8>, String> {
let oid = synta::ObjectIdentifier::new(oid_comps).map_err(|e| format!("invalid OID: {e}"))?;
oid.to_der()
.map_err(|e| format!("OID DER encoding failed: {e}"))
}
pub(crate) fn priv_generate_composite_mldsa(
sub_arc: u32,
) -> Result<BackendPrivateKey, PrivateKeyError> {
let spec = composite_spec(sub_arc).ok_or_else(|| {
PrivateKeyError::new(CompositeNssError(format!(
"unknown composite ML-DSA sub_arc: {sub_arc}"
)))
})?;
let mldsa_key = super::key_ops::generate_ml_dsa(spec.mldsa_variant)?;
let mldsa_pkcs8 = mldsa_key.pkcs8_bytes().to_vec();
let mldsa_seed = pkcs8_private_key_content(&mldsa_pkcs8)
.map_err(|e| PrivateKeyError::new(CompositeNssError(e)))?;
let mldsa_spki = mldsa_key
.public_key()
.map_err(|e| PrivateKeyError::new(CompositeNssError(format!("{e}"))))?
.spki_der;
let mldsa_pk = extract_spki_bitstring_payload(&mldsa_spki)
.map_err(|e| PrivateKeyError::new(CompositeNssError(e)))?;
let (trad_sk, trad_pk) = generate_trad_key_nss(&spec.trad_alg)?;
let oid_comps = composite_oid_components(spec.sub_arc);
let spki_der = encode_composite_spki(&oid_comps, &mldsa_pk, &trad_pk)
.map_err(|e| PrivateKeyError::new(CompositeNssError(e)))?;
let pkcs8_der = encode_composite_pkcs8(&oid_comps, &mldsa_seed, &trad_sk)
.map_err(|e| PrivateKeyError::new(CompositeNssError(e)))?;
let pkcs8_cell = std::sync::OnceLock::new();
pkcs8_cell.set(pkcs8_der).expect("fresh OnceLock");
Ok(BackendPrivateKey {
pkcs8_der: pkcs8_cell,
spki_cache: Some(spki_der),
pkcs11: None,
})
}
fn generate_trad_key_nss(trad_alg: &TradAlg) -> Result<(Vec<u8>, Vec<u8>), PrivateKeyError> {
let err = |s: String| PrivateKeyError::new(CompositeNssError(s));
match trad_alg {
TradAlg::RsaPss { bits, .. } | TradAlg::RsaPkcs15 { bits, .. } => {
let key = BackendPrivateKey::generate_rsa(*bits, 65537)?;
let pkcs8 = key.pkcs8_bytes().to_vec();
let trad_sk = pkcs8_private_key_content(&pkcs8).map_err(err)?;
let spki = key
.public_key()
.map_err(|e| PrivateKeyError::new(CompositeNssError(format!("{e}"))))?
.spki_der;
let trad_pk = extract_spki_bitstring_payload(&spki).map_err(err)?;
Ok((trad_sk, trad_pk))
}
TradAlg::Ec { curve, .. } => {
let key = BackendPrivateKey::generate_ec(curve)?;
let pkcs8 = key.pkcs8_bytes().to_vec();
let trad_sk = pkcs8_private_key_content(&pkcs8).map_err(err)?;
let spki = key
.public_key()
.map_err(|e| PrivateKeyError::new(CompositeNssError(format!("{e}"))))?
.spki_der;
let trad_pk = extract_spki_bitstring_payload(&spki).map_err(err)?;
Ok((trad_sk, trad_pk))
}
TradAlg::Ed25519 => {
let key = BackendPrivateKey::generate_ed25519()?;
let pkcs8 = key.pkcs8_bytes().to_vec();
let trad_sk = pkcs8_private_key_content(&pkcs8).map_err(err)?;
let spki = key
.public_key()
.map_err(|e| PrivateKeyError::new(CompositeNssError(format!("{e}"))))?
.spki_der;
let trad_pk = extract_spki_bitstring_payload(&spki).map_err(err)?;
Ok((trad_sk, trad_pk))
}
TradAlg::Ed448 => {
let key = BackendPrivateKey::generate_ed448()?;
let pkcs8 = key.pkcs8_bytes().to_vec();
let trad_sk = pkcs8_private_key_content(&pkcs8).map_err(err)?;
let spki = key
.public_key()
.map_err(|e| PrivateKeyError::new(CompositeNssError(format!("{e}"))))?
.spki_der;
let trad_pk = extract_spki_bitstring_payload(&spki).map_err(err)?;
Ok((trad_sk, trad_pk))
}
}
}
enum NssTradSignerKind {
SecSignData(SECOidTag),
RsaPss { hash: &'static str },
Ed448,
}
pub(crate) struct CompositeMLDsaNssSigner {
spec: &'static CompositeMlDsaSpec,
mldsa_pkcs8: Vec<u8>,
trad_pkcs8: Vec<u8>,
trad_kind: NssTradSignerKind,
}
impl CompositeMLDsaNssSigner {
fn from_composite_pkcs8(
pkcs8_der: &[u8],
spec: &'static CompositeMlDsaSpec,
) -> Result<Self, CompositeNssError> {
let privkey = pkcs8_private_key_content(pkcs8_der).map_err(CompositeNssError::from)?;
let (mldsa_seed, trad_sk) =
split_composite_privkey(&privkey).map_err(CompositeNssError::from)?;
let mldsa_oid: &[u32] = match spec.mldsa_variant {
"ML-DSA-44" => oids::ML_DSA_44,
"ML-DSA-65" => oids::ML_DSA_65,
"ML-DSA-87" => oids::ML_DSA_87,
other => {
return Err(CompositeNssError(format!(
"unknown ML-DSA variant: {other}"
)))
}
};
let mldsa_pkcs8 = encode_standalone_pkcs8(mldsa_oid, None, mldsa_seed)
.map_err(CompositeNssError::from)?;
let trad_pkcs8 =
encode_trad_pkcs8(&spec.trad_alg, trad_sk).map_err(CompositeNssError::from)?;
let trad_kind = match &spec.trad_alg {
TradAlg::RsaPkcs15 { hash, .. } => match *hash {
"sha256" => {
NssTradSignerKind::SecSignData(SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION)
}
"sha512" => {
NssTradSignerKind::SecSignData(SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION)
}
other => {
return Err(CompositeNssError(format!(
"unsupported RSA-PKCS15 hash: {other}"
)))
}
},
TradAlg::Ec { hash, .. } => match *hash {
"sha256" => NssTradSignerKind::SecSignData(SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE),
"sha512" => NssTradSignerKind::SecSignData(SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE),
other => {
return Err(CompositeNssError(format!(
"unsupported ECDSA hash: {other}"
)))
}
},
TradAlg::Ed25519 => NssTradSignerKind::SecSignData(SEC_OID_ED25519_SIGNATURE),
TradAlg::RsaPss { hash, .. } => NssTradSignerKind::RsaPss { hash },
TradAlg::Ed448 => NssTradSignerKind::Ed448,
};
Ok(Self {
spec,
mldsa_pkcs8,
trad_pkcs8,
trad_kind,
})
}
}
impl ErasedCertificateSigner for CompositeMLDsaNssSigner {
fn signature_algorithm_der_erased(&self) -> Result<Vec<u8>, PrivateKeyError> {
let comps = composite_oid_components(self.spec.sub_arc);
let oid = synta::ObjectIdentifier::new(&comps)
.map_err(|e| PrivateKeyError::new(CompositeNssError(e.to_string())))?;
let alg = crate::AlgorithmIdentifier {
algorithm: oid,
parameters: None,
};
alg.to_der()
.map_err(|e| PrivateKeyError::new(CompositeNssError(e.to_string())))
}
fn sign_tbs_erased(&self, tbs_der: &[u8]) -> Result<Vec<u8>, PrivateKeyError> {
let m_prime = nss_build_m_prime(tbs_der, self.spec).map_err(PrivateKeyError::new)?;
let mldsa_sig = sign_mldsa_with_context(
&self.mldsa_pkcs8,
&m_prime,
self.spec.label.as_bytes(),
self.spec.mldsa_sig_size,
)
.map_err(PrivateKeyError::new)?;
let trad_sig = match &self.trad_kind {
NssTradSignerKind::SecSignData(tag) => {
do_nss_sign(&self.trad_pkcs8, &m_prime, *tag).map_err(PrivateKeyError::new)?
}
NssTradSignerKind::RsaPss { hash } => {
sign_rsa_pss(&self.trad_pkcs8, &m_prime, hash).map_err(PrivateKeyError::new)?
}
NssTradSignerKind::Ed448 => {
sign_ed448(&self.trad_pkcs8, &m_prime).map_err(PrivateKeyError::new)?
}
};
let mut sig = Vec::with_capacity(mldsa_sig.len() + trad_sig.len());
sig.extend_from_slice(&mldsa_sig);
sig.extend_from_slice(&trad_sig);
Ok(sig)
}
}
pub(crate) fn composite_mldsa_signer_from_pkcs8(
pkcs8_der: &[u8],
spec: &'static CompositeMlDsaSpec,
) -> Box<dyn ErasedCertificateSigner> {
match CompositeMLDsaNssSigner::from_composite_pkcs8(pkcs8_der, spec) {
Ok(signer) => Box::new(signer),
Err(e) => Box::new(NssFailedCompositeSigner(format!(
"composite signer init failed: {e}"
))),
}
}
struct NssFailedCompositeSigner(String);
impl ErasedCertificateSigner for NssFailedCompositeSigner {
fn signature_algorithm_der_erased(&self) -> Result<Vec<u8>, PrivateKeyError> {
Err(PrivateKeyError::new(NssSignerError(self.0.clone())))
}
fn sign_tbs_erased(&self, _tbs_der: &[u8]) -> Result<Vec<u8>, PrivateKeyError> {
Err(PrivateKeyError::new(NssSignerError(self.0.clone())))
}
}
pub(super) fn verify_composite_mldsa_signature(
tbs_der: &[u8],
sub_arc: u32,
signature_bits: &[u8],
issuer_spki_der: &[u8],
) -> Result<(), super::signature::NssVerifierError> {
use super::signature::NssVerifierError;
let err = |s: String| NssVerifierError(s);
let spec = composite_spec(sub_arc)
.ok_or_else(|| err(format!("unknown composite ML-DSA sub_arc: {sub_arc}")))?;
let m_prime = nss_build_m_prime(tbs_der, spec).map_err(|e| err(e.to_string()))?;
let spki_payload = extract_spki_bitstring_payload(issuer_spki_der).map_err(|e| err(e))?;
let (mldsa_pk, trad_pk) =
split_composite_spki_content(&spki_payload, spec).map_err(|e| err(e))?;
let (mldsa_sig, trad_sig) = split_composite_sig(signature_bits, spec).map_err(|e| err(e))?;
let mldsa_oid: &[u32] = match spec.mldsa_variant {
"ML-DSA-44" => oids::ML_DSA_44,
"ML-DSA-65" => oids::ML_DSA_65,
"ML-DSA-87" => oids::ML_DSA_87,
other => return Err(err(format!("unknown ML-DSA variant: {other}"))),
};
let mldsa_spki = encode_standalone_spki(mldsa_oid, None, mldsa_pk).map_err(|e| err(e))?;
let mldsa_result =
verify_mldsa_with_context(&mldsa_spki, &m_prime, mldsa_sig, spec.label.as_bytes())
.map_err(|e| err(e.to_string()));
let trad_spki = encode_trad_spki(&spec.trad_alg, trad_pk).map_err(|e| err(e))?;
let trad_result = verify_trad_component_nss(&trad_spki, &m_prime, trad_sig, &spec.trad_alg)
.map_err(|e| err(e.to_string()));
mldsa_result.and(trad_result)
}
fn sign_mldsa_with_context(
mldsa_pkcs8: &[u8],
message: &[u8],
label: &[u8],
sig_size: usize,
) -> Result<Vec<u8>, CompositeNssError> {
if !ensure_nss_init() {
return Err(CompositeNssError("NSS initialisation failed".to_string()));
}
let slot = unsafe { PK11_GetInternalSlot() };
if slot.is_null() {
return Err(CompositeNssError("PK11_GetInternalSlot failed".to_string()));
}
let pkcs8_item = SECItemStr {
type_: SECItemType::siBuffer,
data: mldsa_pkcs8.as_ptr() as *mut _,
len: mldsa_pkcs8.len() as u32,
};
let mut priv_key: *mut super::ffi::SECKEYPrivateKeyStr = ptr::null_mut();
let import_status = unsafe {
PK11_ImportDERPrivateKeyInfoAndReturnKey(
slot,
&pkcs8_item,
ptr::null(),
ptr::null(),
PR_FALSE,
PR_TRUE,
KU_DIGITAL_SIGNATURE,
&mut priv_key,
ptr::null_mut(),
)
};
unsafe { PK11_FreeSlot(slot) };
if import_status != SECStatus::SECSuccess || priv_key.is_null() {
return Err(CompositeNssError(
"ML-DSA key import into NSS slot failed".to_string(),
));
}
let ctx = CkSignAdditionalContext {
hedge_variant: CKH_HEDGE_PREFERRED,
p_context: label.as_ptr(),
ul_context_len: label.len() as std::ffi::c_ulong,
};
let param_item = SECItemStr {
type_: SECItemType::siBuffer,
data: &ctx as *const CkSignAdditionalContext as *mut _,
len: std::mem::size_of::<CkSignAdditionalContext>() as u32,
};
let mut sig_buf = vec![0u8; sig_size];
let mut sig_item = SECItemStr {
type_: SECItemType::siBuffer,
data: sig_buf.as_mut_ptr(),
len: sig_buf.len() as u32,
};
let msg_item = SECItemStr {
type_: SECItemType::siBuffer,
data: message.as_ptr() as *mut _,
len: message.len() as u32,
};
let sign_status = unsafe {
PK11_SignWithMechanism(priv_key, CKM_ML_DSA, ¶m_item, &mut sig_item, &msg_item)
};
unsafe { SECKEY_DestroyPrivateKey(priv_key) };
if sign_status != SECStatus::SECSuccess {
let nss_err = unsafe { nss_sys::nspr::PR_GetError() };
return Err(CompositeNssError(format!(
"PK11_SignWithMechanism (CKM_ML_DSA) failed (NSS error {nss_err})"
)));
}
sig_buf.truncate(sig_item.len as usize);
Ok(sig_buf)
}
fn sign_rsa_pss(
rsa_pkcs8: &[u8],
message: &[u8],
hash: &str,
) -> Result<Vec<u8>, CompositeNssError> {
use nss_sys::SECStatus;
if !ensure_nss_init() {
return Err(CompositeNssError("NSS initialisation failed".to_string()));
}
let (mech, hash_alg, mgf, s_len) = match hash {
"sha256" => (CKM_SHA256_RSA_PKCS_PSS, CKM_SHA256, CKG_MGF1_SHA256, 32u64),
"sha512" => (CKM_SHA512_RSA_PKCS_PSS, CKM_SHA512, CKG_MGF1_SHA512, 64u64),
other => {
return Err(CompositeNssError(format!(
"unsupported RSA-PSS hash for NSS composite signing: {other}"
)))
}
};
let slot = unsafe { PK11_GetInternalSlot() };
if slot.is_null() {
return Err(CompositeNssError("PK11_GetInternalSlot failed".to_string()));
}
let pkcs8_item = nss_sys::SECItemStr {
type_: nss_sys::SECItemType::siBuffer,
data: rsa_pkcs8.as_ptr() as *mut _,
len: rsa_pkcs8.len() as u32,
};
let mut priv_key: *mut super::ffi::SECKEYPrivateKeyStr = ptr::null_mut();
let import_status = unsafe {
PK11_ImportDERPrivateKeyInfoAndReturnKey(
slot,
&pkcs8_item,
ptr::null(),
ptr::null(),
nss_sys::nspr::PR_FALSE,
nss_sys::nspr::PR_TRUE,
KU_DIGITAL_SIGNATURE,
&mut priv_key,
ptr::null_mut(),
)
};
unsafe { PK11_FreeSlot(slot) };
if import_status != SECStatus::SECSuccess || priv_key.is_null() {
return Err(CompositeNssError(
"RSA key import into NSS slot failed".to_string(),
));
}
let sig_size_raw = unsafe { PK11_SignatureLen(priv_key) };
if sig_size_raw <= 0 {
unsafe { SECKEY_DestroyPrivateKey(priv_key) };
return Err(CompositeNssError(
"PK11_SignatureLen returned <= 0 for RSA key".to_string(),
));
}
let sig_size = sig_size_raw as usize;
let pss_params = CkRsaPkcsPssParams {
hash_alg,
mgf,
s_len: s_len as std::ffi::c_ulong,
};
let param_item = nss_sys::SECItemStr {
type_: nss_sys::SECItemType::siBuffer,
data: &pss_params as *const CkRsaPkcsPssParams as *mut _,
len: std::mem::size_of::<CkRsaPkcsPssParams>() as u32,
};
let mut sig_buf = vec![0u8; sig_size];
let mut sig_item = nss_sys::SECItemStr {
type_: nss_sys::SECItemType::siBuffer,
data: sig_buf.as_mut_ptr(),
len: sig_buf.len() as u32,
};
let msg_item = nss_sys::SECItemStr {
type_: nss_sys::SECItemType::siBuffer,
data: message.as_ptr() as *mut _,
len: message.len() as u32,
};
let sign_status =
unsafe { PK11_SignWithMechanism(priv_key, mech, ¶m_item, &mut sig_item, &msg_item) };
unsafe { SECKEY_DestroyPrivateKey(priv_key) };
if sign_status != SECStatus::SECSuccess {
let nss_err = unsafe { nss_sys::nspr::PR_GetError() };
return Err(CompositeNssError(format!(
"PK11_SignWithMechanism (RSA-PSS {hash}) failed (NSS error {nss_err})"
)));
}
sig_buf.truncate(sig_item.len as usize);
Ok(sig_buf)
}
fn sign_ed448(ed448_pkcs8: &[u8], message: &[u8]) -> Result<Vec<u8>, CompositeNssError> {
use nss_sys::SECStatus;
if !ensure_nss_init() {
return Err(CompositeNssError("NSS initialisation failed".to_string()));
}
let slot = unsafe { PK11_GetInternalSlot() };
if slot.is_null() {
return Err(CompositeNssError("PK11_GetInternalSlot failed".to_string()));
}
let pkcs8_item = nss_sys::SECItemStr {
type_: nss_sys::SECItemType::siBuffer,
data: ed448_pkcs8.as_ptr() as *mut _,
len: ed448_pkcs8.len() as u32,
};
let mut priv_key: *mut super::ffi::SECKEYPrivateKeyStr = ptr::null_mut();
let import_status = unsafe {
PK11_ImportDERPrivateKeyInfoAndReturnKey(
slot,
&pkcs8_item,
ptr::null(),
ptr::null(),
nss_sys::nspr::PR_FALSE,
nss_sys::nspr::PR_TRUE,
KU_DIGITAL_SIGNATURE,
&mut priv_key,
ptr::null_mut(),
)
};
unsafe { PK11_FreeSlot(slot) };
if import_status != SECStatus::SECSuccess || priv_key.is_null() {
return Err(CompositeNssError(
"Ed448 key import into NSS slot failed".to_string(),
));
}
let sig_size_raw = unsafe { PK11_SignatureLen(priv_key) };
if sig_size_raw <= 0 {
unsafe { SECKEY_DestroyPrivateKey(priv_key) };
return Err(CompositeNssError(
"PK11_SignatureLen returned <= 0 for Ed448 key".to_string(),
));
}
let sig_size = sig_size_raw as usize;
static DUMMY: u8 = 0;
let eddsa_params = CkEddsaParams {
ph_flag: 0,
ul_context_data_len: 0,
p_context_data: &DUMMY as *const u8,
};
let param_item = nss_sys::SECItemStr {
type_: nss_sys::SECItemType::siBuffer,
data: &eddsa_params as *const CkEddsaParams as *mut _,
len: std::mem::size_of::<CkEddsaParams>() as u32,
};
let mut sig_buf = vec![0u8; sig_size];
let mut sig_item = nss_sys::SECItemStr {
type_: nss_sys::SECItemType::siBuffer,
data: sig_buf.as_mut_ptr(),
len: sig_buf.len() as u32,
};
let msg_item = nss_sys::SECItemStr {
type_: nss_sys::SECItemType::siBuffer,
data: message.as_ptr() as *mut _,
len: message.len() as u32,
};
let sign_status = unsafe {
PK11_SignWithMechanism(priv_key, CKM_EDDSA, ¶m_item, &mut sig_item, &msg_item)
};
unsafe { SECKEY_DestroyPrivateKey(priv_key) };
if sign_status != SECStatus::SECSuccess {
let nss_err = unsafe { nss_sys::nspr::PR_GetError() };
return Err(CompositeNssError(format!(
"PK11_SignWithMechanism (CKM_EDDSA) for Ed448 failed (NSS error {nss_err})"
)));
}
sig_buf.truncate(sig_item.len as usize);
Ok(sig_buf)
}
fn verify_mldsa_with_context(
mldsa_spki_der: &[u8],
message: &[u8],
sig: &[u8],
label: &[u8],
) -> Result<(), CompositeNssError> {
if !ensure_nss_init() {
return Err(CompositeNssError("NSS initialisation failed".to_string()));
}
let spki_item = SECItemStr {
type_: SECItemType::siBuffer,
data: mldsa_spki_der.as_ptr() as *mut _,
len: mldsa_spki_der.len() as u32,
};
let spki_info = unsafe { SECKEY_DecodeDERSubjectPublicKeyInfo(&spki_item) };
if spki_info.is_null() {
let nss_err = unsafe { nss_sys::nspr::PR_GetError() };
return Err(CompositeNssError(format!(
"SECKEY_DecodeDERSubjectPublicKeyInfo failed for ML-DSA SPKI (NSS error {nss_err})"
)));
}
let pub_key = unsafe { SECKEY_ExtractPublicKey(spki_info) };
unsafe { SECKEY_DestroySubjectPublicKeyInfo(spki_info) };
if pub_key.is_null() {
let nss_err = unsafe { nss_sys::nspr::PR_GetError() };
return Err(CompositeNssError(format!(
"SECKEY_ExtractPublicKey failed for ML-DSA SPKI (NSS error {nss_err})"
)));
}
let ctx = CkSignAdditionalContext {
hedge_variant: CKH_HEDGE_PREFERRED,
p_context: label.as_ptr(),
ul_context_len: label.len() as std::ffi::c_ulong,
};
let param_item = SECItemStr {
type_: SECItemType::siBuffer,
data: &ctx as *const CkSignAdditionalContext as *mut _,
len: std::mem::size_of::<CkSignAdditionalContext>() as u32,
};
let sig_item = SECItemStr {
type_: SECItemType::siBuffer,
data: sig.as_ptr() as *mut _,
len: sig.len() as u32,
};
let msg_item = SECItemStr {
type_: SECItemType::siBuffer,
data: message.as_ptr() as *mut _,
len: message.len() as u32,
};
let status = unsafe {
PK11_VerifyWithMechanism(
pub_key,
CKM_ML_DSA,
¶m_item,
&sig_item,
&msg_item,
ptr::null_mut(),
)
};
unsafe { SECKEY_DestroyPublicKey(pub_key) };
if status == SECStatus::SECSuccess {
Ok(())
} else {
let nss_err = unsafe { nss_sys::nspr::PR_GetError() };
Err(CompositeNssError(format!(
"ML-DSA composite component verification failed (NSS error {nss_err})"
)))
}
}
fn verify_component_nss(
spki_der: &[u8],
message: &[u8],
signature: &[u8],
alg_oid_comps: &[u32],
alg_params_der: Option<&[u8]>,
use_pk11_verify: bool,
) -> Result<(), CompositeNssError> {
if !ensure_nss_init() {
return Err(CompositeNssError("NSS initialisation failed".to_string()));
}
let spki_item = SECItemStr {
type_: SECItemType::siBuffer,
data: spki_der.as_ptr() as *mut _,
len: spki_der.len() as u32,
};
let spki_info = unsafe { SECKEY_DecodeDERSubjectPublicKeyInfo(&spki_item) };
if spki_info.is_null() {
let nss_err = unsafe { nss_sys::nspr::PR_GetError() };
return Err(CompositeNssError(format!(
"SECKEY_DecodeDERSubjectPublicKeyInfo failed for composite SPKI (NSS error {nss_err})"
)));
}
let pub_key = unsafe { SECKEY_ExtractPublicKey(spki_info) };
unsafe { SECKEY_DestroySubjectPublicKeyInfo(spki_info) };
if pub_key.is_null() {
let nss_err = unsafe { nss_sys::nspr::PR_GetError() };
return Err(CompositeNssError(format!(
"SECKEY_ExtractPublicKey failed for composite SPKI (NSS error {nss_err})"
)));
}
let sig_item = SECItemStr {
type_: SECItemType::siBuffer,
data: signature.as_ptr() as *mut _,
len: signature.len() as u32,
};
let status = if use_pk11_verify {
let msg_item = SECItemStr {
type_: SECItemType::siBuffer,
data: message.as_ptr() as *mut _,
len: message.len() as u32,
};
unsafe { PK11_Verify(pub_key, &sig_item, &msg_item, ptr::null_mut()) }
} else {
let oid_der = match encode_oid_der(alg_oid_comps) {
Ok(der) => der,
Err(e) => {
unsafe { SECKEY_DestroyPublicKey(pub_key) };
return Err(CompositeNssError(e));
}
};
let msg_len = match i32::try_from(message.len()) {
Ok(n) => n,
Err(_) => {
unsafe { SECKEY_DestroyPublicKey(pub_key) };
return Err(CompositeNssError(
"VFY_VerifyDataWithAlgorithmID: message exceeds i32::MAX bytes".to_string(),
));
}
};
let alg_id = SECAlgorithmIDStr {
algorithm: SECItemStr {
type_: SECItemType::siDEROID,
data: oid_der.as_ptr() as *mut _,
len: oid_der.len() as u32,
},
parameters: SECItemStr {
type_: SECItemType::siBuffer,
data: if let Some(p) = alg_params_der {
p.as_ptr() as *mut _
} else {
ptr::null_mut()
},
len: alg_params_der.map(|p| p.len() as u32).unwrap_or(0),
},
};
unsafe {
VFY_VerifyDataWithAlgorithmID(
message.as_ptr(),
msg_len,
pub_key,
&sig_item,
&alg_id,
ptr::null(),
ptr::null_mut(),
)
}
};
unsafe { SECKEY_DestroyPublicKey(pub_key) };
if status == nss_sys::SECSuccess {
Ok(())
} else {
Err(CompositeNssError(
"composite component signature verification failed".to_string(),
))
}
}
fn verify_trad_component_nss(
trad_spki_der: &[u8],
m_prime: &[u8],
trad_sig: &[u8],
trad_alg: &TradAlg,
) -> Result<(), CompositeNssError> {
match trad_alg {
TradAlg::RsaPkcs15 { hash, .. } => {
let oid_comps: &[u32] = match *hash {
"sha256" => oids::SHA256_WITH_RSA,
"sha512" => oids::SHA512_WITH_RSA,
other => {
return Err(CompositeNssError(format!(
"unsupported RSA-PKCS15 hash: {other}"
)))
}
};
verify_component_nss(trad_spki_der, m_prime, trad_sig, oid_comps, None, false)
}
TradAlg::RsaPss { hash, .. } => {
let pss_params_der = build_rsa_pss_params_der(hash).map_err(CompositeNssError::from)?;
verify_component_nss(
trad_spki_der,
m_prime,
trad_sig,
oids::RSASSA_PSS,
Some(&pss_params_der),
false,
)
}
TradAlg::Ec { hash, .. } => {
let oid_comps: &[u32] = match *hash {
"sha256" => oids::ECDSA_WITH_SHA256,
"sha512" => oids::ECDSA_WITH_SHA512,
other => {
return Err(CompositeNssError(format!(
"unsupported ECDSA hash: {other}"
)))
}
};
verify_component_nss(trad_spki_der, m_prime, trad_sig, oid_comps, None, false)
}
TradAlg::Ed25519 => {
verify_component_nss(trad_spki_der, m_prime, trad_sig, oids::ED25519, None, true)
}
TradAlg::Ed448 => {
verify_component_nss(trad_spki_der, m_prime, trad_sig, oids::ED448, None, true)
}
}
}