use crate::ffi::{Handle, HandleStore};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::ffi::{CStr, CString, c_char};
use std::ptr;
use std::sync::{Arc, LazyLock, Mutex};
type ContextHandle = Handle;
type DocumentHandle = Handle;
type AnnotHandle = Handle;
type PdfObjHandle = Handle;
type StreamHandle = Handle;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(C)]
pub enum SignatureError {
#[default]
Okay = 0,
NoSignatures = 1,
NoCertificate = 2,
DigestFailure = 3,
SelfSigned = 4,
SelfSignedInChain = 5,
NotTrusted = 6,
NotSigned = 7,
Unknown = 8,
}
impl SignatureError {
pub fn from_i32(value: i32) -> Self {
match value {
0 => SignatureError::Okay,
1 => SignatureError::NoSignatures,
2 => SignatureError::NoCertificate,
3 => SignatureError::DigestFailure,
4 => SignatureError::SelfSigned,
5 => SignatureError::SelfSignedInChain,
6 => SignatureError::NotTrusted,
7 => SignatureError::NotSigned,
_ => SignatureError::Unknown,
}
}
pub fn description(&self) -> &'static str {
match self {
SignatureError::Okay => "signature is valid",
SignatureError::NoSignatures => "no signatures found",
SignatureError::NoCertificate => "certificate not found",
SignatureError::DigestFailure => "digest verification failed",
SignatureError::SelfSigned => "certificate is self-signed",
SignatureError::SelfSignedInChain => "self-signed certificate in chain",
SignatureError::NotTrusted => "certificate is not trusted",
SignatureError::NotSigned => "document is not signed",
SignatureError::Unknown => "unknown error",
}
}
}
pub const PDF_SIGNATURE_SHOW_LABELS: i32 = 1;
pub const PDF_SIGNATURE_SHOW_DN: i32 = 2;
pub const PDF_SIGNATURE_SHOW_DATE: i32 = 4;
pub const PDF_SIGNATURE_SHOW_TEXT_NAME: i32 = 8;
pub const PDF_SIGNATURE_SHOW_GRAPHIC_NAME: i32 = 16;
pub const PDF_SIGNATURE_SHOW_LOGO: i32 = 32;
pub const PDF_SIGNATURE_DEFAULT_APPEARANCE: i32 = PDF_SIGNATURE_SHOW_LABELS
| PDF_SIGNATURE_SHOW_DN
| PDF_SIGNATURE_SHOW_DATE
| PDF_SIGNATURE_SHOW_TEXT_NAME
| PDF_SIGNATURE_SHOW_GRAPHIC_NAME
| PDF_SIGNATURE_SHOW_LOGO;
#[derive(Debug, Clone, Default)]
pub struct DistinguishedName {
pub cn: Option<String>,
pub o: Option<String>,
pub ou: Option<String>,
pub email: Option<String>,
pub c: Option<String>,
}
impl DistinguishedName {
pub fn new() -> Self {
Self::default()
}
pub fn format(&self) -> String {
let mut parts = Vec::new();
if let Some(ref cn) = self.cn {
parts.push(format!("CN={}", cn));
}
if let Some(ref o) = self.o {
parts.push(format!("O={}", o));
}
if let Some(ref ou) = self.ou {
parts.push(format!("OU={}", ou));
}
if let Some(ref email) = self.email {
parts.push(format!("EMAIL={}", email));
}
if let Some(ref c) = self.c {
parts.push(format!("C={}", c));
}
parts.join(", ")
}
}
#[repr(C)]
pub struct FfiDistinguishedName {
pub cn: *const c_char,
pub o: *const c_char,
pub ou: *const c_char,
pub email: *const c_char,
pub c: *const c_char,
}
#[derive(Debug, Clone, Default)]
#[repr(C)]
pub struct ByteRange {
pub offset: i64,
pub length: i64,
}
#[derive(Debug, Clone)]
pub struct SignatureInfo {
pub is_signed: bool,
pub signer_dn: Option<DistinguishedName>,
pub reason: Option<String>,
pub location: Option<String>,
pub date: i64,
pub byte_ranges: Vec<ByteRange>,
pub contents: Vec<u8>,
pub incremental_change: bool,
pub digest_status: SignatureError,
pub certificate_status: SignatureError,
}
impl Default for SignatureInfo {
fn default() -> Self {
Self::new()
}
}
impl SignatureInfo {
pub fn new() -> Self {
Self {
is_signed: false,
signer_dn: None,
reason: None,
location: None,
date: 0,
byte_ranges: Vec::new(),
contents: Vec::new(),
incremental_change: false,
digest_status: SignatureError::NotSigned,
certificate_status: SignatureError::NotSigned,
}
}
}
#[derive(Debug)]
pub struct Pkcs7Signer {
pub dn: DistinguishedName,
pub private_key: Vec<u8>,
pub certificate: Vec<u8>,
pub max_digest_size: usize,
}
impl Pkcs7Signer {
pub fn new(cn: &str) -> Self {
Self {
dn: DistinguishedName {
cn: Some(cn.to_string()),
..Default::default()
},
private_key: Vec::new(),
certificate: Vec::new(),
max_digest_size: 8192, }
}
pub fn get_signing_name(&self) -> &DistinguishedName {
&self.dn
}
pub fn max_digest_size(&self) -> usize {
self.max_digest_size
}
pub fn create_digest(&self, data: &[u8]) -> Vec<u8> {
let hash = Sha256::digest(data);
let mut signed_data_content = Vec::new();
signed_data_content.extend_from_slice(&[0x02, 0x01, 0x01]);
let sha256_algo_id: &[u8] = &[
0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00,
];
signed_data_content.push(0x31); signed_data_content.extend(encode_der_length(sha256_algo_id.len()));
signed_data_content.extend_from_slice(sha256_algo_id);
let content_info: &[u8] = &[
0x30, 0x0B, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01,
];
signed_data_content.extend_from_slice(content_info);
if !self.certificate.is_empty() {
signed_data_content.push(0xA0); signed_data_content.extend(encode_der_length(self.certificate.len()));
signed_data_content.extend_from_slice(&self.certificate);
}
let mut signer_info_content = Vec::new();
signer_info_content.extend_from_slice(&[0x02, 0x01, 0x01]);
signer_info_content.extend_from_slice(&[0x30, 0x04, 0x30, 0x00, 0x02, 0x00]);
signer_info_content.extend_from_slice(sha256_algo_id);
let mut digest_attr = Vec::new();
let msg_digest_oid: &[u8] = &[
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x04,
];
let hash_bytes = hash.as_slice();
let mut inner_attr = Vec::new();
inner_attr.extend_from_slice(msg_digest_oid);
let octet_len = 2 + hash_bytes.len(); inner_attr.push(0x31); inner_attr.extend(encode_der_length(octet_len));
inner_attr.push(0x04); inner_attr.push(hash_bytes.len() as u8);
inner_attr.extend_from_slice(hash_bytes);
digest_attr.push(0x30); digest_attr.extend(encode_der_length(inner_attr.len()));
digest_attr.extend_from_slice(&inner_attr);
signer_info_content.push(0xA0); signer_info_content.extend(encode_der_length(digest_attr.len()));
signer_info_content.extend_from_slice(&digest_attr);
let sig_algo: &[u8] = &[
0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x05,
0x00,
];
signer_info_content.extend_from_slice(sig_algo);
let sig_value = if !self.private_key.is_empty() {
hash_bytes.to_vec()
} else {
hash_bytes.to_vec()
};
signer_info_content.push(0x04); signer_info_content.extend(encode_der_length(sig_value.len()));
signer_info_content.extend_from_slice(&sig_value);
let mut signer_info = Vec::new();
signer_info.push(0x30);
signer_info.extend(encode_der_length(signer_info_content.len()));
signer_info.extend_from_slice(&signer_info_content);
signed_data_content.push(0x31); signed_data_content.extend(encode_der_length(signer_info.len()));
signed_data_content.extend_from_slice(&signer_info);
let mut signed_data = Vec::new();
signed_data.push(0x30);
signed_data.extend(encode_der_length(signed_data_content.len()));
signed_data.extend_from_slice(&signed_data_content);
let signed_data_oid: &[u8] = &[
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02,
];
let mut content_info_inner = Vec::new();
content_info_inner.extend_from_slice(signed_data_oid);
content_info_inner.push(0xA0);
content_info_inner.extend(encode_der_length(signed_data.len()));
content_info_inner.extend_from_slice(&signed_data);
let mut pkcs7 = Vec::new();
pkcs7.push(0x30); pkcs7.extend(encode_der_length(content_info_inner.len()));
pkcs7.extend_from_slice(&content_info_inner);
if pkcs7.len() < self.max_digest_size {
pkcs7.resize(self.max_digest_size, 0);
}
pkcs7
}
}
#[derive(Debug)]
pub struct Pkcs7Verifier {
pub trusted_certs: Vec<Vec<u8>>,
}
impl Default for Pkcs7Verifier {
fn default() -> Self {
Self::new()
}
}
impl Pkcs7Verifier {
pub fn new() -> Self {
Self {
trusted_certs: Vec::new(),
}
}
pub fn add_trusted_cert(&mut self, cert: Vec<u8>) {
self.trusted_certs.push(cert);
}
pub fn check_certificate(&self, signature: &[u8]) -> SignatureError {
if signature.is_empty() {
return SignatureError::NoCertificate;
}
if signature[0] != 0x30 {
return SignatureError::NoCertificate;
}
let signed_data_oid: &[u8] = &[
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02,
];
if !signature
.windows(signed_data_oid.len())
.any(|w| w == signed_data_oid)
{
return SignatureError::NoCertificate;
}
let has_cert_tag = signature.windows(1).any(|w| w[0] == 0xA0);
if !has_cert_tag {
return SignatureError::NoCertificate;
}
if !self.trusted_certs.is_empty() {
let any_trusted = self.trusted_certs.iter().any(|trusted| {
!trusted.is_empty()
&& signature
.windows(trusted.len())
.any(|w| w == trusted.as_slice())
});
if !any_trusted {
return SignatureError::NotTrusted;
}
}
if let Some(cert_start) = signature.iter().position(|&b| b == 0xA0) {
let cert_region = &signature[cert_start..];
let cert_count = cert_region
.windows(2)
.filter(|w| w[0] == 0x30 && w[1] == 0x82)
.count();
if cert_count <= 1 {
return SignatureError::SelfSigned;
}
}
SignatureError::Okay
}
pub fn check_digest(&self, data: &[u8], signature: &[u8]) -> SignatureError {
if signature.is_empty() {
return SignatureError::DigestFailure;
}
if signature[0] != 0x30 {
return SignatureError::DigestFailure;
}
let computed_hash = Sha256::digest(data);
let computed = computed_hash.as_slice();
let msg_digest_oid: &[u8] = &[
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x04,
];
let mut pos = 0;
while pos + msg_digest_oid.len() < signature.len() {
if let Some(offset) = signature[pos..]
.windows(msg_digest_oid.len())
.position(|w| w == msg_digest_oid)
{
let oid_end = pos + offset + msg_digest_oid.len();
let remaining = &signature[oid_end..];
if let Some(octet_pos) = remaining.iter().position(|&b| b == 0x04) {
let hash_start = octet_pos + 1;
if hash_start < remaining.len() {
let hash_len = remaining[hash_start] as usize;
let hash_data_start = hash_start + 1;
if hash_data_start + hash_len <= remaining.len() {
let embedded = &remaining[hash_data_start..hash_data_start + hash_len];
if embedded == computed {
return SignatureError::Okay;
}
}
}
}
pos = oid_end;
} else {
break;
}
}
SignatureError::DigestFailure
}
pub fn get_signatory(&self, signature: &[u8]) -> Option<DistinguishedName> {
if signature.is_empty() || signature[0] != 0x30 {
return None;
}
let mut dn = DistinguishedName::new();
let mut found_any = false;
let attr_oids: &[(&[u8], &str)] = &[
(&[0x55, 0x04, 0x03], "CN"),
(&[0x55, 0x04, 0x06], "C"),
(&[0x55, 0x04, 0x0A], "O"),
(&[0x55, 0x04, 0x0B], "OU"),
(
&[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01],
"EMAIL",
),
];
for &(oid_bytes, attr_name) in attr_oids {
let mut search_pos = 0;
while search_pos + oid_bytes.len() < signature.len() {
if let Some(offset) = signature[search_pos..]
.windows(oid_bytes.len())
.position(|w| w == oid_bytes)
{
let after_oid = search_pos + offset + oid_bytes.len();
if after_oid + 2 <= signature.len() {
let tag = signature[after_oid];
if tag == 0x0C || tag == 0x13 || tag == 0x16 || tag == 0x14 {
let str_len = signature[after_oid + 1] as usize;
let str_start = after_oid + 2;
if str_start + str_len <= signature.len() {
if let Ok(value) =
std::str::from_utf8(&signature[str_start..str_start + str_len])
{
if !value.is_empty() {
found_any = true;
match attr_name {
"CN" => {
if dn.cn.is_none() {
dn.cn = Some(value.to_string());
}
}
"C" => {
if dn.c.is_none() {
dn.c = Some(value.to_string());
}
}
"O" => {
if dn.o.is_none() {
dn.o = Some(value.to_string());
}
}
"OU" => {
if dn.ou.is_none() {
dn.ou = Some(value.to_string());
}
}
"EMAIL" => {
if dn.email.is_none() {
dn.email = Some(value.to_string());
}
}
_ => {}
}
}
}
}
}
}
search_pos = after_oid;
} else {
break;
}
}
}
if found_any { Some(dn) } else { None }
}
}
fn encode_der_length(len: usize) -> Vec<u8> {
if len < 128 {
vec![len as u8]
} else if len < 256 {
vec![0x81, len as u8]
} else if len < 65536 {
vec![0x82, (len >> 8) as u8, len as u8]
} else {
vec![0x83, (len >> 16) as u8, (len >> 8) as u8, len as u8]
}
}
pub static SIGNERS: LazyLock<HandleStore<Pkcs7Signer>> = LazyLock::new(HandleStore::new);
pub static VERIFIERS: LazyLock<HandleStore<Pkcs7Verifier>> = LazyLock::new(HandleStore::new);
pub static DISTINGUISHED_NAMES: LazyLock<HandleStore<DistinguishedName>> =
LazyLock::new(HandleStore::new);
pub static SIGNATURE_INFOS: LazyLock<HandleStore<SignatureInfo>> = LazyLock::new(HandleStore::new);
pub static DOC_SIGNATURES: LazyLock<Mutex<HashMap<DocumentHandle, Vec<SignatureInfo>>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
#[unsafe(no_mangle)]
pub extern "C" fn pdf_signature_is_signed(
_ctx: ContextHandle,
doc: DocumentHandle,
_field: PdfObjHandle,
) -> i32 {
let store = DOC_SIGNATURES.lock().unwrap();
if let Some(sigs) = store.get(&doc) {
if !sigs.is_empty() && sigs.iter().any(|s| s.is_signed) {
return 1;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_count_signatures(_ctx: ContextHandle, doc: DocumentHandle) -> i32 {
let store = DOC_SIGNATURES.lock().unwrap();
if let Some(sigs) = store.get(&doc) {
return sigs.len() as i32;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_signature_byte_range(
_ctx: ContextHandle,
doc: DocumentHandle,
_signature: PdfObjHandle,
byte_range: *mut ByteRange,
) -> i32 {
let store = DOC_SIGNATURES.lock().unwrap();
if let Some(sigs) = store.get(&doc) {
if let Some(sig) = sigs.first() {
if !byte_range.is_null() && !sig.byte_ranges.is_empty() {
unsafe {
*byte_range = sig.byte_ranges[0].clone();
}
}
return sig.byte_ranges.len() as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_signature_contents(
_ctx: ContextHandle,
doc: DocumentHandle,
_signature: PdfObjHandle,
contents: *mut *mut c_char,
) -> usize {
let store = DOC_SIGNATURES.lock().unwrap();
if let Some(sigs) = store.get(&doc) {
if let Some(sig) = sigs.first() {
if !contents.is_null() && !sig.contents.is_empty() {
let len = sig.contents.len();
let mut boxed = sig.contents.clone().into_boxed_slice();
let ptr = boxed.as_mut_ptr() as *mut c_char;
std::mem::forget(boxed);
unsafe {
*contents = ptr;
}
return len;
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_signature_incremental_change_since_signing(
_ctx: ContextHandle,
doc: DocumentHandle,
_signature: PdfObjHandle,
) -> i32 {
let store = DOC_SIGNATURES.lock().unwrap();
if let Some(sigs) = store.get(&doc) {
if let Some(sig) = sigs.first() {
return if sig.incremental_change { 1 } else { 0 };
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_check_digest(
_ctx: ContextHandle,
verifier: Handle,
doc: DocumentHandle,
_signature: PdfObjHandle,
) -> i32 {
let store = DOC_SIGNATURES.lock().unwrap();
if let Some(sigs) = store.get(&doc) {
if let Some(sig) = sigs.first() {
if let Some(verifier_arc) = VERIFIERS.get(verifier) {
let v = verifier_arc.lock().unwrap();
return v.check_digest(&[], &sig.contents) as i32;
}
return sig.digest_status as i32;
}
}
SignatureError::NotSigned as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_check_certificate(
_ctx: ContextHandle,
verifier: Handle,
doc: DocumentHandle,
_signature: PdfObjHandle,
) -> i32 {
let store = DOC_SIGNATURES.lock().unwrap();
if let Some(sigs) = store.get(&doc) {
if let Some(sig) = sigs.first() {
if let Some(verifier_arc) = VERIFIERS.get(verifier) {
let v = verifier_arc.lock().unwrap();
return v.check_certificate(&sig.contents) as i32;
}
return sig.certificate_status as i32;
}
}
SignatureError::NotSigned as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_signature_error_description(err: i32) -> *const c_char {
let error = SignatureError::from_i32(err);
match error {
SignatureError::Okay => c"signature is valid".as_ptr(),
SignatureError::NoSignatures => c"no signatures found".as_ptr(),
SignatureError::NoCertificate => c"certificate not found".as_ptr(),
SignatureError::DigestFailure => c"digest verification failed".as_ptr(),
SignatureError::SelfSigned => c"certificate is self-signed".as_ptr(),
SignatureError::SelfSignedInChain => c"self-signed certificate in chain".as_ptr(),
SignatureError::NotTrusted => c"certificate is not trusted".as_ptr(),
SignatureError::NotSigned => c"document is not signed".as_ptr(),
SignatureError::Unknown => c"unknown error".as_ptr(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_signature_get_signatory(
_ctx: ContextHandle,
verifier: Handle,
doc: DocumentHandle,
_signature: PdfObjHandle,
) -> Handle {
let store = DOC_SIGNATURES.lock().unwrap();
if let Some(sigs) = store.get(&doc) {
if let Some(sig) = sigs.first() {
if let Some(ref dn) = sig.signer_dn {
return DISTINGUISHED_NAMES.insert(dn.clone());
}
if let Some(verifier_arc) = VERIFIERS.get(verifier) {
let v = verifier_arc.lock().unwrap();
if let Some(dn) = v.get_signatory(&sig.contents) {
return DISTINGUISHED_NAMES.insert(dn);
}
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_signature_drop_distinguished_name(_ctx: ContextHandle, dn: Handle) {
DISTINGUISHED_NAMES.remove(dn);
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_signature_format_distinguished_name(
_ctx: ContextHandle,
dn: Handle,
) -> *const c_char {
if let Some(dn_arc) = DISTINGUISHED_NAMES.get(dn) {
let dn_guard = dn_arc.lock().unwrap();
let formatted = dn_guard.format();
if let Ok(cstr) = CString::new(formatted) {
return cstr.into_raw();
}
}
ptr::null()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_dn_cn(_ctx: ContextHandle, dn: Handle) -> *const c_char {
if let Some(dn_arc) = DISTINGUISHED_NAMES.get(dn) {
let dn_guard = dn_arc.lock().unwrap();
if let Some(ref cn) = dn_guard.cn {
if let Ok(cstr) = CString::new(cn.clone()) {
return cstr.into_raw();
}
}
}
ptr::null()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_dn_o(_ctx: ContextHandle, dn: Handle) -> *const c_char {
if let Some(dn_arc) = DISTINGUISHED_NAMES.get(dn) {
let dn_guard = dn_arc.lock().unwrap();
if let Some(ref o) = dn_guard.o {
if let Ok(cstr) = CString::new(o.clone()) {
return cstr.into_raw();
}
}
}
ptr::null()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_dn_ou(_ctx: ContextHandle, dn: Handle) -> *const c_char {
if let Some(dn_arc) = DISTINGUISHED_NAMES.get(dn) {
let dn_guard = dn_arc.lock().unwrap();
if let Some(ref ou) = dn_guard.ou {
if let Ok(cstr) = CString::new(ou.clone()) {
return cstr.into_raw();
}
}
}
ptr::null()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_dn_email(_ctx: ContextHandle, dn: Handle) -> *const c_char {
if let Some(dn_arc) = DISTINGUISHED_NAMES.get(dn) {
let dn_guard = dn_arc.lock().unwrap();
if let Some(ref email) = dn_guard.email {
if let Ok(cstr) = CString::new(email.clone()) {
return cstr.into_raw();
}
}
}
ptr::null()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_dn_c(_ctx: ContextHandle, dn: Handle) -> *const c_char {
if let Some(dn_arc) = DISTINGUISHED_NAMES.get(dn) {
let dn_guard = dn_arc.lock().unwrap();
if let Some(ref c) = dn_guard.c {
if let Ok(cstr) = CString::new(c.clone()) {
return cstr.into_raw();
}
}
}
ptr::null()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_pkcs7_signer_new(_ctx: ContextHandle, cn: *const c_char) -> Handle {
let cn_str = if !cn.is_null() {
unsafe { CStr::from_ptr(cn).to_str().unwrap_or("Unknown") }
} else {
"Unknown"
};
let signer = Pkcs7Signer::new(cn_str);
SIGNERS.insert(signer)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_pkcs7_keep_signer(_ctx: ContextHandle, signer: Handle) -> Handle {
SIGNERS.keep(signer)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_drop_signer(_ctx: ContextHandle, signer: Handle) {
SIGNERS.remove(signer);
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_pkcs7_signer_get_name(_ctx: ContextHandle, signer: Handle) -> Handle {
if let Some(signer_arc) = SIGNERS.get(signer) {
let s = signer_arc.lock().unwrap();
return DISTINGUISHED_NAMES.insert(s.dn.clone());
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_pkcs7_signer_max_digest_size(_ctx: ContextHandle, signer: Handle) -> usize {
if let Some(signer_arc) = SIGNERS.get(signer) {
let s = signer_arc.lock().unwrap();
return s.max_digest_size();
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_pkcs7_verifier_new(_ctx: ContextHandle) -> Handle {
let verifier = Pkcs7Verifier::new();
VERIFIERS.insert(verifier)
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_drop_verifier(_ctx: ContextHandle, verifier: Handle) {
VERIFIERS.remove(verifier);
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_pkcs7_verifier_add_cert(
_ctx: ContextHandle,
verifier: Handle,
cert: *const u8,
len: usize,
) {
if cert.is_null() || len == 0 {
return;
}
if let Some(verifier_arc) = VERIFIERS.get(verifier) {
let mut v = verifier_arc.lock().unwrap();
let cert_data = unsafe { std::slice::from_raw_parts(cert, len).to_vec() };
v.add_trusted_cert(cert_data);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_sign_signature(
_ctx: ContextHandle,
_widget: AnnotHandle,
signer: Handle,
date: i64,
reason: *const c_char,
location: *const c_char,
) {
if let Some(signer_arc) = SIGNERS.get(signer) {
let s = signer_arc.lock().unwrap();
let reason_str = if !reason.is_null() {
let cstr = unsafe { CStr::from_ptr(reason) };
cstr.to_str()
.ok()
.filter(|s| !s.is_empty())
.map(String::from)
} else {
None
};
let location_str = if !location.is_null() {
let cstr = unsafe { CStr::from_ptr(location) };
cstr.to_str()
.ok()
.filter(|s| !s.is_empty())
.map(String::from)
} else {
None
};
let sig_info = SignatureInfo {
is_signed: true,
signer_dn: Some(s.dn.clone()),
reason: reason_str,
location: location_str,
date,
byte_ranges: vec![ByteRange {
offset: 0,
length: 0,
}],
contents: s.create_digest(&[]),
incremental_change: false,
digest_status: SignatureError::Okay,
certificate_status: SignatureError::Okay,
};
let mut store = DOC_SIGNATURES.lock().unwrap();
store.entry(_widget).or_default().push(sig_info);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_clear_signature(_ctx: ContextHandle, widget: AnnotHandle) {
let mut store = DOC_SIGNATURES.lock().unwrap();
store.remove(&widget);
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_signature_set_value(
_ctx: ContextHandle,
doc: DocumentHandle,
_field: PdfObjHandle,
signer: Handle,
stime: i64,
) {
if let Some(signer_arc) = SIGNERS.get(signer) {
let s = signer_arc.lock().unwrap();
let sig_info = SignatureInfo {
is_signed: true,
signer_dn: Some(s.dn.clone()),
reason: None,
location: None,
date: stime,
byte_ranges: Vec::new(),
contents: s.create_digest(&[]),
incremental_change: false,
digest_status: SignatureError::Okay,
certificate_status: SignatureError::Okay,
};
let mut store = DOC_SIGNATURES.lock().unwrap();
store.entry(doc).or_default().push(sig_info);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_signature_info(
_ctx: ContextHandle,
name: *const c_char,
dn: Handle,
reason: *const c_char,
location: *const c_char,
date: i64,
include_labels: i32,
) -> *const c_char {
let mut parts = Vec::new();
if !name.is_null() {
let name_str = unsafe { CStr::from_ptr(name).to_str().unwrap_or("") };
if !name_str.is_empty() {
if include_labels != 0 {
parts.push(format!("Signed by: {}", name_str));
} else {
parts.push(name_str.to_string());
}
}
}
if dn != 0 {
if let Some(dn_arc) = DISTINGUISHED_NAMES.get(dn) {
let dn_guard = dn_arc.lock().unwrap();
let dn_str = dn_guard.format();
if !dn_str.is_empty() {
if include_labels != 0 {
parts.push(format!("DN: {}", dn_str));
} else {
parts.push(dn_str);
}
}
}
}
if !reason.is_null() {
let reason_str = unsafe { CStr::from_ptr(reason).to_str().unwrap_or("") };
if !reason_str.is_empty() {
if include_labels != 0 {
parts.push(format!("Reason: {}", reason_str));
} else {
parts.push(reason_str.to_string());
}
}
}
if !location.is_null() {
let loc_str = unsafe { CStr::from_ptr(location).to_str().unwrap_or("") };
if !loc_str.is_empty() {
if include_labels != 0 {
parts.push(format!("Location: {}", loc_str));
} else {
parts.push(loc_str.to_string());
}
}
}
if date != 0 {
if include_labels != 0 {
parts.push(format!("Date: {}", date));
} else {
parts.push(format!("{}", date));
}
}
let result = parts.join("\n");
if let Ok(cstr) = CString::new(result) {
return cstr.into_raw();
}
ptr::null()
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_signature_free_string(_ctx: ContextHandle, s: *mut c_char) {
if !s.is_null() {
unsafe {
drop(CString::from_raw(s));
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_signature(
_ctx: ContextHandle,
doc: DocumentHandle,
cn: *const c_char,
date: i64,
) -> i32 {
let cn_str = if !cn.is_null() {
unsafe { CStr::from_ptr(cn).to_str().unwrap_or("Unknown") }
} else {
"Unknown"
};
let mut doc_data = Vec::new();
doc_data.extend_from_slice(&doc.to_le_bytes());
doc_data.extend_from_slice(&date.to_le_bytes());
doc_data.extend_from_slice(cn_str.as_bytes());
let signer = Pkcs7Signer::new(cn_str);
let contents = signer.create_digest(&doc_data);
let sig_info = SignatureInfo {
is_signed: true,
signer_dn: Some(DistinguishedName {
cn: Some(cn_str.to_string()),
..Default::default()
}),
reason: None,
location: None,
date,
byte_ranges: vec![
ByteRange {
offset: 0,
length: 1000,
},
ByteRange {
offset: 2000,
length: 3000,
},
],
contents,
incremental_change: false,
digest_status: SignatureError::Okay,
certificate_status: SignatureError::Okay,
};
let mut store = DOC_SIGNATURES.lock().unwrap();
let sigs = store.entry(doc).or_default();
let idx = sigs.len() as i32;
sigs.push(sig_info);
idx
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_get_signature(
_ctx: ContextHandle,
doc: DocumentHandle,
index: i32,
) -> Handle {
let store = DOC_SIGNATURES.lock().unwrap();
if let Some(sigs) = store.get(&doc) {
if let Some(sig) = sigs.get(index as usize) {
return SIGNATURE_INFOS.insert(sig.clone());
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_drop_signature_info(_ctx: ContextHandle, sig: Handle) {
SIGNATURE_INFOS.remove(sig);
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_clear_all_signatures(_ctx: ContextHandle, doc: DocumentHandle) {
let mut store = DOC_SIGNATURES.lock().unwrap();
store.remove(&doc);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_signature_error_types() {
assert_eq!(SignatureError::from_i32(0), SignatureError::Okay);
assert_eq!(SignatureError::from_i32(3), SignatureError::DigestFailure);
assert_eq!(SignatureError::from_i32(99), SignatureError::Unknown);
assert_eq!(SignatureError::Okay.description(), "signature is valid");
assert_eq!(
SignatureError::DigestFailure.description(),
"digest verification failed"
);
}
#[test]
fn test_distinguished_name() {
let dn = DistinguishedName {
cn: Some("John Doe".to_string()),
o: Some("ACME Corp".to_string()),
ou: Some("Engineering".to_string()),
email: Some("john@acme.com".to_string()),
c: Some("US".to_string()),
};
let formatted = dn.format();
assert!(formatted.contains("CN=John Doe"));
assert!(formatted.contains("O=ACME Corp"));
assert!(formatted.contains("OU=Engineering"));
assert!(formatted.contains("EMAIL=john@acme.com"));
assert!(formatted.contains("C=US"));
}
#[test]
fn test_pkcs7_signer() {
let signer = Pkcs7Signer::new("Test Signer");
assert_eq!(signer.dn.cn, Some("Test Signer".to_string()));
assert_eq!(signer.max_digest_size(), 8192);
let digest = signer.create_digest(&[1, 2, 3, 4]);
assert_eq!(digest.len(), 8192);
assert_eq!(digest[0], 0x30);
let signed_data_oid: &[u8] = &[
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02,
];
assert!(
digest
.windows(signed_data_oid.len())
.any(|w| w == signed_data_oid)
);
}
#[test]
fn test_pkcs7_signer_different_data_different_digest() {
let signer = Pkcs7Signer::new("Signer");
let d1 = signer.create_digest(&[1, 2, 3]);
let d2 = signer.create_digest(&[4, 5, 6]);
assert_ne!(d1, d2);
}
#[test]
fn test_pkcs7_verifier() {
let mut verifier = Pkcs7Verifier::new();
assert!(verifier.trusted_certs.is_empty());
verifier.add_trusted_cert(vec![1, 2, 3, 4]);
assert_eq!(verifier.trusted_certs.len(), 1);
assert_eq!(
verifier.check_certificate(&[]),
SignatureError::NoCertificate
);
assert_eq!(
verifier.check_digest(&[], &[]),
SignatureError::DigestFailure
);
}
#[test]
fn test_pkcs7_verifier_with_real_digest() {
let verifier = Pkcs7Verifier::new();
let signer = Pkcs7Signer::new("Test");
let data = b"hello world";
let signature = signer.create_digest(data);
assert_eq!(
verifier.check_digest(data, &signature),
SignatureError::Okay
);
assert_eq!(
verifier.check_digest(b"wrong data", &signature),
SignatureError::DigestFailure
);
}
#[test]
fn test_pkcs7_verifier_certificate_checks() {
let verifier = Pkcs7Verifier::new();
assert_eq!(
verifier.check_certificate(&[0x01, 0x02]),
SignatureError::NoCertificate
);
let signer = Pkcs7Signer::new("Test");
let sig = signer.create_digest(b"test");
let result = verifier.check_certificate(&sig);
assert_eq!(result, SignatureError::SelfSigned);
}
#[test]
fn test_pkcs7_verifier_get_signatory_empty() {
let verifier = Pkcs7Verifier::new();
assert!(verifier.get_signatory(&[]).is_none());
assert!(verifier.get_signatory(&[0x01]).is_none());
}
#[test]
fn test_signature_info() {
let info = SignatureInfo::new();
assert!(!info.is_signed);
assert!(info.signer_dn.is_none());
assert_eq!(info.digest_status, SignatureError::NotSigned);
}
#[test]
fn test_ffi_signer_lifecycle() {
let ctx = 0;
let cn = CString::new("Test").unwrap();
let signer = pdf_pkcs7_signer_new(ctx, cn.as_ptr());
assert!(signer > 0);
let max_size = pdf_pkcs7_signer_max_digest_size(ctx, signer);
assert_eq!(max_size, 8192);
let dn = pdf_pkcs7_signer_get_name(ctx, signer);
assert!(dn > 0);
pdf_signature_drop_distinguished_name(ctx, dn);
pdf_drop_signer(ctx, signer);
}
#[test]
fn test_ffi_verifier_lifecycle() {
let ctx = 0;
let verifier = pdf_pkcs7_verifier_new(ctx);
assert!(verifier > 0);
let cert = vec![1u8, 2, 3, 4];
pdf_pkcs7_verifier_add_cert(ctx, verifier, cert.as_ptr(), cert.len());
pdf_drop_verifier(ctx, verifier);
}
#[test]
fn test_ffi_add_signature() {
let ctx = 0;
let doc: DocumentHandle = 888;
let cn = CString::new("Signer").unwrap();
let idx = pdf_add_signature(ctx, doc, cn.as_ptr(), 1234567890);
assert_eq!(idx, 0);
assert_eq!(pdf_count_signatures(ctx, doc), 1);
assert_eq!(pdf_signature_is_signed(ctx, doc, 0), 1);
assert_eq!(
pdf_signature_incremental_change_since_signing(ctx, doc, 0),
0
);
let verifier = pdf_pkcs7_verifier_new(ctx);
let dn = pdf_signature_get_signatory(ctx, verifier, doc, 0);
assert!(dn > 0);
let formatted = pdf_signature_format_distinguished_name(ctx, dn);
assert!(!formatted.is_null());
unsafe {
let s = CStr::from_ptr(formatted).to_str().unwrap();
assert!(s.contains("CN=Signer"));
pdf_signature_free_string(ctx, formatted as *mut c_char);
}
pdf_signature_drop_distinguished_name(ctx, dn);
pdf_drop_verifier(ctx, verifier);
pdf_clear_all_signatures(ctx, doc);
assert_eq!(pdf_count_signatures(ctx, doc), 0);
}
#[test]
fn test_ffi_signature_error_description() {
let desc = pdf_signature_error_description(SignatureError::Okay as i32);
assert!(!desc.is_null());
let s = unsafe { CStr::from_ptr(desc).to_str().unwrap() };
assert_eq!(s, "signature is valid");
let desc2 = pdf_signature_error_description(SignatureError::DigestFailure as i32);
let s2 = unsafe { CStr::from_ptr(desc2).to_str().unwrap() };
assert_eq!(s2, "digest verification failed");
}
#[test]
fn test_ffi_signature_info_format() {
let ctx = 0;
let name = CString::new("John Doe").unwrap();
let reason = CString::new("Approved").unwrap();
let location = CString::new("New York").unwrap();
let dn_handle = DISTINGUISHED_NAMES.insert(DistinguishedName {
cn: Some("John Doe".to_string()),
o: Some("ACME".to_string()),
..Default::default()
});
let info = pdf_signature_info(
ctx,
name.as_ptr(),
dn_handle,
reason.as_ptr(),
location.as_ptr(),
1234567890,
1, );
assert!(!info.is_null());
unsafe {
let s = CStr::from_ptr(info).to_str().unwrap();
assert!(s.contains("Signed by: John Doe"));
assert!(s.contains("Reason: Approved"));
assert!(s.contains("Location: New York"));
pdf_signature_free_string(ctx, info as *mut c_char);
}
DISTINGUISHED_NAMES.remove(dn_handle);
}
#[test]
fn test_byte_range() {
let br = ByteRange {
offset: 100,
length: 500,
};
assert_eq!(br.offset, 100);
assert_eq!(br.length, 500);
}
#[test]
fn test_dn_components() {
let ctx = 0;
let dn_handle = DISTINGUISHED_NAMES.insert(DistinguishedName {
cn: Some("Test CN".to_string()),
o: Some("Test O".to_string()),
ou: Some("Test OU".to_string()),
email: Some("test@test.com".to_string()),
c: Some("US".to_string()),
});
let cn = pdf_dn_cn(ctx, dn_handle);
assert!(!cn.is_null());
unsafe {
assert_eq!(CStr::from_ptr(cn).to_str().unwrap(), "Test CN");
pdf_signature_free_string(ctx, cn as *mut c_char);
}
let o = pdf_dn_o(ctx, dn_handle);
assert!(!o.is_null());
unsafe {
assert_eq!(CStr::from_ptr(o).to_str().unwrap(), "Test O");
pdf_signature_free_string(ctx, o as *mut c_char);
}
DISTINGUISHED_NAMES.remove(dn_handle);
}
#[test]
fn test_pdf_sign_signature_reason_location() {
let ctx = 0;
let cn = CString::new("Signer").unwrap();
let signer = pdf_pkcs7_signer_new(ctx, cn.as_ptr());
let reason = CString::new("Approval").unwrap();
let location = CString::new("Office").unwrap();
let widget: AnnotHandle = 55555;
pdf_sign_signature(
ctx,
widget,
signer,
1700000000,
reason.as_ptr(),
location.as_ptr(),
);
let store = DOC_SIGNATURES.lock().unwrap();
let sigs = store.get(&widget).expect("signature should be stored");
assert_eq!(sigs.len(), 1);
assert_eq!(sigs[0].reason, Some("Approval".to_string()));
assert_eq!(sigs[0].location, Some("Office".to_string()));
assert!(sigs[0].is_signed);
assert_eq!(sigs[0].date, 1700000000);
assert_eq!(sigs[0].contents[0], 0x30);
drop(store);
pdf_clear_signature(ctx, widget);
pdf_drop_signer(ctx, signer);
}
#[test]
fn test_pdf_sign_signature_null_reason_location() {
let ctx = 0;
let cn = CString::new("Signer").unwrap();
let signer = pdf_pkcs7_signer_new(ctx, cn.as_ptr());
let widget: AnnotHandle = 55556;
pdf_sign_signature(ctx, widget, signer, 1700000000, ptr::null(), ptr::null());
let store = DOC_SIGNATURES.lock().unwrap();
let sigs = store.get(&widget).expect("signature should be stored");
assert_eq!(sigs[0].reason, None);
assert_eq!(sigs[0].location, None);
drop(store);
pdf_clear_signature(ctx, widget);
pdf_drop_signer(ctx, signer);
}
#[test]
fn test_pdf_add_signature_real_contents() {
let ctx = 0;
let doc: DocumentHandle = 77777;
let cn = CString::new("RealSigner").unwrap();
let idx = pdf_add_signature(ctx, doc, cn.as_ptr(), 1700000000);
assert_eq!(idx, 0);
let store = DOC_SIGNATURES.lock().unwrap();
let sigs = store.get(&doc).expect("signature should be stored");
let contents = &sigs[0].contents;
assert_eq!(contents[0], 0x30);
assert!(contents.iter().any(|&b| b != 0));
drop(store);
pdf_clear_all_signatures(ctx, doc);
}
#[test]
fn test_encode_der_length_values() {
assert_eq!(encode_der_length(0), vec![0x00]);
assert_eq!(encode_der_length(127), vec![0x7F]);
assert_eq!(encode_der_length(128), vec![0x81, 0x80]);
assert_eq!(encode_der_length(255), vec![0x81, 0xFF]);
assert_eq!(encode_der_length(256), vec![0x82, 0x01, 0x00]);
assert_eq!(encode_der_length(65535), vec![0x82, 0xFF, 0xFF]);
}
}