rust_sign/
signer.rs

1//! Document signing functionality.
2
3use crate::error::Result;
4use crate::hash::{hash_bytes, hash_file, hash_reader};
5use crate::keys::KeyPair;
6use crate::signature::DocumentSignature;
7use std::collections::HashMap;
8use std::io::Read;
9use std::path::Path;
10
11/// A builder for creating document signatures.
12#[derive(Debug)]
13pub struct Signer<'a> {
14    keypair: &'a KeyPair,
15    signer_id: Option<String>,
16    metadata: HashMap<String, String>,
17}
18
19impl<'a> Signer<'a> {
20    /// Create a new signer with the given keypair.
21    pub fn new(keypair: &'a KeyPair) -> Self {
22        Self {
23            keypair,
24            signer_id: None,
25            metadata: HashMap::new(),
26        }
27    }
28
29    /// Set the signer ID (e.g., email address, name).
30    pub fn with_signer_id<S: Into<String>>(mut self, signer_id: S) -> Self {
31        self.signer_id = Some(signer_id.into());
32        self
33    }
34
35    /// Add a metadata key-value pair.
36    pub fn with_metadata<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
37        self.metadata.insert(key.into(), value.into());
38        self
39    }
40
41    /// Sign a byte slice.
42    pub fn sign_bytes(&self, data: &[u8]) -> Result<DocumentSignature> {
43        let hash = hash_bytes(data);
44        let mut doc_sig = DocumentSignature::new(hash);
45        doc_sig.add_signature_with_metadata(
46            self.keypair,
47            self.signer_id.clone(),
48            self.metadata.clone(),
49        )?;
50        Ok(doc_sig)
51    }
52
53    /// Sign a file.
54    pub fn sign_file<P: AsRef<Path>>(&self, path: P) -> Result<DocumentSignature> {
55        let hash = hash_file(path)?;
56        let mut doc_sig = DocumentSignature::new(hash);
57        doc_sig.add_signature_with_metadata(
58            self.keypair,
59            self.signer_id.clone(),
60            self.metadata.clone(),
61        )?;
62        Ok(doc_sig)
63    }
64
65    /// Sign data from a reader (streaming).
66    pub fn sign_reader<R: Read>(&self, reader: &mut R) -> Result<DocumentSignature> {
67        let hash = hash_reader(reader)?;
68        let mut doc_sig = DocumentSignature::new(hash);
69        doc_sig.add_signature_with_metadata(
70            self.keypair,
71            self.signer_id.clone(),
72            self.metadata.clone(),
73        )?;
74        Ok(doc_sig)
75    }
76
77    /// Add a signature to an existing document signature.
78    pub fn cosign(&self, doc_sig: &mut DocumentSignature) -> Result<()> {
79        doc_sig.add_signature_with_metadata(
80            self.keypair,
81            self.signer_id.clone(),
82            self.metadata.clone(),
83        )
84    }
85}
86
87/// Convenience function to sign bytes with a keypair.
88pub fn sign_bytes(keypair: &KeyPair, data: &[u8]) -> Result<DocumentSignature> {
89    Signer::new(keypair).sign_bytes(data)
90}
91
92/// Convenience function to sign a file with a keypair.
93pub fn sign_file<P: AsRef<Path>>(keypair: &KeyPair, path: P) -> Result<DocumentSignature> {
94    Signer::new(keypair).sign_file(path)
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_sign_bytes() {
103        let keypair = KeyPair::generate();
104        let data = b"Test document content";
105        
106        let doc_sig = Signer::new(&keypair)
107            .with_signer_id("test@example.com")
108            .sign_bytes(data)
109            .unwrap();
110        
111        assert_eq!(doc_sig.signature_count(), 1);
112        assert_eq!(
113            doc_sig.signatures[0].signer_id,
114            Some("test@example.com".to_string())
115        );
116    }
117
118    #[test]
119    fn test_sign_with_metadata() {
120        let keypair = KeyPair::generate();
121        let data = b"Test document content";
122        
123        let doc_sig = Signer::new(&keypair)
124            .with_signer_id("test@example.com")
125            .with_metadata("purpose", "testing")
126            .with_metadata("version", "1.0")
127            .sign_bytes(data)
128            .unwrap();
129        
130        assert_eq!(doc_sig.signatures[0].metadata.get("purpose"), Some(&"testing".to_string()));
131        assert_eq!(doc_sig.signatures[0].metadata.get("version"), Some(&"1.0".to_string()));
132    }
133
134    #[test]
135    fn test_cosign() {
136        let keypair1 = KeyPair::generate();
137        let keypair2 = KeyPair::generate();
138        let data = b"Test document content";
139        
140        let mut doc_sig = Signer::new(&keypair1)
141            .with_signer_id("alice@example.com")
142            .sign_bytes(data)
143            .unwrap();
144        
145        Signer::new(&keypair2)
146            .with_signer_id("bob@example.com")
147            .cosign(&mut doc_sig)
148            .unwrap();
149        
150        assert_eq!(doc_sig.signature_count(), 2);
151    }
152
153    #[test]
154    fn test_convenience_functions() {
155        let keypair = KeyPair::generate();
156        let data = b"Test document content";
157        
158        let doc_sig = sign_bytes(&keypair, data).unwrap();
159        assert_eq!(doc_sig.signature_count(), 1);
160    }
161}
162