use std::path::Path;
use crate::error::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum PadesProfile {
BasicSignature,
Timestamped,
#[default]
LongTerm,
LongTermArchive,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct SignOptions {
pub(crate) reason: Option<String>,
pub(crate) location: Option<String>,
pub(crate) contact_info: Option<String>,
pub(crate) field_name: Option<String>,
pub(crate) visible_rect: Option<(usize, [f64; 4])>,
pub(crate) profile: PadesProfile,
}
impl SignOptions {
pub fn new() -> Self {
Self::default()
}
pub fn reason(mut self, v: impl Into<String>) -> Self {
self.reason = Some(v.into());
self
}
pub fn location(mut self, v: impl Into<String>) -> Self {
self.location = Some(v.into());
self
}
pub fn contact_info(mut self, v: impl Into<String>) -> Self {
self.contact_info = Some(v.into());
self
}
pub fn field_name(mut self, v: impl Into<String>) -> Self {
self.field_name = Some(v.into());
self
}
pub fn visible_rect(mut self, page: usize, rect: [f64; 4]) -> Self {
self.visible_rect = Some((page, rect));
self
}
pub fn profile(mut self, p: PadesProfile) -> Self {
self.profile = p;
self
}
}
pub trait PdfSigner: Send + Sync {
fn sign(&self, data: &[u8]) -> Result<Vec<u8>>;
fn certificate_chain(&self) -> &[Vec<u8>];
}
pub struct Pkcs12Signer {
inner: pdf_sign::Pkcs12Signer,
}
impl std::fmt::Debug for Pkcs12Signer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Pkcs12Signer").finish_non_exhaustive()
}
}
impl Pkcs12Signer {
pub fn from_pfx_file<P: AsRef<Path>>(path: P, password: &str) -> Result<Self> {
let path_ref = path.as_ref();
let bytes = std::fs::read(path_ref).map_err(|source| match source.kind() {
std::io::ErrorKind::NotFound => crate::Error::FileNotFound {
path: path_ref.to_path_buf(),
},
_ => crate::Error::Io {
source,
path: Some(path_ref.to_path_buf()),
},
})?;
Self::from_pfx_bytes(&bytes, password)
}
pub fn from_pfx_bytes(bytes: &[u8], password: &str) -> Result<Self> {
let inner = pdf_sign::Pkcs12Signer::from_pkcs12(bytes, password).map_err(|e| {
crate::Error::InvalidSignature {
field: "<signer-load>".into(),
reason: format!("{e:?}"),
}
})?;
Ok(Self { inner })
}
}
impl PdfSigner for Pkcs12Signer {
fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
use pdf_sign::PdfSigner as InnerSigner;
self.inner
.sign(data)
.map_err(|e| crate::Error::InvalidSignature {
field: "<signing>".into(),
reason: format!("{e:?}"),
})
}
fn certificate_chain(&self) -> &[Vec<u8>] {
use pdf_sign::PdfSigner as InnerSigner;
self.inner.certificate_chain_der()
}
}
#[derive(Debug, Clone)]
pub struct SignatureInfo {
pub field_name: String,
pub signer_name: String,
pub timestamp: Option<String>,
pub profile: Option<PadesProfile>,
}
#[derive(Debug, Clone)]
pub struct SignatureValidation {
pub info: SignatureInfo,
pub status: SignatureStatus,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum SignatureStatus {
Valid,
Invalid {
reason: String,
},
Unknown {
reason: String,
},
}
#[derive(Debug, Clone, Default)]
pub struct SignatureValidationReport {
validations: Vec<SignatureValidation>,
}
impl SignatureValidationReport {
pub(crate) fn from_validations(validations: Vec<SignatureValidation>) -> Self {
Self { validations }
}
pub fn validations(&self) -> &[SignatureValidation] {
&self.validations
}
pub fn is_signed(&self) -> bool {
!self.validations.is_empty()
}
pub fn all_valid(&self) -> bool {
self.validations
.iter()
.all(|v| matches!(v.status, SignatureStatus::Valid))
}
pub fn failures(&self) -> Vec<&SignatureValidation> {
self.validations
.iter()
.filter(|v| !matches!(v.status, SignatureStatus::Valid))
.collect()
}
}