use crate::error::PdfError;
use crate::graphics::Color;
use crate::objects::{Dictionary, Object};
use crate::text::Font;
use chrono::{DateTime, Utc};
#[derive(Debug, Clone)]
pub struct SignatureField {
pub name: String,
pub signer: Option<SignerInfo>,
pub signature_value: Option<SignatureValue>,
pub lock_fields: Vec<String>,
pub required: bool,
pub reason: Option<String>,
pub location: Option<String>,
pub contact_info: Option<String>,
pub appearance: SignatureAppearance,
}
#[derive(Debug, Clone)]
pub struct SignerInfo {
pub name: String,
pub distinguished_name: Option<String>,
pub email: Option<String>,
pub organization: Option<String>,
pub organizational_unit: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SignatureValue {
pub timestamp: DateTime<Utc>,
pub document_hash: Vec<u8>,
pub algorithm: SignatureAlgorithm,
pub certificates: Vec<Certificate>,
pub signature_bytes: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SignatureAlgorithm {
RsaSha256,
RsaSha384,
RsaSha512,
EcdsaSha256,
DsaSha256,
}
#[derive(Debug, Clone)]
pub struct Certificate {
pub subject: String,
pub issuer: String,
pub serial_number: String,
pub not_before: DateTime<Utc>,
pub not_after: DateTime<Utc>,
pub public_key_info: String,
}
#[derive(Debug, Clone)]
pub struct SignatureAppearance {
pub show_name: bool,
pub show_date: bool,
pub show_reason: bool,
pub show_location: bool,
pub show_dn: bool,
pub show_labels: bool,
pub background_color: Option<Color>,
pub border_color: Color,
pub border_width: f64,
pub text_color: Color,
pub font: Font,
pub font_size: f64,
pub logo_data: Option<Vec<u8>>,
}
impl Default for SignatureAppearance {
fn default() -> Self {
Self {
show_name: true,
show_date: true,
show_reason: true,
show_location: false,
show_dn: false,
show_labels: true,
background_color: Some(Color::gray(0.95)),
border_color: Color::black(),
border_width: 1.0,
text_color: Color::black(),
font: Font::Helvetica,
font_size: 10.0,
logo_data: None,
}
}
}
impl SignatureField {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
signer: None,
signature_value: None,
lock_fields: Vec::new(),
required: false,
reason: None,
location: None,
contact_info: None,
appearance: SignatureAppearance::default(),
}
}
pub fn with_signer(mut self, signer: SignerInfo) -> Self {
self.signer = Some(signer);
self
}
pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
self.reason = Some(reason.into());
self
}
pub fn with_location(mut self, location: impl Into<String>) -> Self {
self.location = Some(location.into());
self
}
pub fn with_contact(mut self, contact: impl Into<String>) -> Self {
self.contact_info = Some(contact.into());
self
}
pub fn lock_fields_after_signing(mut self, fields: Vec<String>) -> Self {
self.lock_fields = fields;
self
}
pub fn required(mut self) -> Self {
self.required = true;
self
}
pub fn with_appearance(mut self, appearance: SignatureAppearance) -> Self {
self.appearance = appearance;
self
}
pub fn is_signed(&self) -> bool {
self.signature_value.is_some()
}
pub fn sign(&mut self, signer: SignerInfo, reason: Option<String>) -> Result<(), PdfError> {
if self.is_signed() {
return Err(PdfError::InvalidOperation(
"Field is already signed".to_string(),
));
}
let signature_value = SignatureValue {
timestamp: Utc::now(),
document_hash: vec![0; 32], algorithm: SignatureAlgorithm::RsaSha256,
certificates: vec![],
signature_bytes: vec![0; 256], };
self.signer = Some(signer);
if let Some(r) = reason {
self.reason = Some(r);
}
self.signature_value = Some(signature_value);
Ok(())
}
pub fn verify(&self) -> Result<bool, PdfError> {
if !self.is_signed() {
return Ok(false);
}
Ok(true)
}
pub fn generate_appearance(&self, width: f64, height: f64) -> Result<Vec<u8>, PdfError> {
let mut stream = Vec::new();
if let Some(bg_color) = self.appearance.background_color {
match bg_color {
Color::Rgb(r, g, b) => {
stream.extend(format!("{} {} {} rg\n", r, g, b).as_bytes());
}
Color::Gray(v) => {
stream.extend(format!("{} g\n", v).as_bytes());
}
Color::Cmyk(c, m, y, k) => {
stream.extend(format!("{} {} {} {} k\n", c, m, y, k).as_bytes());
}
}
stream.extend(format!("0 0 {} {} re f\n", width, height).as_bytes());
}
match self.appearance.border_color {
Color::Rgb(r, g, b) => {
stream.extend(format!("{} {} {} RG\n", r, g, b).as_bytes());
}
Color::Gray(v) => {
stream.extend(format!("{} G\n", v).as_bytes());
}
Color::Cmyk(c, m, y, k) => {
stream.extend(format!("{} {} {} {} K\n", c, m, y, k).as_bytes());
}
}
stream.extend(format!("{} w\n", self.appearance.border_width).as_bytes());
stream.extend(format!("0 0 {} {} re S\n", width, height).as_bytes());
stream.extend(b"BT\n");
stream.extend(
format!(
"/{} {} Tf\n",
self.appearance.font.pdf_name(),
self.appearance.font_size
)
.as_bytes(),
);
match self.appearance.text_color {
Color::Rgb(r, g, b) => {
stream.extend(format!("{} {} {} rg\n", r, g, b).as_bytes());
}
Color::Gray(v) => {
stream.extend(format!("{} g\n", v).as_bytes());
}
Color::Cmyk(c, m, y, k) => {
stream.extend(format!("{} {} {} {} k\n", c, m, y, k).as_bytes());
}
}
let mut y_pos = height - self.appearance.font_size - 5.0;
let x_pos = 5.0;
if self.is_signed() {
if let Some(ref signer) = self.signer {
if self.appearance.show_name {
let label = if self.appearance.show_labels {
"Digitally signed by: "
} else {
""
};
stream.extend(format!("{} {} Td\n", x_pos, y_pos).as_bytes());
stream.extend(format!("({}{}) Tj\n", label, signer.name).as_bytes());
y_pos -= self.appearance.font_size + 2.0;
}
if self.appearance.show_dn {
if let Some(ref dn) = signer.distinguished_name {
stream.extend(format!("{} {} Td\n", x_pos, y_pos).as_bytes());
stream.extend(format!("(DN: {}) Tj\n", dn).as_bytes());
y_pos -= self.appearance.font_size + 2.0;
}
}
}
if self.appearance.show_date {
if let Some(ref sig_value) = self.signature_value {
let label = if self.appearance.show_labels {
"Date: "
} else {
""
};
let date_str = sig_value
.timestamp
.format("%Y-%m-%d %H:%M:%S UTC")
.to_string();
stream.extend(format!("{} {} Td\n", x_pos, y_pos).as_bytes());
stream.extend(format!("({}{}) Tj\n", label, date_str).as_bytes());
y_pos -= self.appearance.font_size + 2.0;
}
}
if self.appearance.show_reason {
if let Some(ref reason) = self.reason {
let label = if self.appearance.show_labels {
"Reason: "
} else {
""
};
stream.extend(format!("{} {} Td\n", x_pos, y_pos).as_bytes());
stream.extend(format!("({}{}) Tj\n", label, reason).as_bytes());
y_pos -= self.appearance.font_size + 2.0;
}
}
if self.appearance.show_location {
if let Some(ref location) = self.location {
let label = if self.appearance.show_labels {
"Location: "
} else {
""
};
stream.extend(format!("{} {} Td\n", x_pos, y_pos).as_bytes());
stream.extend(format!("({}{}) Tj\n", label, location).as_bytes());
}
}
} else {
stream.extend(format!("{} {} Td\n", x_pos, y_pos).as_bytes());
stream.extend(b"(Click to sign) Tj\n");
}
stream.extend(b"ET\n");
Ok(stream)
}
pub fn to_dict(&self) -> Dictionary {
let mut dict = Dictionary::new();
dict.set("Type", Object::Name("Annot".to_string()));
dict.set("Subtype", Object::Name("Widget".to_string()));
dict.set("FT", Object::Name("Sig".to_string()));
dict.set("T", Object::String(self.name.clone()));
let mut flags = 0;
if self.required {
flags |= 2; }
dict.set("Ff", Object::Integer(flags));
if self.is_signed() {
let mut sig_dict = Dictionary::new();
sig_dict.set("Type", Object::Name("Sig".to_string()));
if let Some(ref signer) = self.signer {
sig_dict.set("Name", Object::String(signer.name.clone()));
if let Some(ref email) = signer.email {
sig_dict.set("ContactInfo", Object::String(email.clone()));
}
}
if let Some(ref reason) = self.reason {
sig_dict.set("Reason", Object::String(reason.clone()));
}
if let Some(ref location) = self.location {
sig_dict.set("Location", Object::String(location.clone()));
}
if let Some(ref sig_value) = self.signature_value {
sig_dict.set(
"M",
Object::String(sig_value.timestamp.format("%Y%m%d%H%M%S%z").to_string()),
);
}
dict.set("V", Object::Dictionary(sig_dict));
}
if !self.lock_fields.is_empty() {
let mut lock_dict = Dictionary::new();
lock_dict.set("Type", Object::Name("SigFieldLock".to_string()));
let fields: Vec<Object> = self
.lock_fields
.iter()
.map(|f| Object::String(f.clone()))
.collect();
lock_dict.set("Fields", Object::Array(fields));
dict.set("Lock", Object::Dictionary(lock_dict));
}
dict
}
}
impl SignerInfo {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
distinguished_name: None,
email: None,
organization: None,
organizational_unit: None,
}
}
pub fn with_email(mut self, email: impl Into<String>) -> Self {
self.email = Some(email.into());
self
}
pub fn with_organization(mut self, org: impl Into<String>) -> Self {
self.organization = Some(org.into());
self
}
pub fn build_dn(&mut self) {
let mut dn_parts = vec![format!("CN={}", self.name)];
if let Some(ref email) = self.email {
dn_parts.push(format!("emailAddress={}", email));
}
if let Some(ref org) = self.organization {
dn_parts.push(format!("O={}", org));
}
if let Some(ref ou) = self.organizational_unit {
dn_parts.push(format!("OU={}", ou));
}
self.distinguished_name = Some(dn_parts.join(", "));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_signature_field_creation() {
let field = SignatureField::new("sig1");
assert_eq!(field.name, "sig1");
assert!(!field.is_signed());
assert!(!field.required);
}
#[test]
fn test_signer_info() {
let mut signer = SignerInfo::new("John Doe")
.with_email("john@example.com")
.with_organization("ACME Corp");
signer.build_dn();
assert!(signer.distinguished_name.is_some());
assert!(signer.distinguished_name.unwrap().contains("CN=John Doe"));
}
#[test]
fn test_sign_field() {
let mut field = SignatureField::new("sig1");
let signer = SignerInfo::new("Jane Smith");
assert!(field
.sign(signer.clone(), Some("Approval".to_string()))
.is_ok());
assert!(field.is_signed());
assert_eq!(field.reason, Some("Approval".to_string()));
assert!(field.sign(signer, None).is_err());
}
#[test]
fn test_signature_appearance() {
let field = SignatureField::new("sig1");
let appearance = field.generate_appearance(200.0, 50.0);
assert!(appearance.is_ok());
let stream = appearance.unwrap();
assert!(!stream.is_empty());
}
#[test]
fn test_lock_fields() {
let field = SignatureField::new("sig1")
.lock_fields_after_signing(vec!["field1".to_string(), "field2".to_string()]);
assert_eq!(field.lock_fields.len(), 2);
}
#[test]
fn test_required_field() {
let field = SignatureField::new("sig1").required();
assert!(field.required);
let dict = field.to_dict();
if let Some(Object::Integer(flags)) = dict.get("Ff") {
assert_eq!(flags & 2, 2);
}
}
}