Skip to main content

driven/cli/
sign.rs

1//! Sign Command
2//!
3//! Ed25519 signing for .drv binary rules.
4
5use crate::security::{Ed25519Signer, KeyPair};
6use crate::{DrivenError, Result};
7use console::style;
8use std::path::Path;
9
10/// Sign command for cryptographic rule signing
11#[derive(Debug)]
12pub struct SignCommand;
13
14impl SignCommand {
15    /// Execute sign command
16    pub fn execute(path: &Path, _key_path: Option<&Path>) -> Result<()> {
17        println!("{} Signing rules in {}", style("🔐").bold(), path.display());
18
19        // Generate new keypair
20        println!("  Generating Ed25519 keypair...");
21        let key_pair = KeyPair::generate()?;
22        let signer = Ed25519Signer::with_key_pair(key_pair.clone());
23
24        // Get public key for display
25        let public_key = key_pair.public_key();
26        println!("  Public key: {:02x?}...", &public_key.0[..8]);
27
28        // Find and sign all .drv files
29        let mut signed_count = 0;
30        let mut error_count = 0;
31
32        if path.is_file() {
33            match Self::sign_file(&signer, path) {
34                Ok(()) => signed_count += 1,
35                Err(e) => {
36                    eprintln!(
37                        "  {} Failed to sign {}: {}",
38                        style("✗").red(),
39                        path.display(),
40                        e
41                    );
42                    error_count += 1;
43                }
44            }
45        } else if path.is_dir() {
46            for entry in walkdir::WalkDir::new(path)
47                .follow_links(true)
48                .into_iter()
49                .filter_map(|e| e.ok())
50            {
51                let file_path = entry.path();
52                if file_path.extension().is_some_and(|ext| ext == "drv") {
53                    match Self::sign_file(&signer, file_path) {
54                        Ok(()) => signed_count += 1,
55                        Err(e) => {
56                            eprintln!(
57                                "  {} Failed to sign {}: {}",
58                                style("✗").red(),
59                                file_path.display(),
60                                e
61                            );
62                            error_count += 1;
63                        }
64                    }
65                }
66            }
67        }
68
69        println!();
70        if error_count > 0 {
71            println!(
72                "{} Signed {} files, {} errors",
73                style("⚠").yellow().bold(),
74                signed_count,
75                error_count
76            );
77        } else if signed_count > 0 {
78            println!(
79                "{} Successfully signed {} files",
80                style("✓").green().bold(),
81                signed_count
82            );
83        } else {
84            println!("{} No .drv files found to sign", style("ℹ").blue());
85        }
86
87        Ok(())
88    }
89
90    /// Sign a single file
91    fn sign_file(signer: &Ed25519Signer, path: &Path) -> Result<()> {
92        let data = std::fs::read(path)?;
93        let signature = signer.sign(&data)?;
94
95        // Write signature file (.drv.sig)
96        let sig_path = path.with_extension("drv.sig");
97        std::fs::write(&sig_path, signature.0)?;
98
99        println!("  {} {}", style("✓").green(), path.display());
100        Ok(())
101    }
102
103    /// Verify signed files
104    pub fn verify(path: &Path, public_key_path: &Path) -> Result<()> {
105        println!(
106            "{} Verifying signatures in {}",
107            style("🔍").bold(),
108            path.display()
109        );
110
111        let public_key_bytes = std::fs::read(public_key_path)?;
112        if public_key_bytes.len() != 32 {
113            return Err(DrivenError::Security("Invalid public key size".into()));
114        }
115
116        let mut pk_array = [0u8; 32];
117        pk_array.copy_from_slice(&public_key_bytes);
118        let public_key = crate::security::PublicKey(pk_array);
119
120        let signer = Ed25519Signer::new();
121        let mut verified = 0;
122        let mut failed = 0;
123
124        for entry in walkdir::WalkDir::new(path)
125            .follow_links(true)
126            .into_iter()
127            .filter_map(|e| e.ok())
128        {
129            let file_path = entry.path();
130            if file_path.extension().is_some_and(|ext| ext == "drv") {
131                let sig_path = file_path.with_extension("drv.sig");
132                if sig_path.exists() {
133                    let data = std::fs::read(file_path)?;
134                    let sig_bytes = std::fs::read(&sig_path)?;
135
136                    if sig_bytes.len() == 64 {
137                        let mut sig_array = [0u8; 64];
138                        sig_array.copy_from_slice(&sig_bytes);
139                        let signature = crate::security::Signature(sig_array);
140
141                        match signer.verify_with_key(&data, &signature, &public_key) {
142                            Ok(true) => {
143                                println!("  {} {}", style("✓").green(), file_path.display());
144                                verified += 1;
145                            }
146                            _ => {
147                                println!(
148                                    "  {} {} (INVALID)",
149                                    style("✗").red(),
150                                    file_path.display()
151                                );
152                                failed += 1;
153                            }
154                        }
155                    }
156                }
157            }
158        }
159
160        println!();
161        if failed > 0 {
162            println!(
163                "{} Verified: {}, Failed: {}",
164                style("⚠").yellow().bold(),
165                verified,
166                failed
167            );
168            Err(DrivenError::Security(format!(
169                "{} signatures failed verification",
170                failed
171            )))
172        } else {
173            println!(
174                "{} All {} signatures verified",
175                style("✓").green().bold(),
176                verified
177            );
178            Ok(())
179        }
180    }
181}
182
183/// Generate a new keypair
184pub fn generate_keypair(output_dir: &Path) -> Result<()> {
185    println!("{} Generating Ed25519 keypair...", style("🔑").bold());
186
187    let key_pair = KeyPair::generate()?;
188    let public = key_pair.public_key();
189
190    let public_path = output_dir.join("driven.pub");
191
192    std::fs::create_dir_all(output_dir)?;
193    std::fs::write(&public_path, public.0)?;
194
195    println!("  Public key: {}", public_path.display());
196    println!();
197    println!("{} Keypair generated!", style("✓").green().bold());
198
199    Ok(())
200}