1use std::fs;
6use std::io::{self, Read, Write};
7use std::path::Path;
8use std::process;
9
10use crate::args::Args;
11use crate::format::{base32_decode, base32_encode, prettyhexrep};
12use rns_core::destination::destination_hash;
13use rns_crypto::identity::Identity;
14use rns_crypto::OsRng;
15
16const LARGE_FILE_WARN: u64 = 16 * 1024 * 1024; pub fn run(args: Args) {
19 if args.has("version") {
20 println!("rns-ctl {}", env!("FULL_VERSION"));
21 return;
22 }
23
24 if args.has("help") {
25 print_usage();
26 return;
27 }
28
29 if let Some(file) = args.get("g") {
31 generate_identity(file, &args);
32 return;
33 }
34
35 if let Some(hex_str) = args.get("m") {
37 import_from_hex(hex_str, &args);
38 return;
39 }
40
41 if let Some(file_or_hash) = args.get("i") {
43 let path = Path::new(file_or_hash);
44 if path.exists() {
45 inspect_identity_file(path, &args);
46 } else {
47 println!("Hash: {}", file_or_hash);
49 }
50 return;
51 }
52
53 print_usage();
54}
55
56fn generate_identity(file: &str, args: &Args) {
57 let path = Path::new(file);
58 let force = args.has("f") || args.has("force");
59
60 if path.exists() && !force {
61 eprintln!("File already exists: {} (use -f to overwrite)", file);
62 process::exit(1);
63 }
64
65 let identity = Identity::new(&mut OsRng);
66 let Some(prv_key) = identity.get_private_key() else {
67 eprintln!("Generated identity is missing a private key");
68 process::exit(1);
69 };
70
71 fs::write(path, &prv_key).unwrap_or_else(|e| {
72 eprintln!("Error writing identity: {}", e);
73 process::exit(1);
74 });
75
76 println!("Generated new identity");
77 println!(" Hash : {}", prettyhexrep(identity.hash()));
78 println!(" Saved: {}", file);
79
80 if args.has("B") {
82 println!(" Base32: {}", base32_encode(&prv_key));
83 }
84}
85
86fn inspect_identity_file(path: &Path, args: &Args) {
87 let data = fs::read(path).unwrap_or_else(|e| {
88 eprintln!("Error reading file: {}", e);
89 process::exit(1);
90 });
91
92 let identity = if data.len() == 64 {
93 let mut key = [0u8; 64];
95 key.copy_from_slice(&data);
96 Identity::from_private_key(&key)
97 } else if data.len() == 64 + 64 {
98 let mut key = [0u8; 64];
99 key.copy_from_slice(&data[..64]);
100 Identity::from_private_key(&key)
101 } else if data.len() == 32 + 32 {
102 let mut key = [0u8; 64];
104 key.copy_from_slice(&data);
105 Identity::from_public_key(&key)
106 } else {
107 eprintln!("Unknown identity file format ({} bytes)", data.len());
108 process::exit(1);
109 };
110
111 println!("Identity <{}>", prettyhexrep(identity.hash()));
112 println!(" Hash : {}", prettyhexrep(identity.hash()));
113
114 let show_private = args.has("P");
115 let show_public = args.has("p") || show_private;
116
117 if show_public {
118 if let Some(pub_key) = identity.get_public_key() {
119 println!(" Public key: {}", prettyhexrep(&pub_key));
120 }
121 }
122
123 if show_private {
124 if let Some(prv_key) = identity.get_private_key() {
125 println!(" Private key: {}", prettyhexrep(&prv_key));
126 } else {
127 println!(" Private key: (not available)");
128 }
129 }
130
131 if let Some(aspects_str) = args.get("H") {
133 let parts: Vec<&str> = aspects_str.split('.').collect();
134 if parts.len() >= 2 {
135 let app_name = parts[0];
136 let aspects: Vec<&str> = parts[1..].to_vec();
137 let dest_hash = destination_hash(app_name, &aspects, Some(identity.hash()));
138 println!(" Dest hash : {}", prettyhexrep(&dest_hash));
139 } else {
140 eprintln!(" Aspects must be in format: app_name.aspect1.aspect2");
141 }
142 }
143
144 let force = args.has("f") || args.has("force");
145 let use_stdin = args.has("stdin");
146 let use_stdout = args.has("stdout");
147
148 if let Some(file) = args.get("e") {
150 let plaintext = if use_stdin {
151 read_stdin()
152 } else {
153 check_file_size(file);
154 fs::read(file).unwrap_or_else(|e| {
155 eprintln!("Error reading file: {}", e);
156 process::exit(1);
157 })
158 };
159 let ciphertext = identity
160 .encrypt(&plaintext, &mut OsRng)
161 .unwrap_or_else(|e| {
162 eprintln!("Encryption failed: {:?}", e);
163 process::exit(1);
164 });
165 if use_stdout {
166 io::stdout().write_all(&ciphertext).unwrap_or_else(|e| {
167 eprintln!("Error writing to stdout: {}", e);
168 process::exit(1);
169 });
170 } else {
171 let out_file = format!("{}.enc", file);
172 write_file_checked(&out_file, &ciphertext, force);
173 println!(" Encrypted {} -> {}", file, out_file);
174 }
175 }
176
177 if let Some(file) = args.get("d") {
179 let ciphertext = if use_stdin {
180 read_stdin()
181 } else {
182 fs::read(file).unwrap_or_else(|e| {
183 eprintln!("Error reading file: {}", e);
184 process::exit(1);
185 })
186 };
187 match identity.decrypt(&ciphertext) {
188 Ok(plaintext) => {
189 if use_stdout {
190 io::stdout().write_all(&plaintext).unwrap_or_else(|e| {
191 eprintln!("Error writing to stdout: {}", e);
192 process::exit(1);
193 });
194 } else {
195 let out_file = if file.ends_with(".enc") {
196 file[..file.len() - 4].to_string()
197 } else {
198 format!("{}.dec", file)
199 };
200 write_file_checked(&out_file, &plaintext, force);
201 println!(" Decrypted {} -> {}", file, out_file);
202 }
203 }
204 Err(e) => {
205 eprintln!(" Decryption failed: {:?}", e);
206 process::exit(1);
207 }
208 }
209 }
210
211 if let Some(file) = args.get("s") {
213 let data = if use_stdin {
214 read_stdin()
215 } else {
216 fs::read(file).unwrap_or_else(|e| {
217 eprintln!("Error reading file: {}", e);
218 process::exit(1);
219 })
220 };
221 match identity.sign(&data) {
222 Ok(sig) => {
223 if use_stdout {
224 io::stdout().write_all(&sig).unwrap_or_else(|e| {
225 eprintln!("Error writing to stdout: {}", e);
226 process::exit(1);
227 });
228 } else {
229 let out_file = format!("{}.sig", file);
230 write_file_checked(&out_file, &sig, force);
231 println!(" Signed {} -> {}", file, out_file);
232 }
233 }
234 Err(e) => {
235 eprintln!(" Signing failed: {:?}", e);
236 process::exit(1);
237 }
238 }
239 }
240
241 if let Some(sig_file) = args.get("V") {
243 let sig_data = fs::read(sig_file).unwrap_or_else(|e| {
244 eprintln!("Error reading signature: {}", e);
245 process::exit(1);
246 });
247 if sig_data.len() != 64 {
248 eprintln!(
249 " Invalid signature (expected 64 bytes, got {})",
250 sig_data.len()
251 );
252 process::exit(1);
253 }
254 let mut sig = [0u8; 64];
255 sig.copy_from_slice(&sig_data);
256
257 let data_file = if sig_file.ends_with(".sig") {
259 &sig_file[..sig_file.len() - 4]
260 } else {
261 eprintln!(" Cannot determine data file (expected .sig extension)");
262 process::exit(1);
263 };
264
265 let data = fs::read(data_file).unwrap_or_else(|e| {
266 eprintln!("Error reading {}: {}", data_file, e);
267 process::exit(1);
268 });
269
270 if identity.verify(&sig, &data) {
271 println!(" Signature valid");
272 } else {
273 println!(" Signature INVALID");
274 process::exit(1);
275 }
276 }
277
278 if args.has("x") {
280 if let Some(prv_key) = identity.get_private_key() {
281 println!("{}", prettyhexrep(&prv_key));
282 } else if let Some(pub_key) = identity.get_public_key() {
283 println!("{}", prettyhexrep(&pub_key));
284 }
285 }
286
287 if args.has("b") {
289 if let Some(prv_key) = identity.get_private_key() {
290 println!("{}", base64_encode(&prv_key));
291 } else if let Some(pub_key) = identity.get_public_key() {
292 println!("{}", base64_encode(&pub_key));
293 }
294 }
295
296 if args.has("B") {
298 if let Some(prv_key) = identity.get_private_key() {
299 println!("{}", base32_encode(&prv_key));
300 } else if let Some(pub_key) = identity.get_public_key() {
301 println!("{}", base32_encode(&pub_key));
302 }
303 }
304}
305
306fn import_from_hex(hex_str: &str, args: &Args) {
307 let bytes = if args.has("B") {
309 match base32_decode(hex_str) {
310 Some(b) => b,
311 None => {
312 eprintln!("Invalid base32 string");
313 process::exit(1);
314 }
315 }
316 } else {
317 match parse_hex(hex_str) {
318 Some(b) => b,
319 None => {
320 eprintln!("Invalid hex string");
321 process::exit(1);
322 }
323 }
324 };
325
326 if bytes.len() == 64 {
327 let mut key = [0u8; 64];
328 key.copy_from_slice(&bytes);
329 let identity = Identity::from_private_key(&key);
330 println!("Identity <{}>", prettyhexrep(identity.hash()));
331
332 if let Some(file) = args.get("w") {
334 let force = args.has("f") || args.has("force");
335 write_file_checked(file, &key, force);
336 println!(" Saved to {}", file);
337 }
338 } else {
339 eprintln!(
340 "Expected 64 bytes (128 hex chars or base32), got {} bytes",
341 bytes.len()
342 );
343 process::exit(1);
344 }
345}
346
347fn parse_hex(s: &str) -> Option<Vec<u8>> {
348 let s = s.trim();
349 if s.len() % 2 != 0 {
350 return None;
351 }
352 let mut bytes = Vec::with_capacity(s.len() / 2);
353 for i in (0..s.len()).step_by(2) {
354 match u8::from_str_radix(&s[i..i + 2], 16) {
355 Ok(b) => bytes.push(b),
356 Err(_) => return None,
357 }
358 }
359 Some(bytes)
360}
361
362fn base64_encode(data: &[u8]) -> String {
363 const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
364 let mut result = String::new();
365 let mut i = 0;
366 while i < data.len() {
367 let b0 = data[i] as u32;
368 let b1 = if i + 1 < data.len() {
369 data[i + 1] as u32
370 } else {
371 0
372 };
373 let b2 = if i + 2 < data.len() {
374 data[i + 2] as u32
375 } else {
376 0
377 };
378
379 let triple = (b0 << 16) | (b1 << 8) | b2;
380
381 result.push(CHARS[((triple >> 18) & 0x3F) as usize] as char);
382 result.push(CHARS[((triple >> 12) & 0x3F) as usize] as char);
383 if i + 1 < data.len() {
384 result.push(CHARS[((triple >> 6) & 0x3F) as usize] as char);
385 } else {
386 result.push('=');
387 }
388 if i + 2 < data.len() {
389 result.push(CHARS[(triple & 0x3F) as usize] as char);
390 } else {
391 result.push('=');
392 }
393
394 i += 3;
395 }
396 result
397}
398
399fn read_stdin() -> Vec<u8> {
400 let mut buf = Vec::new();
401 io::stdin().read_to_end(&mut buf).unwrap_or_else(|e| {
402 eprintln!("Error reading stdin: {}", e);
403 process::exit(1);
404 });
405 buf
406}
407
408fn check_file_size(file: &str) {
409 if let Ok(meta) = fs::metadata(file) {
410 if meta.len() > LARGE_FILE_WARN {
411 eprintln!(
412 "Warning: file is {} — encryption is done in-memory",
413 crate::format::size_str(meta.len()),
414 );
415 }
416 }
417}
418
419fn write_file_checked(path: &str, data: &[u8], force: bool) {
420 let p = Path::new(path);
421 if p.exists() && !force {
422 eprintln!("File already exists: {} (use -f to overwrite)", path);
423 process::exit(1);
424 }
425 fs::write(p, data).unwrap_or_else(|e| {
426 eprintln!("Error writing: {}", e);
427 process::exit(1);
428 });
429}
430
431fn print_usage() {
432 println!("Usage: rns-ctl id [OPTIONS]");
433 println!();
434 println!("Options:");
435 println!(" -g FILE Generate new identity and save to file");
436 println!(" -i FILE Load and inspect identity from file");
437 println!(" -p Print public key");
438 println!(" -P Print private key (implies -p)");
439 println!(" -H APP.ASPECT Compute destination hash");
440 println!(" -e FILE Encrypt file with identity");
441 println!(" -d FILE Decrypt file with identity");
442 println!(" -s FILE Sign file with identity");
443 println!(" -V FILE.sig Verify signature");
444 println!(" -m HEX Import identity from hex string");
445 println!(" -w FILE Write imported identity to file");
446 println!(" -x Export as hex");
447 println!(" -b Export as base64");
448 println!(" -B Export/import as base32");
449 println!(" -f, --force Force overwrite existing files");
450 println!(" --stdin Read input from stdin");
451 println!(" --stdout Write output to stdout");
452 println!(" --version Print version and exit");
453 println!(" --help, -h Print this help");
454}