1use crate::security::{Ed25519Signer, KeyPair};
6use crate::{DrivenError, Result};
7use console::style;
8use std::path::Path;
9
10#[derive(Debug)]
12pub struct SignCommand;
13
14impl SignCommand {
15 pub fn execute(path: &Path, _key_path: Option<&Path>) -> Result<()> {
17 println!("{} Signing rules in {}", style("🔐").bold(), path.display());
18
19 println!(" Generating Ed25519 keypair...");
21 let key_pair = KeyPair::generate()?;
22 let signer = Ed25519Signer::with_key_pair(key_pair.clone());
23
24 let public_key = key_pair.public_key();
26 println!(" Public key: {:02x?}...", &public_key.0[..8]);
27
28 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 fn sign_file(signer: &Ed25519Signer, path: &Path) -> Result<()> {
92 let data = std::fs::read(path)?;
93 let signature = signer.sign(&data)?;
94
95 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 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
183pub 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}