1use std::{borrow::Cow, env, fs, fs::File, io, io::IsTerminal, path::PathBuf, process::exit};
5
6use rand_core::CryptoRngCore;
7
8use reach_aliases::*;
9use reach_core::{
10 RandomFromRng,
11 storage::{ReachablePublicKeyRing, Storable, UnsignedReachableVerifyingKeys},
12 wire::{AttestantVerifyingKeys, ReachableVerifyingKeys, Salts},
13};
14use reach_passphrase::{WORDLIST_EN_V0, generate_passphrase};
15use reach_signatures::{Sign, Verifiable};
16use reach_visual_key_identity::visual_key_identity;
17
18use crate::{
19 cli_utils::*,
20 macros::{shared_keys_random_from_rng, sign_and_store_shared_keys},
21 memory::{
22 self, AttestantSigningKeys, UnsignedSalts, UnsignedSharedPublicKeys,
23 UnsignedSharedSecretKeys, UnsignedSharedSigningKeys, UnsignedSharedVerifyingKeys,
24 },
25 net::authenticate,
26 storage,
27};
28
29pub fn generate(
30 profile_name: &str,
31 profile: &Option<String>,
32 force: bool,
33 csprng: &mut impl CryptoRngCore,
34) {
35 if let (false, Ok(locked_signing_key_path)) =
36 (force, storage::file_path(profile_name, None, "lask", false))
37 {
38 (!locked_signing_key_path.exists()).may_bail_with(
39 &format!(
40 "A locked signing key for '{profile_name}' already exists! {}",
41 "To overwrite it use --force"
42 ),
43 false,
44 );
45 }
46
47 let pac_salt = ThreeTwo::random_from_rng(csprng);
48 let key_salt = ThreeTwo::random_from_rng(csprng);
49
50 let passphrase = generate_passphrase(WORDLIST_EN_V0, &pac_salt, csprng);
52 print!("\u{1f537} Your generated passphrase");
53 if let Some(profile_name) = profile {
54 print!(" for profile '{profile_name}'");
55 }
56 println!(":\n");
57 print_passphrase(&passphrase);
58
59 let signing_keys = AttestantSigningKeys::random_from_rng(csprng);
61 let verifying_keys = signing_keys.verifying_keys();
62
63 println!("\u{2712}\u{fe0f} Successfully generated attestant signing keys");
64
65 let key = derive_unlocking_key(passphrase, &pac_salt, &key_salt, true);
66 let augmentation = [pac_salt, key_salt].concat();
67 memory::lock_and_store(&signing_keys, &key, augmentation, profile_name, csprng)
69 .may_bail_with("Locking and storing your signing keys has failed.", true);
70 println!("\u{1f512} Locked attestant signing keys with your generated passphrase.\n");
71
72 let verifying_keys_path = verifying_keys_path(profile_name, true);
73
74 verifying_keys
75 .store(&verifying_keys_path)
76 .may_bail_with("Couldn't store attestant verifying keys.", true);
77
78 let unsigned_salts = UnsignedSalts::random_from_rng(csprng);
79 let reach_salts = signing_keys.sign(unsigned_salts, csprng);
80
81 let reach_salts_path = storage::file_path(profile_name, None, "slt", true)
82 .may_bail_with("Couldn't determine path for salts.", true);
83
84 reach_salts
85 .store(&reach_salts_path)
86 .may_bail_with("Couldn't store salts.", true);
87
88 let (shared_signing_keys, shared_verifying_keys) = shared_keys_random_from_rng!(
89 signing_keys,
90 UnsignedSharedSigningKeys,
91 UnsignedSharedVerifyingKeys,
92 csprng,
93 );
94 sign_and_store_shared_keys!(
95 (shared_signing_keys, "signing", "ssik"),
96 (shared_verifying_keys, "verifying", "svk"),
97 profile_name,
98 );
99
100 let (shared_secret_keys, shared_public_keys) = shared_keys_random_from_rng!(
101 signing_keys,
102 UnsignedSharedSecretKeys,
103 UnsignedSharedPublicKeys,
104 csprng,
105 );
106 sign_and_store_shared_keys!(
107 (shared_secret_keys, "secret", "ssek"),
108 (shared_public_keys, "public", "spk"),
109 profile_name,
110 );
111
112 let identity = visual_key_identity(&verifying_keys, &reach_salts.attestant_identity)
113 .may_bail_with(
114 "Couldn't synthesize visual identity for your verifying keys.",
115 true,
116 );
117
118 print!("\u{1f6c2} Your attestant verifying keys' visual identity:");
119 println!("\n\n{identity}\n");
120
121 print!("\u{2139}\u{fe0f} You can publish this visual identity on your website to ");
122 println!("allow users to verify that they are communicating with the right folks.");
123}
124
125pub fn list_profiles() {
126 let config_path = storage::config_path(false).may_bail(false);
127
128 let dir_entries = config_path.read_dir().may_bail(false);
129
130 dir_entries.for_each(|f| {
131 let dir_entry = f.may_bail(false);
132 let os_file_name = dir_entry.file_name();
133 let file_name = os_file_name.to_string_lossy();
134 if file_name.ends_with(".lask") {
135 println!("{}", file_name.split(".").next().expect("Infallible"));
136 };
137 });
138}
139
140pub fn list_signed_verifying_keys() {
141 todo!();
143}
144
145pub fn offboard() {
146 todo!();
148}
149
150pub async fn onboard(
152 profile_name: &str,
153 profile: &Option<String>,
154 reachable_verifying_keys_public_key_rings: Vec<(String, String)>,
155) {
156 let url_path = url_path(profile_name, false);
157 let url = format!(
158 "{}/reach",
159 fs::read_to_string(&url_path)
160 .map_err(|_| format!("Could not read {}", url_path.to_string_lossy()))
161 .may_bail(false)
162 .trim()
163 );
164
165 let signing_keys = load_and_unlock_signing_keys(profile_name, profile);
166 let verifying_keys_path = verifying_keys_path(profile_name, false);
167 let verifying_keys = AttestantVerifyingKeys::load(&verifying_keys_path).may_bail(false);
168
169 let reachable_verifying_keys_public_key_rings = reachable_verifying_keys_public_key_rings
170 .iter()
171 .map(|(rvk, rpkr)| {
172 let vfk_aliased =
173 storage::file_path(profile_name, Some(rvk), "vfk", false).may_bail(true);
174 let vfk_path = PathBuf::from(rvk);
175 let vfk = match (vfk_aliased.exists(), vfk_path.exists()) {
176 (_, true) => ReachableVerifyingKeys::load(&vfk_path).may_bail(true),
177 (true, false) => ReachableVerifyingKeys::load(&vfk_aliased).may_bail(true),
178 (false, false) => exit(1),
179 };
180
181 vfk.verify(&verifying_keys).may_bail_with(
182 concat!(
183 "Supplied reachable verifying keys are not verifiable with this profile's ",
184 "attestant verifying keys."
185 ),
186 false,
187 );
188
189 let pkr_path = PathBuf::from(rpkr);
190 let ring = ReachablePublicKeyRing::load(&pkr_path).may_bail_with(
191 &format!(
192 "Couldn't load reachable public key ring from '{}'.",
193 pkr_path.to_string_lossy()
194 ),
195 false,
196 );
197
198 ring.reachable_public_keys
199 .iter()
200 .all(|rpk| rpk.verify(&vfk))
201 .may_bail_with(
202 &format!(
203 concat!(
204 "Reachable public keys in '{}' are not signed with the verifying keys ",
205 "you supplied."
206 ),
207 pkr_path.to_string_lossy()
208 ),
209 false,
210 );
211
212 (vfk, ring)
213 })
214 .collect::<Vec<(_, _)>>();
215
216 let mut client = authenticate(&signing_keys, &verifying_keys, &url).await;
217
218 for (vfk, pkr) in &reachable_verifying_keys_public_key_rings {
219 client
220 .send_and_wait(vfk)
221 .await
222 .map_err(|_| "Onboarding reachable verifying keys failed")
223 .may_bail(true);
224 client
225 .send_and_wait(pkr)
226 .await
227 .map_err(|_| "Adding reachable public key ring failed.")
228 .may_bail(true);
229 }
230
231 println!(
232 "Successfully onboarded {} peers!",
233 reachable_verifying_keys_public_key_rings.len()
234 );
235}
236
237pub fn revoke(profile_name: &str, profile: &Option<String>) {
238 let _signing_keys = load_and_unlock_signing_keys(profile_name, profile);
239 todo!();
243}
244
245pub fn sign(
247 profile_name: &str,
248 profile: &Option<String>,
249 unsigned_verifying_keys: Vec<PathBuf>,
250 copy_to_cwd: bool,
251 csprng: &mut impl CryptoRngCore,
252) {
253 let signing_keys = load_and_unlock_signing_keys(profile_name, profile);
254
255 let data_path =
256 storage::data_path(true).may_bail_with("Data directory needs to be writeable.", false);
257
258 let aliases = unsigned_verifying_keys
259 .iter()
260 .fold(String::new(), |acc, p| {
261 let unsigned_file_name = p
262 .file_name()
263 .may_bail_with("Attempting to sign an invalid path.", false)
264 .to_string_lossy();
265 let alias = unsigned_file_name
266 .rsplit_once('.')
267 .map_or_else(|| unsigned_file_name.clone(), |(alias, _)| Cow::from(alias));
268
269 fs::copy(p, data_path.join(format!("{alias}.uvfk"))).may_bail_with(
270 "Could not copy unsigned verifying key to data directory.",
271 true,
272 );
273
274 let unsigned_reachable_verifying_keys = UnsignedReachableVerifyingKeys::load(p)
275 .may_bail_with(&format!("{p:?} is malformed, it can't be signed."), false);
276
277 let reachable_verifying_keys =
278 signing_keys.sign(unsigned_reachable_verifying_keys, csprng);
279
280 let signed_file_name = format!("{alias}.vfk");
281
282 reachable_verifying_keys
283 .store(&data_path.join(&signed_file_name))
284 .may_bail_with(
285 "Could not store signed reachable verifying keys in data directory.",
286 true,
287 );
288
289 if copy_to_cwd {
290 reachable_verifying_keys
291 .store(&env::current_dir().may_bail(true).join(&signed_file_name))
292 .may_bail_with(
293 "Could not store signed reachable verifying keys in the current directory.",
294 true,
295 );
296 }
297
298 format!("{acc}, {alias}")
299 });
300 let aliases = aliases.trim_start_matches(',').trim();
301
302 println!("\u{2712}\u{FE0F} Successfully signed reachable verifying keys for: {aliases}");
303}
304
305pub fn url(profile_name: &str, force: bool, url: Option<String>) {
306 let url_path = url_path(profile_name, url.is_some());
307 let url_exists = url_path.exists();
308
309 let suffix = match profile_name {
310 "default" => String::new(),
311 profile_name => format!(" for '{profile_name}'"),
312 };
313
314 match (url, !url_exists || force) {
315 (None, _) => {
316 url_exists.may_bail_with("No URL was set for profile.", false);
317
318 let url = fs::read_to_string(url_path).may_bail_with("Could not read URL", true);
319
320 println!("{}/", url.trim().trim_matches('/'));
321 }
322 (Some(url), true) => {
323 fs::write(url_path, url.trim_end_matches('/')).may_bail(true);
324
325 println!("\u{1f517} Successfully set URL{suffix}!");
326 }
327 (Some(_), false) => {
328 err_msg(
329 format!(
330 "URL already exists{suffix}. Rerun with --force if you want to replace it."
331 ),
332 false,
333 );
334 exit(1);
335 }
336 }
337}
338
339pub fn verifying_keys(profile_name: &str) {
341 let verifying_keys_path = verifying_keys_path(profile_name, false);
342
343 let stdout = io::stdout();
344 if stdout.is_terminal() {
345 println!(
346 "\u{1f6c2} Your verifying key is located at:\n\n{}",
347 verifying_keys_path.to_string_lossy()
348 );
349 } else {
350 let mut verifying_keys = File::open(&verifying_keys_path).may_bail(false);
351 let mut lock = stdout.lock();
352 io::copy(&mut verifying_keys, &mut lock).may_bail(false);
353 }
354}
355
356pub fn visual_identity(profile_name: &str) {
357 let verifying_keys_path = storage::file_path(profile_name, None, "vfk", false).may_bail(false);
358
359 let verifying_keys = AttestantVerifyingKeys::load(&verifying_keys_path).may_bail_with(
360 "Verifying keys could not be loaded.\nHave you generated signing keys for this profile?",
361 false,
362 );
363
364 let reach_salts_path = storage::file_path(profile_name, None, "slt", true)
365 .may_bail_with("Couldn't determine path for salts.", true);
366
367 let reach_salts = Salts::load(&reach_salts_path).may_bail_with("", false);
368
369 let identity = visual_key_identity(&verifying_keys, &reach_salts.attestant_identity)
370 .may_bail_with(
371 "Couldn't create visual identity for your verifying keys.",
372 true,
373 );
374
375 println!("{identity}");
376}