Skip to main content

oxidize_pdf/forms/
signature_field.rs

1//! Digital signature fields implementation according to ISO 32000-1 Section 12.7.4.5
2//!
3//! This module provides signature field support including visual representation,
4//! signature metadata, and lock fields after signing.
5
6use crate::error::PdfError;
7use crate::graphics::Color;
8use crate::objects::{Dictionary, Object};
9use crate::text::Font;
10use chrono::{DateTime, Utc};
11
12/// Signature field for digital signatures
13#[derive(Debug, Clone)]
14pub struct SignatureField {
15    /// Field name (unique identifier)
16    pub name: String,
17    /// Signer information
18    pub signer: Option<SignerInfo>,
19    /// Signature value (placeholder for actual signature)
20    pub signature_value: Option<SignatureValue>,
21    /// Fields to lock after signing
22    pub lock_fields: Vec<String>,
23    /// Whether signature is required
24    pub required: bool,
25    /// Signature reason
26    pub reason: Option<String>,
27    /// Signature location
28    pub location: Option<String>,
29    /// Contact information
30    pub contact_info: Option<String>,
31    /// Visual appearance settings
32    pub appearance: SignatureAppearance,
33}
34
35/// Information about the signer
36#[derive(Debug, Clone)]
37pub struct SignerInfo {
38    /// Name of the signer
39    pub name: String,
40    /// Distinguished name (DN)
41    pub distinguished_name: Option<String>,
42    /// Email address
43    pub email: Option<String>,
44    /// Organization
45    pub organization: Option<String>,
46    /// Organizational unit
47    pub organizational_unit: Option<String>,
48}
49
50/// Signature value and metadata
51#[derive(Debug, Clone)]
52pub struct SignatureValue {
53    /// Timestamp of signature
54    pub timestamp: DateTime<Utc>,
55    /// Hash of the document
56    pub document_hash: Vec<u8>,
57    /// Signature algorithm
58    pub algorithm: SignatureAlgorithm,
59    /// Certificate chain (placeholder)
60    pub certificates: Vec<Certificate>,
61    /// Actual signature bytes (placeholder)
62    pub signature_bytes: Vec<u8>,
63}
64
65/// Signature algorithms
66#[derive(Debug, Clone, Copy, PartialEq)]
67pub enum SignatureAlgorithm {
68    /// RSA with SHA-256
69    RsaSha256,
70    /// RSA with SHA-384
71    RsaSha384,
72    /// RSA with SHA-512
73    RsaSha512,
74    /// ECDSA with SHA-256
75    EcdsaSha256,
76    /// DSA with SHA-256
77    DsaSha256,
78}
79
80/// Certificate placeholder
81#[derive(Debug, Clone)]
82pub struct Certificate {
83    /// Subject name
84    pub subject: String,
85    /// Issuer name
86    pub issuer: String,
87    /// Serial number
88    pub serial_number: String,
89    /// Not before date
90    pub not_before: DateTime<Utc>,
91    /// Not after date
92    pub not_after: DateTime<Utc>,
93    /// Public key info
94    pub public_key_info: String,
95}
96
97/// Visual appearance settings for signature field
98#[derive(Debug, Clone)]
99pub struct SignatureAppearance {
100    /// Show signer name
101    pub show_name: bool,
102    /// Show date/time
103    pub show_date: bool,
104    /// Show reason
105    pub show_reason: bool,
106    /// Show location
107    pub show_location: bool,
108    /// Show distinguished name
109    pub show_dn: bool,
110    /// Show labels
111    pub show_labels: bool,
112    /// Background color
113    pub background_color: Option<Color>,
114    /// Border color
115    pub border_color: Color,
116    /// Border width
117    pub border_width: f64,
118    /// Text color
119    pub text_color: Color,
120    /// Font for text
121    pub font: Font,
122    /// Font size
123    pub font_size: f64,
124    /// Custom logo/image
125    pub logo_data: Option<Vec<u8>>,
126}
127
128impl Default for SignatureAppearance {
129    fn default() -> Self {
130        Self {
131            show_name: true,
132            show_date: true,
133            show_reason: true,
134            show_location: false,
135            show_dn: false,
136            show_labels: true,
137            background_color: Some(Color::gray(0.95)),
138            border_color: Color::black(),
139            border_width: 1.0,
140            text_color: Color::black(),
141            font: Font::Helvetica,
142            font_size: 10.0,
143            logo_data: None,
144        }
145    }
146}
147
148impl SignatureField {
149    /// Create a new signature field
150    pub fn new(name: impl Into<String>) -> Self {
151        Self {
152            name: name.into(),
153            signer: None,
154            signature_value: None,
155            lock_fields: Vec::new(),
156            required: false,
157            reason: None,
158            location: None,
159            contact_info: None,
160            appearance: SignatureAppearance::default(),
161        }
162    }
163
164    /// Set the signer information
165    pub fn with_signer(mut self, signer: SignerInfo) -> Self {
166        self.signer = Some(signer);
167        self
168    }
169
170    /// Set the signature reason
171    pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
172        self.reason = Some(reason.into());
173        self
174    }
175
176    /// Set the signature location
177    pub fn with_location(mut self, location: impl Into<String>) -> Self {
178        self.location = Some(location.into());
179        self
180    }
181
182    /// Set contact information
183    pub fn with_contact(mut self, contact: impl Into<String>) -> Self {
184        self.contact_info = Some(contact.into());
185        self
186    }
187
188    /// Add fields to lock after signing
189    pub fn lock_fields_after_signing(mut self, fields: Vec<String>) -> Self {
190        self.lock_fields = fields;
191        self
192    }
193
194    /// Mark field as required
195    pub fn required(mut self) -> Self {
196        self.required = true;
197        self
198    }
199
200    /// Customize appearance
201    pub fn with_appearance(mut self, appearance: SignatureAppearance) -> Self {
202        self.appearance = appearance;
203        self
204    }
205
206    /// Check if field is signed
207    pub fn is_signed(&self) -> bool {
208        self.signature_value.is_some()
209    }
210
211    /// Sign the field (placeholder implementation)
212    pub fn sign(&mut self, signer: SignerInfo, reason: Option<String>) -> Result<(), PdfError> {
213        if self.is_signed() {
214            return Err(PdfError::InvalidOperation(
215                "Field is already signed".to_string(),
216            ));
217        }
218
219        // Create signature value (placeholder)
220        let signature_value = SignatureValue {
221            timestamp: Utc::now(),
222            document_hash: vec![0; 32], // Placeholder hash
223            algorithm: SignatureAlgorithm::RsaSha256,
224            certificates: vec![],
225            signature_bytes: vec![0; 256], // Placeholder signature
226        };
227
228        self.signer = Some(signer);
229        if let Some(r) = reason {
230            self.reason = Some(r);
231        }
232        self.signature_value = Some(signature_value);
233
234        Ok(())
235    }
236
237    /// Verify signature (placeholder implementation)
238    pub fn verify(&self) -> Result<bool, PdfError> {
239        if !self.is_signed() {
240            return Ok(false);
241        }
242
243        // Placeholder verification - always returns true for now
244        // In a real implementation, this would verify the signature
245        // against the document hash and certificate chain
246        Ok(true)
247    }
248
249    /// Generate appearance stream for the signature field
250    pub fn generate_appearance(&self, width: f64, height: f64) -> Result<Vec<u8>, PdfError> {
251        let mut stream = Vec::new();
252
253        // Background — routed through the shared NaN-sanitising helper
254        // (issues #220 + #221).
255        if let Some(bg_color) = self.appearance.background_color {
256            crate::graphics::color::write_fill_color_bytes(&mut stream, bg_color);
257            stream.extend(format!("0 0 {} {} re f\n", width, height).as_bytes());
258        }
259
260        // Border
261        crate::graphics::color::write_stroke_color_bytes(&mut stream, self.appearance.border_color);
262        stream.extend(format!("{} w\n", self.appearance.border_width).as_bytes());
263        stream.extend(format!("0 0 {} {} re S\n", width, height).as_bytes());
264
265        // Text content
266        stream.extend(b"BT\n");
267        stream.extend(
268            format!(
269                "/{} {} Tf\n",
270                self.appearance.font.pdf_name(),
271                self.appearance.font_size
272            )
273            .as_bytes(),
274        );
275        crate::graphics::color::write_fill_color_bytes(&mut stream, self.appearance.text_color);
276
277        let mut y_pos = height - self.appearance.font_size - 5.0;
278        let x_pos = 5.0;
279
280        if self.is_signed() {
281            // Signed appearance
282            if let Some(ref signer) = self.signer {
283                if self.appearance.show_name {
284                    let label = if self.appearance.show_labels {
285                        "Digitally signed by: "
286                    } else {
287                        ""
288                    };
289                    stream.extend(format!("{} {} Td\n", x_pos, y_pos).as_bytes());
290                    stream.extend(format!("({}{}) Tj\n", label, signer.name).as_bytes());
291                    y_pos -= self.appearance.font_size + 2.0;
292                }
293
294                if self.appearance.show_dn {
295                    if let Some(ref dn) = signer.distinguished_name {
296                        stream.extend(format!("{} {} Td\n", x_pos, y_pos).as_bytes());
297                        stream.extend(format!("(DN: {}) Tj\n", dn).as_bytes());
298                        y_pos -= self.appearance.font_size + 2.0;
299                    }
300                }
301            }
302
303            if self.appearance.show_date {
304                if let Some(ref sig_value) = self.signature_value {
305                    let label = if self.appearance.show_labels {
306                        "Date: "
307                    } else {
308                        ""
309                    };
310                    let date_str = sig_value
311                        .timestamp
312                        .format("%Y-%m-%d %H:%M:%S UTC")
313                        .to_string();
314                    stream.extend(format!("{} {} Td\n", x_pos, y_pos).as_bytes());
315                    stream.extend(format!("({}{}) Tj\n", label, date_str).as_bytes());
316                    y_pos -= self.appearance.font_size + 2.0;
317                }
318            }
319
320            if self.appearance.show_reason {
321                if let Some(ref reason) = self.reason {
322                    let label = if self.appearance.show_labels {
323                        "Reason: "
324                    } else {
325                        ""
326                    };
327                    stream.extend(format!("{} {} Td\n", x_pos, y_pos).as_bytes());
328                    stream.extend(format!("({}{}) Tj\n", label, reason).as_bytes());
329                    y_pos -= self.appearance.font_size + 2.0;
330                }
331            }
332
333            if self.appearance.show_location {
334                if let Some(ref location) = self.location {
335                    let label = if self.appearance.show_labels {
336                        "Location: "
337                    } else {
338                        ""
339                    };
340                    stream.extend(format!("{} {} Td\n", x_pos, y_pos).as_bytes());
341                    stream.extend(format!("({}{}) Tj\n", label, location).as_bytes());
342                }
343            }
344        } else {
345            // Unsigned appearance - show placeholder
346            stream.extend(format!("{} {} Td\n", x_pos, y_pos).as_bytes());
347            stream.extend(b"(Click to sign) Tj\n");
348        }
349
350        stream.extend(b"ET\n");
351
352        Ok(stream)
353    }
354
355    /// Convert to PDF dictionary
356    pub fn to_dict(&self) -> Dictionary {
357        let mut dict = Dictionary::new();
358
359        dict.set("Type", Object::Name("Annot".to_string()));
360        dict.set("Subtype", Object::Name("Widget".to_string()));
361        dict.set("FT", Object::Name("Sig".to_string()));
362        dict.set("T", Object::String(self.name.clone()));
363
364        // Field flags
365        let mut flags = 0;
366        if self.required {
367            flags |= 2; // Required flag
368        }
369        dict.set("Ff", Object::Integer(flags));
370
371        // Signature dictionary
372        if self.is_signed() {
373            let mut sig_dict = Dictionary::new();
374            sig_dict.set("Type", Object::Name("Sig".to_string()));
375
376            if let Some(ref signer) = self.signer {
377                sig_dict.set("Name", Object::String(signer.name.clone()));
378                if let Some(ref email) = signer.email {
379                    sig_dict.set("ContactInfo", Object::String(email.clone()));
380                }
381            }
382
383            if let Some(ref reason) = self.reason {
384                sig_dict.set("Reason", Object::String(reason.clone()));
385            }
386
387            if let Some(ref location) = self.location {
388                sig_dict.set("Location", Object::String(location.clone()));
389            }
390
391            if let Some(ref sig_value) = self.signature_value {
392                sig_dict.set(
393                    "M",
394                    Object::String(sig_value.timestamp.format("%Y%m%d%H%M%S%z").to_string()),
395                );
396            }
397
398            dict.set("V", Object::Dictionary(sig_dict));
399        }
400
401        // Lock dictionary for fields to lock after signing
402        if !self.lock_fields.is_empty() {
403            let mut lock_dict = Dictionary::new();
404            lock_dict.set("Type", Object::Name("SigFieldLock".to_string()));
405
406            let fields: Vec<Object> = self
407                .lock_fields
408                .iter()
409                .map(|f| Object::String(f.clone()))
410                .collect();
411            lock_dict.set("Fields", Object::Array(fields));
412
413            dict.set("Lock", Object::Dictionary(lock_dict));
414        }
415
416        dict
417    }
418}
419
420impl SignerInfo {
421    /// Create new signer info
422    pub fn new(name: impl Into<String>) -> Self {
423        Self {
424            name: name.into(),
425            distinguished_name: None,
426            email: None,
427            organization: None,
428            organizational_unit: None,
429        }
430    }
431
432    /// Set email
433    pub fn with_email(mut self, email: impl Into<String>) -> Self {
434        self.email = Some(email.into());
435        self
436    }
437
438    /// Set organization
439    pub fn with_organization(mut self, org: impl Into<String>) -> Self {
440        self.organization = Some(org.into());
441        self
442    }
443
444    /// Build distinguished name
445    pub fn build_dn(&mut self) {
446        let mut dn_parts = vec![format!("CN={}", self.name)];
447
448        if let Some(ref email) = self.email {
449            dn_parts.push(format!("emailAddress={}", email));
450        }
451
452        if let Some(ref org) = self.organization {
453            dn_parts.push(format!("O={}", org));
454        }
455
456        if let Some(ref ou) = self.organizational_unit {
457            dn_parts.push(format!("OU={}", ou));
458        }
459
460        self.distinguished_name = Some(dn_parts.join(", "));
461    }
462}
463
464#[cfg(test)]
465mod tests {
466    use super::*;
467
468    #[test]
469    fn test_signature_field_creation() {
470        let field = SignatureField::new("sig1");
471        assert_eq!(field.name, "sig1");
472        assert!(!field.is_signed());
473        assert!(!field.required);
474    }
475
476    #[test]
477    fn test_signer_info() {
478        let mut signer = SignerInfo::new("John Doe")
479            .with_email("john@example.com")
480            .with_organization("ACME Corp");
481
482        signer.build_dn();
483        assert!(signer.distinguished_name.is_some());
484        assert!(signer.distinguished_name.unwrap().contains("CN=John Doe"));
485    }
486
487    #[test]
488    fn test_sign_field() {
489        let mut field = SignatureField::new("sig1");
490        let signer = SignerInfo::new("Jane Smith");
491
492        assert!(field
493            .sign(signer.clone(), Some("Approval".to_string()))
494            .is_ok());
495        assert!(field.is_signed());
496        assert_eq!(field.reason, Some("Approval".to_string()));
497
498        // Cannot sign twice
499        assert!(field.sign(signer, None).is_err());
500    }
501
502    #[test]
503    fn test_signature_appearance() {
504        let field = SignatureField::new("sig1");
505        let appearance = field.generate_appearance(200.0, 50.0);
506
507        assert!(appearance.is_ok());
508        let stream = appearance.unwrap();
509        assert!(!stream.is_empty());
510    }
511
512    #[test]
513    fn test_lock_fields() {
514        let field = SignatureField::new("sig1")
515            .lock_fields_after_signing(vec!["field1".to_string(), "field2".to_string()]);
516
517        assert_eq!(field.lock_fields.len(), 2);
518    }
519
520    #[test]
521    fn test_required_field() {
522        let field = SignatureField::new("sig1").required();
523        assert!(field.required);
524
525        let dict = field.to_dict();
526        // Check that required flag is set
527        if let Some(Object::Integer(flags)) = dict.get("Ff") {
528            assert_eq!(flags & 2, 2);
529        }
530    }
531}