use crate::crypto::xmlsec::wrapper::bindings;
use super::XmlSecKey;
use super::XmlSecResult;
use super::{XmlDocument, XmlSecError};
use std::os::raw::c_uchar;
use std::ptr::{null, null_mut, NonNull};
pub struct VerifiedReference {
pub uri: Option<String>,
pub predigest_xml: String,
}
pub struct XmlSecSignatureContext {
ctx: NonNull<bindings::xmlSecDSigCtx>,
}
impl XmlSecSignatureContext {
pub fn new() -> XmlSecResult<Self> {
super::xmlsec_internal::guarantee_xmlsec_init()?;
let ctx = unsafe { bindings::xmlSecDSigCtxCreate(null_mut()) };
let ctx = NonNull::new(ctx).ok_or(XmlSecError::ContextInitError)?;
Ok(Self { ctx })
}
pub fn new_with_flags(flags: u32) -> XmlSecResult<Self> {
let mut ctx = Self::new()?;
unsafe {
ctx.ctx.as_mut().flags |= flags;
}
Ok(ctx)
}
pub fn get_verified_references(&self) -> XmlSecResult<Vec<VerifiedReference>> {
let mut result = Vec::new();
unsafe {
let list_ptr = &self.ctx.as_ref().signedInfoReferences as *const bindings::xmlSecPtrList
as *mut bindings::xmlSecPtrList;
let count = bindings::xmlSecPtrListGetSize(list_ptr);
for i in 0..count {
let ref_ctx_ptr = bindings::xmlSecPtrListGetItem(list_ptr, i);
if ref_ctx_ptr.is_null() {
return Err(XmlSecError::SigningError);
}
let ref_ctx_ptr = ref_ctx_ptr as bindings::xmlSecDSigReferenceCtxPtr;
let ref_ctx = &*ref_ctx_ptr;
if ref_ctx.status != bindings::xmlSecDSigStatus_xmlSecDSigStatusSucceeded {
return Err(XmlSecError::VerifyError);
}
let uri = if ref_ctx.uri.is_null() {
None
} else {
let uri_cstr = std::ffi::CStr::from_ptr(ref_ctx.uri as *const std::ffi::c_char);
Some(
uri_cstr
.to_str()
.map_err(|_| XmlSecError::InvalidInputString)?
.to_string(),
)
};
let predigest_buf = bindings::xmlSecDSigReferenceCtxGetPreDigestBuffer(ref_ctx_ptr);
if predigest_buf.is_null() {
return Err(XmlSecError::VerifyError);
}
let data_ptr = bindings::xmlSecBufferGetData(predigest_buf);
let data_size = bindings::xmlSecBufferGetSize(predigest_buf);
let predigest_xml = predigest_xml_from_raw_buffer(data_ptr, data_size as usize)?;
result.push(VerifiedReference { uri, predigest_xml });
}
}
Ok(result)
}
pub fn insert_key(&mut self, key: XmlSecKey) -> Option<XmlSecKey> {
let mut old = None;
unsafe {
let ctx = self.ctx.as_mut();
if !ctx.signKey.is_null() {
old = Some(XmlSecKey::from_ptr(ctx.signKey));
}
ctx.signKey = XmlSecKey::leak(key);
}
old
}
pub fn sign_document(&self, doc: &XmlDocument, id_attr: Option<&str>) -> XmlSecResult<()> {
self.key_is_set()?;
let doc_ptr = doc.doc_ptr();
let root = if let Some(root) = doc.get_root_element() {
root
} else {
return Err(XmlSecError::RootNotFound);
};
let root_ptr = root.node_ptr() as *mut bindings::xmlNode;
if let Some(id_attr) = id_attr {
let cid =
std::ffi::CString::new(id_attr).map_err(|_| XmlSecError::InvalidInputString)?;
unsafe {
let mut list = [cid.as_bytes().as_ptr(), null()];
bindings::xmlSecAddIDs(
doc_ptr as *mut bindings::xmlDoc,
root_ptr,
list.as_mut_ptr(),
);
}
}
let signode = find_signode(root_ptr)?;
self.sign_node_raw(signode)
}
pub fn verify_document(&self, doc: &XmlDocument, id_attr: Option<&str>) -> XmlSecResult<bool> {
self.key_is_set()?;
let doc_ptr = doc.doc_ptr();
let root = if let Some(root) = doc.get_root_element() {
root
} else {
return Err(XmlSecError::RootNotFound);
};
let root_ptr = root.node_ptr() as *mut bindings::xmlNode;
if let Some(id_attr) = id_attr {
let cid =
std::ffi::CString::new(id_attr).map_err(|_| XmlSecError::InvalidInputString)?;
unsafe {
let mut list = [cid.as_bytes().as_ptr(), null()];
bindings::xmlSecAddIDs(
doc_ptr as *mut bindings::xmlDoc,
root_ptr,
list.as_mut_ptr(),
);
}
}
let signode = find_signode(root_ptr)?;
self.verify_node_raw(signode)
}
pub fn verify_node(&self, sig_node: &libxml::tree::Node) -> XmlSecResult<bool> {
self.key_is_set()?;
if let Some(ns) = sig_node.get_namespace() {
if ns.get_href() != "http://www.w3.org/2000/09/xmldsig#"
|| sig_node.get_name() != "Signature"
{
return Err(XmlSecError::NotASignatureNode);
}
} else {
return Err(XmlSecError::NotASignatureNode);
}
let node_ptr = sig_node.node_ptr();
self.verify_node_raw(node_ptr as *mut bindings::xmlNode)
}
}
fn predigest_xml_from_raw_buffer(
data_ptr: *const c_uchar,
data_size: usize,
) -> XmlSecResult<String> {
if data_size == 0 {
return Ok(String::new());
}
if data_ptr.is_null() {
return Err(XmlSecError::VerifyError);
}
let data_slice = unsafe { std::slice::from_raw_parts(data_ptr, data_size) };
let predigest_xml = std::str::from_utf8(data_slice)
.map_err(|_| XmlSecError::InvalidInputString)?
.to_string();
Ok(predigest_xml)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn predigest_xml_from_raw_buffer_accepts_empty_buffers() {
let predigest_xml = predigest_xml_from_raw_buffer(std::ptr::null(), 0)
.expect("zero-length pre-digest buffers should be accepted");
assert!(predigest_xml.is_empty());
}
#[test]
fn predigest_xml_from_raw_buffer_rejects_invalid_utf8() {
let bytes = [0xff];
let error = predigest_xml_from_raw_buffer(bytes.as_ptr(), bytes.len())
.expect_err("non-utf8 pre-digest buffers should be rejected");
assert!(matches!(error, XmlSecError::InvalidInputString));
}
}
impl XmlSecSignatureContext {
fn key_is_set(&self) -> XmlSecResult<()> {
unsafe {
if !self.ctx.as_ref().signKey.is_null() {
Ok(())
} else {
Err(XmlSecError::KeyNotLoaded)
}
}
}
fn sign_node_raw(&self, node: *mut bindings::xmlNode) -> XmlSecResult<()> {
let rc = unsafe { bindings::xmlSecDSigCtxSign(self.ctx.as_ptr(), node) };
if rc < 0 {
Err(XmlSecError::SigningError)
} else {
Ok(())
}
}
fn verify_node_raw(&self, node: *mut bindings::xmlNode) -> XmlSecResult<bool> {
let rc = unsafe { bindings::xmlSecDSigCtxVerify(self.ctx.as_ptr(), node) };
if rc < 0 {
return Err(XmlSecError::VerifyError);
}
match unsafe { self.ctx.as_ref().status } {
bindings::xmlSecDSigStatus_xmlSecDSigStatusSucceeded => Ok(true),
_ => Ok(false),
}
}
}
impl Drop for XmlSecSignatureContext {
fn drop(&mut self) {
unsafe { bindings::xmlSecDSigCtxDestroy(self.ctx.as_ptr()) };
}
}
fn find_signode(tree: *mut bindings::xmlNode) -> XmlSecResult<*mut bindings::xmlNode> {
let signode = unsafe {
bindings::xmlSecFindNode(
tree,
&bindings::xmlSecNodeSignature as *const c_uchar,
&bindings::xmlSecDSigNs as *const c_uchar,
)
};
if signode.is_null() {
return Err(XmlSecError::NodeNotFound);
}
Ok(signode)
}