Skip to main content

zoi_lua/api/
crypto.rs

1use mlua::{self, Lua, Value};
2use std::path::{Path, PathBuf};
3use zoi_core::utils;
4
5use colored::Colorize;
6use sequoia_openpgp::{Cert, parse::Parse};
7use std::fs;
8pub fn add_verify_hash(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
9    let verify_hash_fn = lua.create_function(move |lua, args: mlua::MultiValue| {
10        let mut args_iter = args.into_iter();
11        let file_path = match args_iter.next().unwrap_or(Value::Nil) {
12            Value::String(s) => s.to_str()?.to_string(),
13            v => {
14                return Err(mlua::Error::RuntimeError(format!(
15                    "verifyHash: first argument must be a string, got {}",
16                    v.type_name()
17                )));
18            }
19        };
20        let hash_str = match args_iter.next().unwrap_or(Value::Nil) {
21            Value::String(s) => s.to_str()?.to_string(),
22            v => {
23                return Err(mlua::Error::RuntimeError(format!(
24                    "verifyHash: second argument must be a string, got {}",
25                    v.type_name()
26                )));
27            }
28        };
29
30        let parts: Vec<&str> = hash_str.splitn(2, '-').collect();
31        if parts.len() != 2 {
32            return Err(mlua::Error::RuntimeError(
33                "Invalid hash format. Expected 'algo-hash'".to_string(),
34            ));
35        }
36        let algo = parts[0];
37        let expected_hash = parts[1];
38
39        let p = Path::new(&file_path);
40        let actual_path = if p.exists() {
41            p.to_path_buf()
42        } else if let Ok(build_dir) = lua.globals().get::<String>("BUILD_DIR") {
43            Path::new(&build_dir).join(p)
44        } else {
45            p.to_path_buf()
46        };
47
48        let hash_algo = match zoi_core::hash::HashAlgorithm::from_name(algo) {
49            Some(a) => a,
50            None => {
51                return Err(mlua::Error::RuntimeError(format!(
52                    "Unsupported hash algorithm: {}",
53                    algo
54                )));
55            }
56        };
57
58        let actual_hash = match zoi_core::hash::calculate_file_hash(&actual_path, hash_algo) {
59            Ok(h) => h,
60            Err(e) => {
61                return Err(mlua::Error::RuntimeError(format!(
62                    "Failed to calculate hash: {}",
63                    e
64                )));
65            }
66        };
67
68        if actual_hash.eq_ignore_ascii_case(expected_hash) {
69            Ok(true)
70        } else {
71            if !quiet {
72                println!(
73                    "\n{}: Hash mismatch for {}",
74                    "Error".red().bold(),
75                    file_path.cyan()
76                );
77                println!("  specified: {}-{}", algo, expected_hash.yellow());
78                println!("       got:    {}-{}", algo, actual_hash.green());
79            }
80            Ok(false)
81        }
82    })?;
83    lua.globals().set("verifyHash", verify_hash_fn)?;
84    Ok(())
85}
86
87pub fn add_verify_signature(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
88    let verify_sig_fn = lua.create_function(move |lua, args: mlua::MultiValue| {
89        let mut args_iter = args.into_iter();
90        let file_path = match args_iter.next().unwrap_or(Value::Nil) {
91            Value::String(s) => s.to_str()?.to_string(),
92            v => {
93                return Err(mlua::Error::RuntimeError(format!(
94                    "verifySignature: first argument must be a string, got {}",
95                    v.type_name()
96                )));
97            }
98        };
99        let sig_path = match args_iter.next().unwrap_or(Value::Nil) {
100            Value::String(s) => s.to_str()?.to_string(),
101            v => {
102                return Err(mlua::Error::RuntimeError(format!(
103                    "verifySignature: second argument must be a string, got {}",
104                    v.type_name()
105                )));
106            }
107        };
108        let key_source = match args_iter.next().unwrap_or(Value::Nil) {
109            Value::String(s) => s.to_str()?.to_string(),
110            v => {
111                return Err(mlua::Error::RuntimeError(format!(
112                    "verifySignature: third argument must be a string, got {}",
113                    v.type_name()
114                )));
115            }
116        };
117
118        let resolve_path = |p_str: &str| -> PathBuf {
119            let p = Path::new(p_str);
120            if p.exists() {
121                p.to_path_buf()
122            } else if let Ok(build_dir) = lua.globals().get::<String>("BUILD_DIR") {
123                Path::new(&build_dir).join(p)
124            } else {
125                p.to_path_buf()
126            }
127        };
128
129        let key_bytes: Vec<u8> = if key_source.starts_with("http") {
130            let client =
131                utils::get_http_client().map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
132            match client.get(&key_source).send().and_then(|r| r.bytes()) {
133                Ok(b) => b.to_vec(),
134                Err(e) => {
135                    return Err(mlua::Error::RuntimeError(format!(
136                        "Failed to download key: {}",
137                        e
138                    )));
139                }
140            }
141        } else {
142            let resolved_key_path = resolve_path(&key_source);
143            if resolved_key_path.exists() {
144                match fs::read(&resolved_key_path) {
145                    Ok(b) => b,
146                    Err(e) => {
147                        return Err(mlua::Error::RuntimeError(format!(
148                            "Failed to read key file {:?}: {}",
149                            resolved_key_path, e
150                        )));
151                    }
152                }
153            } else {
154                let pgp_dir = match zoi_core::pgp::get_pgp_dir() {
155                    Ok(dir) => dir,
156                    Err(e) => {
157                        return Err(mlua::Error::RuntimeError(format!(
158                            "Failed to get PGP dir: {}",
159                            e
160                        )));
161                    }
162                };
163                let key_path = pgp_dir.join(format!("{}.asc", key_source));
164                if !key_path.exists() {
165                    return Err(mlua::Error::RuntimeError(format!(
166                        "Key with name '{}' not found (checked locally and at {:?}).",
167                        key_source, resolved_key_path
168                    )));
169                }
170                match fs::read(&key_path) {
171                    Ok(b) => b,
172                    Err(e) => {
173                        return Err(mlua::Error::RuntimeError(format!(
174                            "Failed to read key file {:?}: {}",
175                            key_path, e
176                        )));
177                    }
178                }
179            }
180        };
181
182        let cert = match Cert::from_bytes(&key_bytes) {
183            Ok(c) => c,
184            Err(e) => return Err(mlua::Error::RuntimeError(format!("Invalid PGP key: {}", e))),
185        };
186
187        let final_file_path = resolve_path(&file_path);
188        let final_sig_path = resolve_path(&sig_path);
189
190        let result =
191            zoi_core::pgp::verify_detached_signature(&final_file_path, &final_sig_path, &cert);
192
193        match result {
194            Ok(_) => Ok(true),
195            Err(e) => {
196                if !quiet {
197                    eprintln!("Signature verification failed: {}", e);
198                }
199                Ok(false)
200            }
201        }
202    })?;
203    lua.globals().set("verifySignature", verify_sig_fn)?;
204    Ok(())
205}
206
207pub fn add_add_pgp_key(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
208    let add_pgp_key_fn = lua.create_function(move |lua, args: mlua::MultiValue| {
209        let mut args_iter = args.into_iter();
210        let source = match args_iter.next().unwrap_or(Value::Nil) {
211            Value::String(s) => s.to_str()?.to_string(),
212            v => {
213                return Err(mlua::Error::RuntimeError(format!(
214                    "addPgpKey: first argument must be a string, got {}",
215                    v.type_name()
216                )));
217            }
218        };
219        let name = match args_iter.next().unwrap_or(Value::Nil) {
220            Value::String(s) => s.to_str()?.to_string(),
221            v => {
222                return Err(mlua::Error::RuntimeError(format!(
223                    "addPgpKey: second argument must be a string, got {}",
224                    v.type_name()
225                )));
226            }
227        };
228
229        let result = if source.starts_with("http") {
230            zoi_core::pgp::add_key_from_url(&source, &name, quiet)
231        } else {
232            let p = Path::new(&source);
233            let actual_path = if p.exists() {
234                p.to_path_buf()
235            } else if let Ok(build_dir) = lua.globals().get::<String>("BUILD_DIR") {
236                Path::new(&build_dir).join(p)
237            } else {
238                p.to_path_buf()
239            };
240            zoi_core::pgp::add_key_from_path(
241                actual_path.to_str().unwrap_or(&source),
242                Some(&name),
243                quiet,
244            )
245        };
246
247        if let Err(e) = result {
248            if !quiet {
249                eprintln!("Failed to add PGP key '{}': {}", name, e);
250            }
251            return Ok(false);
252        }
253        Ok(true)
254    })?;
255    lua.globals().set("addPgpKey", add_pgp_key_fn)?;
256    Ok(())
257}