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