upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2023-09-01 00:00:00 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2023-09-01 00:00:00 +0000
commit96660a90e4cd296a2922d7a547de4cd9d0b1928b (patch)
treee5216e22ee1a3e1653d8d1ecd856f4f03615d6a1
parent6423baebd92e45c9be85157c443dff42e65d8d14 (diff)
feat(login) password login using encrypted nsec
Enables the user to only handle the nsec upon first use of the tool by encrypting it with a password and storing it on disk in an application cache. The approach to encryption draws heavily from that used by the gossip nostr client. - unencrypted nsec is zeroed from memory - a salt is used to defend against rainbow tables - computationally expensive key stretching defends against brute-force attacks of passwords with low entropy. There is UX trade-off between decryption speed and key-stretching computation. This UX challenge is exacerbated in a cli tool as decryption must take place more regularly. Thought was put into the selected n_log and a heavily reduced value is provided for long passwords where security benefits are smaller. A more granular reducing in computation was also considered by rejected to avoided to revealing just how weak a password is as most weak passwords are reused.
-rw-r--r--Cargo.lock1896
-rw-r--r--Cargo.toml8
-rw-r--r--flake.nix18
-rw-r--r--planning.md78
-rw-r--r--src/cli_interactor.rs31
-rw-r--r--src/config.rs111
-rw-r--r--src/key_handling/encryption.rs247
-rw-r--r--src/key_handling/mod.rs1
-rw-r--r--src/key_handling/users.rs262
-rw-r--r--src/login.rs83
-rw-r--r--src/main.rs5
-rw-r--r--src/sub_commands/login.rs3
-rw-r--r--test_utils/Cargo.toml2
-rw-r--r--test_utils/src/lib.rs116
-rw-r--r--tests/login.rs334
15 files changed, 3046 insertions, 149 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3471bc3..994445e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,6 +3,54 @@
3version = 3 3version = 3
4 4
5[[package]] 5[[package]]
6name = "addr2line"
7version = "0.21.0"
8source = "registry+https://github.com/rust-lang/crates.io-index"
9checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
10dependencies = [
11 "gimli",
12]
13
14[[package]]
15name = "adler"
16version = "1.0.2"
17source = "registry+https://github.com/rust-lang/crates.io-index"
18checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
19
20[[package]]
21name = "aead"
22version = "0.5.2"
23source = "registry+https://github.com/rust-lang/crates.io-index"
24checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
25dependencies = [
26 "crypto-common",
27 "generic-array",
28]
29
30[[package]]
31name = "aes"
32version = "0.7.5"
33source = "registry+https://github.com/rust-lang/crates.io-index"
34checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
35dependencies = [
36 "cfg-if",
37 "cipher 0.3.0",
38 "cpufeatures",
39 "opaque-debug",
40]
41
42[[package]]
43name = "aes"
44version = "0.8.3"
45source = "registry+https://github.com/rust-lang/crates.io-index"
46checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
47dependencies = [
48 "cfg-if",
49 "cipher 0.4.4",
50 "cpufeatures",
51]
52
53[[package]]
6name = "aho-corasick" 54name = "aho-corasick"
7version = "1.0.5" 55version = "1.0.5"
8source = "registry+https://github.com/rust-lang/crates.io-index" 56source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -81,12 +129,219 @@ dependencies = [
81] 129]
82 130
83[[package]] 131[[package]]
132name = "async-broadcast"
133version = "0.5.1"
134source = "registry+https://github.com/rust-lang/crates.io-index"
135checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b"
136dependencies = [
137 "event-listener",
138 "futures-core",
139]
140
141[[package]]
142name = "async-channel"
143version = "1.9.0"
144source = "registry+https://github.com/rust-lang/crates.io-index"
145checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
146dependencies = [
147 "concurrent-queue",
148 "event-listener",
149 "futures-core",
150]
151
152[[package]]
153name = "async-executor"
154version = "1.5.1"
155source = "registry+https://github.com/rust-lang/crates.io-index"
156checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb"
157dependencies = [
158 "async-lock",
159 "async-task",
160 "concurrent-queue",
161 "fastrand 1.9.0",
162 "futures-lite",
163 "slab",
164]
165
166[[package]]
167name = "async-fs"
168version = "1.6.0"
169source = "registry+https://github.com/rust-lang/crates.io-index"
170checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
171dependencies = [
172 "async-lock",
173 "autocfg",
174 "blocking",
175 "futures-lite",
176]
177
178[[package]]
179name = "async-io"
180version = "1.13.0"
181source = "registry+https://github.com/rust-lang/crates.io-index"
182checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
183dependencies = [
184 "async-lock",
185 "autocfg",
186 "cfg-if",
187 "concurrent-queue",
188 "futures-lite",
189 "log",
190 "parking",
191 "polling",
192 "rustix 0.37.23",
193 "slab",
194 "socket2 0.4.9",
195 "waker-fn",
196]
197
198[[package]]
199name = "async-lock"
200version = "2.8.0"
201source = "registry+https://github.com/rust-lang/crates.io-index"
202checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
203dependencies = [
204 "event-listener",
205]
206
207[[package]]
208name = "async-process"
209version = "1.7.0"
210source = "registry+https://github.com/rust-lang/crates.io-index"
211checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9"
212dependencies = [
213 "async-io",
214 "async-lock",
215 "autocfg",
216 "blocking",
217 "cfg-if",
218 "event-listener",
219 "futures-lite",
220 "rustix 0.37.23",
221 "signal-hook",
222 "windows-sys 0.48.0",
223]
224
225[[package]]
226name = "async-recursion"
227version = "1.0.4"
228source = "registry+https://github.com/rust-lang/crates.io-index"
229checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba"
230dependencies = [
231 "proc-macro2",
232 "quote",
233 "syn 2.0.32",
234]
235
236[[package]]
237name = "async-task"
238version = "4.4.0"
239source = "registry+https://github.com/rust-lang/crates.io-index"
240checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae"
241
242[[package]]
243name = "async-trait"
244version = "0.1.73"
245source = "registry+https://github.com/rust-lang/crates.io-index"
246checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
247dependencies = [
248 "proc-macro2",
249 "quote",
250 "syn 2.0.32",
251]
252
253[[package]]
254name = "atomic-waker"
255version = "1.1.1"
256source = "registry+https://github.com/rust-lang/crates.io-index"
257checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3"
258
259[[package]]
84name = "autocfg" 260name = "autocfg"
85version = "1.1.0" 261version = "1.1.0"
86source = "registry+https://github.com/rust-lang/crates.io-index" 262source = "registry+https://github.com/rust-lang/crates.io-index"
87checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 263checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
88 264
89[[package]] 265[[package]]
266name = "backtrace"
267version = "0.3.69"
268source = "registry+https://github.com/rust-lang/crates.io-index"
269checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
270dependencies = [
271 "addr2line",
272 "cc",
273 "cfg-if",
274 "libc",
275 "miniz_oxide",
276 "object",
277 "rustc-demangle",
278]
279
280[[package]]
281name = "base64"
282version = "0.21.3"
283source = "registry+https://github.com/rust-lang/crates.io-index"
284checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53"
285
286[[package]]
287name = "base64ct"
288version = "1.6.0"
289source = "registry+https://github.com/rust-lang/crates.io-index"
290checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
291
292[[package]]
293name = "bech32"
294version = "0.9.1"
295source = "registry+https://github.com/rust-lang/crates.io-index"
296checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445"
297
298[[package]]
299name = "bip39"
300version = "2.0.0"
301source = "registry+https://github.com/rust-lang/crates.io-index"
302checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f"
303dependencies = [
304 "bitcoin_hashes 0.11.0",
305 "serde",
306 "unicode-normalization",
307]
308
309[[package]]
310name = "bitcoin"
311version = "0.30.1"
312source = "registry+https://github.com/rust-lang/crates.io-index"
313checksum = "4e99ff7289b20a7385f66a0feda78af2fc119d28fb56aea8886a9cd0a4abdd75"
314dependencies = [
315 "bech32",
316 "bitcoin-private",
317 "bitcoin_hashes 0.12.0",
318 "hex_lit",
319 "secp256k1",
320]
321
322[[package]]
323name = "bitcoin-private"
324version = "0.1.0"
325source = "registry+https://github.com/rust-lang/crates.io-index"
326checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57"
327
328[[package]]
329name = "bitcoin_hashes"
330version = "0.11.0"
331source = "registry+https://github.com/rust-lang/crates.io-index"
332checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4"
333
334[[package]]
335name = "bitcoin_hashes"
336version = "0.12.0"
337source = "registry+https://github.com/rust-lang/crates.io-index"
338checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501"
339dependencies = [
340 "bitcoin-private",
341 "serde",
342]
343
344[[package]]
90name = "bitflags" 345name = "bitflags"
91version = "1.3.2" 346version = "1.3.2"
92source = "registry+https://github.com/rust-lang/crates.io-index" 347source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -99,6 +354,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
99checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 354checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
100 355
101[[package]] 356[[package]]
357name = "block-buffer"
358version = "0.10.4"
359source = "registry+https://github.com/rust-lang/crates.io-index"
360checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
361dependencies = [
362 "generic-array",
363]
364
365[[package]]
366name = "block-modes"
367version = "0.8.1"
368source = "registry+https://github.com/rust-lang/crates.io-index"
369checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e"
370dependencies = [
371 "block-padding 0.2.1",
372 "cipher 0.3.0",
373]
374
375[[package]]
376name = "block-padding"
377version = "0.2.1"
378source = "registry+https://github.com/rust-lang/crates.io-index"
379checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
380
381[[package]]
382name = "block-padding"
383version = "0.3.3"
384source = "registry+https://github.com/rust-lang/crates.io-index"
385checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
386dependencies = [
387 "generic-array",
388]
389
390[[package]]
391name = "blocking"
392version = "1.3.1"
393source = "registry+https://github.com/rust-lang/crates.io-index"
394checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65"
395dependencies = [
396 "async-channel",
397 "async-lock",
398 "async-task",
399 "atomic-waker",
400 "fastrand 1.9.0",
401 "futures-lite",
402 "log",
403]
404
405[[package]]
102name = "bstr" 406name = "bstr"
103version = "1.6.2" 407version = "1.6.2"
104source = "registry+https://github.com/rust-lang/crates.io-index" 408source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -110,6 +414,33 @@ dependencies = [
110] 414]
111 415
112[[package]] 416[[package]]
417name = "bumpalo"
418version = "3.13.0"
419source = "registry+https://github.com/rust-lang/crates.io-index"
420checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
421
422[[package]]
423name = "byteorder"
424version = "1.4.3"
425source = "registry+https://github.com/rust-lang/crates.io-index"
426checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
427
428[[package]]
429name = "bytes"
430version = "1.4.0"
431source = "registry+https://github.com/rust-lang/crates.io-index"
432checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
433
434[[package]]
435name = "cbc"
436version = "0.1.2"
437source = "registry+https://github.com/rust-lang/crates.io-index"
438checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
439dependencies = [
440 "cipher 0.4.4",
441]
442
443[[package]]
113name = "cc" 444name = "cc"
114version = "1.0.83" 445version = "1.0.83"
115source = "registry+https://github.com/rust-lang/crates.io-index" 446source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -125,6 +456,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
125checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 456checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
126 457
127[[package]] 458[[package]]
459name = "chacha20"
460version = "0.9.1"
461source = "registry+https://github.com/rust-lang/crates.io-index"
462checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
463dependencies = [
464 "cfg-if",
465 "cipher 0.4.4",
466 "cpufeatures",
467]
468
469[[package]]
470name = "chacha20poly1305"
471version = "0.10.1"
472source = "registry+https://github.com/rust-lang/crates.io-index"
473checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
474dependencies = [
475 "aead",
476 "chacha20",
477 "cipher 0.4.4",
478 "poly1305",
479 "zeroize",
480]
481
482[[package]]
483name = "cipher"
484version = "0.3.0"
485source = "registry+https://github.com/rust-lang/crates.io-index"
486checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
487dependencies = [
488 "generic-array",
489]
490
491[[package]]
492name = "cipher"
493version = "0.4.4"
494source = "registry+https://github.com/rust-lang/crates.io-index"
495checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
496dependencies = [
497 "crypto-common",
498 "inout",
499 "zeroize",
500]
501
502[[package]]
128name = "clap" 503name = "clap"
129version = "4.4.2" 504version = "4.4.2"
130source = "registry+https://github.com/rust-lang/crates.io-index" 505source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -177,6 +552,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
177checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" 552checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335"
178 553
179[[package]] 554[[package]]
555name = "concurrent-queue"
556version = "2.2.0"
557source = "registry+https://github.com/rust-lang/crates.io-index"
558checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
559dependencies = [
560 "crossbeam-utils",
561]
562
563[[package]]
180name = "console" 564name = "console"
181version = "0.15.7" 565version = "0.15.7"
182source = "registry+https://github.com/rust-lang/crates.io-index" 566source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -190,19 +574,75 @@ dependencies = [
190] 574]
191 575
192[[package]] 576[[package]]
577name = "core-foundation"
578version = "0.9.3"
579source = "registry+https://github.com/rust-lang/crates.io-index"
580checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
581dependencies = [
582 "core-foundation-sys",
583 "libc",
584]
585
586[[package]]
587name = "core-foundation-sys"
588version = "0.8.4"
589source = "registry+https://github.com/rust-lang/crates.io-index"
590checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
591
592[[package]]
593name = "cpufeatures"
594version = "0.2.9"
595source = "registry+https://github.com/rust-lang/crates.io-index"
596checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
597dependencies = [
598 "libc",
599]
600
601[[package]]
602name = "crossbeam-utils"
603version = "0.8.16"
604source = "registry+https://github.com/rust-lang/crates.io-index"
605checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
606dependencies = [
607 "cfg-if",
608]
609
610[[package]]
611name = "crypto-common"
612version = "0.1.6"
613source = "registry+https://github.com/rust-lang/crates.io-index"
614checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
615dependencies = [
616 "generic-array",
617 "rand_core",
618 "typenum",
619]
620
621[[package]]
193name = "dashmap" 622name = "dashmap"
194version = "5.5.3" 623version = "5.5.3"
195source = "registry+https://github.com/rust-lang/crates.io-index" 624source = "registry+https://github.com/rust-lang/crates.io-index"
196checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" 625checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
197dependencies = [ 626dependencies = [
198 "cfg-if", 627 "cfg-if",
199 "hashbrown", 628 "hashbrown 0.14.0",
200 "lock_api", 629 "lock_api",
201 "once_cell", 630 "once_cell",
202 "parking_lot_core", 631 "parking_lot_core",
203] 632]
204 633
205[[package]] 634[[package]]
635name = "derivative"
636version = "2.2.0"
637source = "registry+https://github.com/rust-lang/crates.io-index"
638checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
639dependencies = [
640 "proc-macro2",
641 "quote",
642 "syn 1.0.109",
643]
644
645[[package]]
206name = "dialoguer" 646name = "dialoguer"
207version = "0.10.4" 647version = "0.10.4"
208source = "registry+https://github.com/rust-lang/crates.io-index" 648source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -221,6 +661,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
221checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 661checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
222 662
223[[package]] 663[[package]]
664name = "digest"
665version = "0.10.7"
666source = "registry+https://github.com/rust-lang/crates.io-index"
667checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
668dependencies = [
669 "block-buffer",
670 "crypto-common",
671 "subtle",
672]
673
674[[package]]
224name = "directories" 675name = "directories"
225version = "5.0.1" 676version = "5.0.1"
226source = "registry+https://github.com/rust-lang/crates.io-index" 677source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -276,6 +727,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
276checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 727checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
277 728
278[[package]] 729[[package]]
730name = "encoding_rs"
731version = "0.8.33"
732source = "registry+https://github.com/rust-lang/crates.io-index"
733checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
734dependencies = [
735 "cfg-if",
736]
737
738[[package]]
739name = "enumflags2"
740version = "0.7.7"
741source = "registry+https://github.com/rust-lang/crates.io-index"
742checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2"
743dependencies = [
744 "enumflags2_derive",
745 "serde",
746]
747
748[[package]]
749name = "enumflags2_derive"
750version = "0.7.7"
751source = "registry+https://github.com/rust-lang/crates.io-index"
752checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745"
753dependencies = [
754 "proc-macro2",
755 "quote",
756 "syn 2.0.32",
757]
758
759[[package]]
760name = "equivalent"
761version = "1.0.1"
762source = "registry+https://github.com/rust-lang/crates.io-index"
763checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
764
765[[package]]
279name = "errno" 766name = "errno"
280version = "0.3.3" 767version = "0.3.3"
281source = "registry+https://github.com/rust-lang/crates.io-index" 768source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -297,6 +784,21 @@ dependencies = [
297] 784]
298 785
299[[package]] 786[[package]]
787name = "event-listener"
788version = "2.5.3"
789source = "registry+https://github.com/rust-lang/crates.io-index"
790checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
791
792[[package]]
793name = "fastrand"
794version = "1.9.0"
795source = "registry+https://github.com/rust-lang/crates.io-index"
796checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
797dependencies = [
798 "instant",
799]
800
801[[package]]
300name = "fastrand" 802name = "fastrand"
301version = "2.0.0" 803version = "2.0.0"
302source = "registry+https://github.com/rust-lang/crates.io-index" 804source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -312,6 +814,21 @@ dependencies = [
312] 814]
313 815
314[[package]] 816[[package]]
817name = "fnv"
818version = "1.0.7"
819source = "registry+https://github.com/rust-lang/crates.io-index"
820checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
821
822[[package]]
823name = "form_urlencoded"
824version = "1.2.0"
825source = "registry+https://github.com/rust-lang/crates.io-index"
826checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
827dependencies = [
828 "percent-encoding",
829]
830
831[[package]]
315name = "fragile" 832name = "fragile"
316version = "2.0.0" 833version = "2.0.0"
317source = "registry+https://github.com/rust-lang/crates.io-index" 834source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -366,6 +883,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
366checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" 883checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
367 884
368[[package]] 885[[package]]
886name = "futures-lite"
887version = "1.13.0"
888source = "registry+https://github.com/rust-lang/crates.io-index"
889checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
890dependencies = [
891 "fastrand 1.9.0",
892 "futures-core",
893 "futures-io",
894 "memchr",
895 "parking",
896 "pin-project-lite",
897 "waker-fn",
898]
899
900[[package]]
901name = "futures-macro"
902version = "0.3.28"
903source = "registry+https://github.com/rust-lang/crates.io-index"
904checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
905dependencies = [
906 "proc-macro2",
907 "quote",
908 "syn 2.0.32",
909]
910
911[[package]]
369name = "futures-sink" 912name = "futures-sink"
370version = "0.3.28" 913version = "0.3.28"
371source = "registry+https://github.com/rust-lang/crates.io-index" 914source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -386,6 +929,7 @@ dependencies = [
386 "futures-channel", 929 "futures-channel",
387 "futures-core", 930 "futures-core",
388 "futures-io", 931 "futures-io",
932 "futures-macro",
389 "futures-sink", 933 "futures-sink",
390 "futures-task", 934 "futures-task",
391 "memchr", 935 "memchr",
@@ -395,18 +939,61 @@ dependencies = [
395] 939]
396 940
397[[package]] 941[[package]]
942name = "generic-array"
943version = "0.14.7"
944source = "registry+https://github.com/rust-lang/crates.io-index"
945checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
946dependencies = [
947 "typenum",
948 "version_check",
949]
950
951[[package]]
398name = "getrandom" 952name = "getrandom"
399version = "0.2.10" 953version = "0.2.10"
400source = "registry+https://github.com/rust-lang/crates.io-index" 954source = "registry+https://github.com/rust-lang/crates.io-index"
401checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 955checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
402dependencies = [ 956dependencies = [
403 "cfg-if", 957 "cfg-if",
958 "js-sys",
404 "libc", 959 "libc",
405 "wasi", 960 "wasi",
961 "wasm-bindgen",
962]
963
964[[package]]
965name = "gimli"
966version = "0.28.0"
967source = "registry+https://github.com/rust-lang/crates.io-index"
968checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
969
970[[package]]
971name = "h2"
972version = "0.3.21"
973source = "registry+https://github.com/rust-lang/crates.io-index"
974checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
975dependencies = [
976 "bytes",
977 "fnv",
978 "futures-core",
979 "futures-sink",
980 "futures-util",
981 "http",
982 "indexmap 1.9.3",
983 "slab",
984 "tokio",
985 "tokio-util",
986 "tracing",
406] 987]
407 988
408[[package]] 989[[package]]
409name = "hashbrown" 990name = "hashbrown"
991version = "0.12.3"
992source = "registry+https://github.com/rust-lang/crates.io-index"
993checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
994
995[[package]]
996name = "hashbrown"
410version = "0.14.0" 997version = "0.14.0"
411source = "registry+https://github.com/rust-lang/crates.io-index" 998source = "registry+https://github.com/rust-lang/crates.io-index"
412checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" 999checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
@@ -418,6 +1005,183 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
418checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 1005checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
419 1006
420[[package]] 1007[[package]]
1008name = "hermit-abi"
1009version = "0.3.2"
1010source = "registry+https://github.com/rust-lang/crates.io-index"
1011checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
1012
1013[[package]]
1014name = "hex"
1015version = "0.4.3"
1016source = "registry+https://github.com/rust-lang/crates.io-index"
1017checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1018
1019[[package]]
1020name = "hex_lit"
1021version = "0.1.1"
1022source = "registry+https://github.com/rust-lang/crates.io-index"
1023checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
1024
1025[[package]]
1026name = "hkdf"
1027version = "0.12.3"
1028source = "registry+https://github.com/rust-lang/crates.io-index"
1029checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437"
1030dependencies = [
1031 "hmac",
1032]
1033
1034[[package]]
1035name = "hmac"
1036version = "0.12.1"
1037source = "registry+https://github.com/rust-lang/crates.io-index"
1038checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
1039dependencies = [
1040 "digest",
1041]
1042
1043[[package]]
1044name = "http"
1045version = "0.2.9"
1046source = "registry+https://github.com/rust-lang/crates.io-index"
1047checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
1048dependencies = [
1049 "bytes",
1050 "fnv",
1051 "itoa",
1052]
1053
1054[[package]]
1055name = "http-body"
1056version = "0.4.5"
1057source = "registry+https://github.com/rust-lang/crates.io-index"
1058checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
1059dependencies = [
1060 "bytes",
1061 "http",
1062 "pin-project-lite",
1063]
1064
1065[[package]]
1066name = "httparse"
1067version = "1.8.0"
1068source = "registry+https://github.com/rust-lang/crates.io-index"
1069checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
1070
1071[[package]]
1072name = "httpdate"
1073version = "1.0.3"
1074source = "registry+https://github.com/rust-lang/crates.io-index"
1075checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
1076
1077[[package]]
1078name = "hyper"
1079version = "0.14.27"
1080source = "registry+https://github.com/rust-lang/crates.io-index"
1081checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
1082dependencies = [
1083 "bytes",
1084 "futures-channel",
1085 "futures-core",
1086 "futures-util",
1087 "h2",
1088 "http",
1089 "http-body",
1090 "httparse",
1091 "httpdate",
1092 "itoa",
1093 "pin-project-lite",
1094 "socket2 0.4.9",
1095 "tokio",
1096 "tower-service",
1097 "tracing",
1098 "want",
1099]
1100
1101[[package]]
1102name = "hyper-rustls"
1103version = "0.24.1"
1104source = "registry+https://github.com/rust-lang/crates.io-index"
1105checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97"
1106dependencies = [
1107 "futures-util",
1108 "http",
1109 "hyper",
1110 "rustls",
1111 "tokio",
1112 "tokio-rustls",
1113]
1114
1115[[package]]
1116name = "idna"
1117version = "0.4.0"
1118source = "registry+https://github.com/rust-lang/crates.io-index"
1119checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
1120dependencies = [
1121 "unicode-bidi",
1122 "unicode-normalization",
1123]
1124
1125[[package]]
1126name = "indexmap"
1127version = "1.9.3"
1128source = "registry+https://github.com/rust-lang/crates.io-index"
1129checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
1130dependencies = [
1131 "autocfg",
1132 "hashbrown 0.12.3",
1133]
1134
1135[[package]]
1136name = "indexmap"
1137version = "2.0.0"
1138source = "registry+https://github.com/rust-lang/crates.io-index"
1139checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
1140dependencies = [
1141 "equivalent",
1142 "hashbrown 0.14.0",
1143]
1144
1145[[package]]
1146name = "inout"
1147version = "0.1.3"
1148source = "registry+https://github.com/rust-lang/crates.io-index"
1149checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
1150dependencies = [
1151 "block-padding 0.3.3",
1152 "generic-array",
1153]
1154
1155[[package]]
1156name = "instant"
1157version = "0.1.12"
1158source = "registry+https://github.com/rust-lang/crates.io-index"
1159checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
1160dependencies = [
1161 "cfg-if",
1162 "js-sys",
1163 "wasm-bindgen",
1164 "web-sys",
1165]
1166
1167[[package]]
1168name = "io-lifetimes"
1169version = "1.0.11"
1170source = "registry+https://github.com/rust-lang/crates.io-index"
1171checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
1172dependencies = [
1173 "hermit-abi",
1174 "libc",
1175 "windows-sys 0.48.0",
1176]
1177
1178[[package]]
1179name = "ipnet"
1180version = "2.8.0"
1181source = "registry+https://github.com/rust-lang/crates.io-index"
1182checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
1183
1184[[package]]
421name = "itertools" 1185name = "itertools"
422version = "0.10.5" 1186version = "0.10.5"
423source = "registry+https://github.com/rust-lang/crates.io-index" 1187source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -433,6 +1197,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
433checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 1197checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
434 1198
435[[package]] 1199[[package]]
1200name = "js-sys"
1201version = "0.3.64"
1202source = "registry+https://github.com/rust-lang/crates.io-index"
1203checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
1204dependencies = [
1205 "wasm-bindgen",
1206]
1207
1208[[package]]
1209name = "keyring"
1210version = "2.0.5"
1211source = "registry+https://github.com/rust-lang/crates.io-index"
1212checksum = "9549a129bd08149e0a71b2d1ce2729780d47127991bfd0a78cc1df697ec72492"
1213dependencies = [
1214 "byteorder",
1215 "lazy_static",
1216 "linux-keyutils",
1217 "secret-service",
1218 "security-framework",
1219 "winapi",
1220]
1221
1222[[package]]
436name = "lazy_static" 1223name = "lazy_static"
437version = "1.4.0" 1224version = "1.4.0"
438source = "registry+https://github.com/rust-lang/crates.io-index" 1225source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -445,6 +1232,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
445checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 1232checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
446 1233
447[[package]] 1234[[package]]
1235name = "linux-keyutils"
1236version = "0.2.3"
1237source = "registry+https://github.com/rust-lang/crates.io-index"
1238checksum = "3f27bb67f6dd1d0bb5ab582868e4f65052e58da6401188a08f0da09cf512b84b"
1239dependencies = [
1240 "bitflags 1.3.2",
1241 "libc",
1242]
1243
1244[[package]]
1245name = "linux-raw-sys"
1246version = "0.3.8"
1247source = "registry+https://github.com/rust-lang/crates.io-index"
1248checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
1249
1250[[package]]
448name = "linux-raw-sys" 1251name = "linux-raw-sys"
449version = "0.4.7" 1252version = "0.4.7"
450source = "registry+https://github.com/rust-lang/crates.io-index" 1253source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -474,6 +1277,15 @@ checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
474 1277
475[[package]] 1278[[package]]
476name = "memoffset" 1279name = "memoffset"
1280version = "0.6.5"
1281source = "registry+https://github.com/rust-lang/crates.io-index"
1282checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
1283dependencies = [
1284 "autocfg",
1285]
1286
1287[[package]]
1288name = "memoffset"
477version = "0.7.1" 1289version = "0.7.1"
478source = "registry+https://github.com/rust-lang/crates.io-index" 1290source = "registry+https://github.com/rust-lang/crates.io-index"
479checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" 1291checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
@@ -482,6 +1294,32 @@ dependencies = [
482] 1294]
483 1295
484[[package]] 1296[[package]]
1297name = "mime"
1298version = "0.3.17"
1299source = "registry+https://github.com/rust-lang/crates.io-index"
1300checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
1301
1302[[package]]
1303name = "miniz_oxide"
1304version = "0.7.1"
1305source = "registry+https://github.com/rust-lang/crates.io-index"
1306checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
1307dependencies = [
1308 "adler",
1309]
1310
1311[[package]]
1312name = "mio"
1313version = "0.8.8"
1314source = "registry+https://github.com/rust-lang/crates.io-index"
1315checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
1316dependencies = [
1317 "libc",
1318 "wasi",
1319 "windows-sys 0.48.0",
1320]
1321
1322[[package]]
485name = "mockall" 1323name = "mockall"
486version = "0.11.4" 1324version = "0.11.4"
487source = "registry+https://github.com/rust-lang/crates.io-index" 1325source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -514,15 +1352,37 @@ version = "0.0.1"
514dependencies = [ 1352dependencies = [
515 "anyhow", 1353 "anyhow",
516 "assert_cmd", 1354 "assert_cmd",
1355 "chacha20poly1305",
517 "clap", 1356 "clap",
518 "dialoguer", 1357 "dialoguer",
519 "directories", 1358 "directories",
520 "duplicate", 1359 "duplicate",
1360 "keyring",
521 "mockall", 1361 "mockall",
1362 "nostr",
1363 "once_cell",
1364 "passwords",
1365 "rexpect 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
1366 "scrypt",
522 "serde", 1367 "serde",
523 "serde_json", 1368 "serde_json",
524 "serial_test", 1369 "serial_test",
525 "test_utils", 1370 "test_utils",
1371 "zeroize",
1372]
1373
1374[[package]]
1375name = "nix"
1376version = "0.25.1"
1377source = "registry+https://github.com/rust-lang/crates.io-index"
1378checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
1379dependencies = [
1380 "autocfg",
1381 "bitflags 1.3.2",
1382 "cfg-if",
1383 "libc",
1384 "memoffset 0.6.5",
1385 "pin-utils",
526] 1386]
527 1387
528[[package]] 1388[[package]]
@@ -534,7 +1394,7 @@ dependencies = [
534 "bitflags 1.3.2", 1394 "bitflags 1.3.2",
535 "cfg-if", 1395 "cfg-if",
536 "libc", 1396 "libc",
537 "memoffset", 1397 "memoffset 0.7.1",
538 "pin-utils", 1398 "pin-utils",
539] 1399]
540 1400
@@ -545,6 +1405,96 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
545checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 1405checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
546 1406
547[[package]] 1407[[package]]
1408name = "nostr"
1409version = "0.23.0"
1410source = "registry+https://github.com/rust-lang/crates.io-index"
1411checksum = "525a8f75106f4eeb1fedaacadc61547548fe4715c3edde7d03eed2900b467952"
1412dependencies = [
1413 "aes 0.8.3",
1414 "base64",
1415 "bech32",
1416 "bip39",
1417 "bitcoin",
1418 "bitcoin_hashes 0.12.0",
1419 "cbc",
1420 "getrandom",
1421 "instant",
1422 "reqwest",
1423 "secp256k1",
1424 "serde",
1425 "serde_json",
1426 "tracing",
1427 "url",
1428]
1429
1430[[package]]
1431name = "num"
1432version = "0.4.1"
1433source = "registry+https://github.com/rust-lang/crates.io-index"
1434checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af"
1435dependencies = [
1436 "num-bigint",
1437 "num-complex",
1438 "num-integer",
1439 "num-iter",
1440 "num-rational",
1441 "num-traits",
1442]
1443
1444[[package]]
1445name = "num-bigint"
1446version = "0.4.4"
1447source = "registry+https://github.com/rust-lang/crates.io-index"
1448checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
1449dependencies = [
1450 "autocfg",
1451 "num-integer",
1452 "num-traits",
1453]
1454
1455[[package]]
1456name = "num-complex"
1457version = "0.4.4"
1458source = "registry+https://github.com/rust-lang/crates.io-index"
1459checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214"
1460dependencies = [
1461 "num-traits",
1462]
1463
1464[[package]]
1465name = "num-integer"
1466version = "0.1.45"
1467source = "registry+https://github.com/rust-lang/crates.io-index"
1468checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
1469dependencies = [
1470 "autocfg",
1471 "num-traits",
1472]
1473
1474[[package]]
1475name = "num-iter"
1476version = "0.1.43"
1477source = "registry+https://github.com/rust-lang/crates.io-index"
1478checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
1479dependencies = [
1480 "autocfg",
1481 "num-integer",
1482 "num-traits",
1483]
1484
1485[[package]]
1486name = "num-rational"
1487version = "0.4.1"
1488source = "registry+https://github.com/rust-lang/crates.io-index"
1489checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
1490dependencies = [
1491 "autocfg",
1492 "num-bigint",
1493 "num-integer",
1494 "num-traits",
1495]
1496
1497[[package]]
548name = "num-traits" 1498name = "num-traits"
549version = "0.2.16" 1499version = "0.2.16"
550source = "registry+https://github.com/rust-lang/crates.io-index" 1500source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -554,18 +1504,49 @@ dependencies = [
554] 1504]
555 1505
556[[package]] 1506[[package]]
1507name = "object"
1508version = "0.32.0"
1509source = "registry+https://github.com/rust-lang/crates.io-index"
1510checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe"
1511dependencies = [
1512 "memchr",
1513]
1514
1515[[package]]
557name = "once_cell" 1516name = "once_cell"
558version = "1.18.0" 1517version = "1.18.0"
559source = "registry+https://github.com/rust-lang/crates.io-index" 1518source = "registry+https://github.com/rust-lang/crates.io-index"
560checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 1519checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
561 1520
562[[package]] 1521[[package]]
1522name = "opaque-debug"
1523version = "0.3.0"
1524source = "registry+https://github.com/rust-lang/crates.io-index"
1525checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
1526
1527[[package]]
563name = "option-ext" 1528name = "option-ext"
564version = "0.2.0" 1529version = "0.2.0"
565source = "registry+https://github.com/rust-lang/crates.io-index" 1530source = "registry+https://github.com/rust-lang/crates.io-index"
566checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 1531checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
567 1532
568[[package]] 1533[[package]]
1534name = "ordered-stream"
1535version = "0.2.0"
1536source = "registry+https://github.com/rust-lang/crates.io-index"
1537checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
1538dependencies = [
1539 "futures-core",
1540 "pin-project-lite",
1541]
1542
1543[[package]]
1544name = "parking"
1545version = "2.1.0"
1546source = "registry+https://github.com/rust-lang/crates.io-index"
1547checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e"
1548
1549[[package]]
569name = "parking_lot" 1550name = "parking_lot"
570version = "0.12.1" 1551version = "0.12.1"
571source = "registry+https://github.com/rust-lang/crates.io-index" 1552source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -589,6 +1570,42 @@ dependencies = [
589] 1570]
590 1571
591[[package]] 1572[[package]]
1573name = "password-hash"
1574version = "0.5.0"
1575source = "registry+https://github.com/rust-lang/crates.io-index"
1576checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
1577dependencies = [
1578 "base64ct",
1579 "rand_core",
1580 "subtle",
1581]
1582
1583[[package]]
1584name = "passwords"
1585version = "3.1.13"
1586source = "registry+https://github.com/rust-lang/crates.io-index"
1587checksum = "9ca743e019c2c679e1d92b329214cb4ffc4312200ca5ae60227aad1425c0aac8"
1588dependencies = [
1589 "random-pick",
1590]
1591
1592[[package]]
1593name = "pbkdf2"
1594version = "0.12.2"
1595source = "registry+https://github.com/rust-lang/crates.io-index"
1596checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
1597dependencies = [
1598 "digest",
1599 "hmac",
1600]
1601
1602[[package]]
1603name = "percent-encoding"
1604version = "2.3.0"
1605source = "registry+https://github.com/rust-lang/crates.io-index"
1606checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
1607
1608[[package]]
592name = "pin-project-lite" 1609name = "pin-project-lite"
593version = "0.2.13" 1610version = "0.2.13"
594source = "registry+https://github.com/rust-lang/crates.io-index" 1611source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -601,6 +1618,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
601checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1618checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
602 1619
603[[package]] 1620[[package]]
1621name = "polling"
1622version = "2.8.0"
1623source = "registry+https://github.com/rust-lang/crates.io-index"
1624checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
1625dependencies = [
1626 "autocfg",
1627 "bitflags 1.3.2",
1628 "cfg-if",
1629 "concurrent-queue",
1630 "libc",
1631 "log",
1632 "pin-project-lite",
1633 "windows-sys 0.48.0",
1634]
1635
1636[[package]]
1637name = "poly1305"
1638version = "0.8.0"
1639source = "registry+https://github.com/rust-lang/crates.io-index"
1640checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
1641dependencies = [
1642 "cpufeatures",
1643 "opaque-debug",
1644 "universal-hash",
1645]
1646
1647[[package]]
1648name = "ppv-lite86"
1649version = "0.2.17"
1650source = "registry+https://github.com/rust-lang/crates.io-index"
1651checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
1652
1653[[package]]
604name = "predicates" 1654name = "predicates"
605version = "2.1.5" 1655version = "2.1.5"
606source = "registry+https://github.com/rust-lang/crates.io-index" 1656source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -643,6 +1693,16 @@ dependencies = [
643] 1693]
644 1694
645[[package]] 1695[[package]]
1696name = "proc-macro-crate"
1697version = "1.3.1"
1698source = "registry+https://github.com/rust-lang/crates.io-index"
1699checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
1700dependencies = [
1701 "once_cell",
1702 "toml_edit",
1703]
1704
1705[[package]]
646name = "proc-macro-error" 1706name = "proc-macro-error"
647version = "1.0.4" 1707version = "1.0.4"
648source = "registry+https://github.com/rust-lang/crates.io-index" 1708source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -667,6 +1727,12 @@ dependencies = [
667] 1727]
668 1728
669[[package]] 1729[[package]]
1730name = "proc-macro-hack"
1731version = "0.5.20+deprecated"
1732source = "registry+https://github.com/rust-lang/crates.io-index"
1733checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
1734
1735[[package]]
670name = "proc-macro2" 1736name = "proc-macro2"
671version = "1.0.66" 1737version = "1.0.66"
672source = "registry+https://github.com/rust-lang/crates.io-index" 1738source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -685,6 +1751,67 @@ dependencies = [
685] 1751]
686 1752
687[[package]] 1753[[package]]
1754name = "rand"
1755version = "0.8.5"
1756source = "registry+https://github.com/rust-lang/crates.io-index"
1757checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
1758dependencies = [
1759 "libc",
1760 "rand_chacha",
1761 "rand_core",
1762]
1763
1764[[package]]
1765name = "rand_chacha"
1766version = "0.3.1"
1767source = "registry+https://github.com/rust-lang/crates.io-index"
1768checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
1769dependencies = [
1770 "ppv-lite86",
1771 "rand_core",
1772]
1773
1774[[package]]
1775name = "rand_core"
1776version = "0.6.4"
1777source = "registry+https://github.com/rust-lang/crates.io-index"
1778checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
1779dependencies = [
1780 "getrandom",
1781]
1782
1783[[package]]
1784name = "random-number"
1785version = "0.1.8"
1786source = "registry+https://github.com/rust-lang/crates.io-index"
1787checksum = "3a3da5cbb4c27c5150c03a54a7e4745437cd90f9e329ae657c0b889a144bb7be"
1788dependencies = [
1789 "proc-macro-hack",
1790 "rand",
1791 "random-number-macro-impl",
1792]
1793
1794[[package]]
1795name = "random-number-macro-impl"
1796version = "0.1.7"
1797source = "registry+https://github.com/rust-lang/crates.io-index"
1798checksum = "8b86292cf41ccfc96c5de7165c1c53d5b4ac540c5bab9d1857acbe9eba5f1a0b"
1799dependencies = [
1800 "proc-macro-hack",
1801 "quote",
1802 "syn 2.0.32",
1803]
1804
1805[[package]]
1806name = "random-pick"
1807version = "1.2.16"
1808source = "registry+https://github.com/rust-lang/crates.io-index"
1809checksum = "c179499072da789afe44127d5f4aa6012de2c2f96ef759990196b37387a2a0f8"
1810dependencies = [
1811 "random-number",
1812]
1813
1814[[package]]
688name = "redox_syscall" 1815name = "redox_syscall"
689version = "0.2.16" 1816version = "0.2.16"
690source = "registry+https://github.com/rust-lang/crates.io-index" 1817source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -743,18 +1870,106 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
743checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" 1870checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
744 1871
745[[package]] 1872[[package]]
1873name = "reqwest"
1874version = "0.11.20"
1875source = "registry+https://github.com/rust-lang/crates.io-index"
1876checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
1877dependencies = [
1878 "base64",
1879 "bytes",
1880 "encoding_rs",
1881 "futures-core",
1882 "futures-util",
1883 "h2",
1884 "http",
1885 "http-body",
1886 "hyper",
1887 "hyper-rustls",
1888 "ipnet",
1889 "js-sys",
1890 "log",
1891 "mime",
1892 "once_cell",
1893 "percent-encoding",
1894 "pin-project-lite",
1895 "rustls",
1896 "rustls-pemfile",
1897 "serde",
1898 "serde_json",
1899 "serde_urlencoded",
1900 "tokio",
1901 "tokio-rustls",
1902 "tokio-socks",
1903 "tower-service",
1904 "url",
1905 "wasm-bindgen",
1906 "wasm-bindgen-futures",
1907 "web-sys",
1908 "webpki-roots",
1909 "winreg",
1910]
1911
1912[[package]]
1913name = "rexpect"
1914version = "0.5.0"
1915source = "registry+https://github.com/rust-lang/crates.io-index"
1916checksum = "01ff60778f96fb5a48adbe421d21bf6578ed58c0872d712e7e08593c195adff8"
1917dependencies = [
1918 "comma",
1919 "nix 0.25.1",
1920 "regex",
1921 "tempfile",
1922 "thiserror",
1923]
1924
1925[[package]]
746name = "rexpect" 1926name = "rexpect"
747version = "0.5.0" 1927version = "0.5.0"
748source = "git+https://github.com/phaer/rexpect.git?branch=skip-ansi-escape-codes#f099dc4750e38a1c31d2ab06d7d3e0352679d85a" 1928source = "git+https://github.com/phaer/rexpect.git?branch=skip-ansi-escape-codes#f099dc4750e38a1c31d2ab06d7d3e0352679d85a"
749dependencies = [ 1929dependencies = [
750 "comma", 1930 "comma",
751 "nix", 1931 "nix 0.26.4",
752 "regex", 1932 "regex",
753 "tempfile", 1933 "tempfile",
754 "thiserror", 1934 "thiserror",
755] 1935]
756 1936
757[[package]] 1937[[package]]
1938name = "ring"
1939version = "0.16.20"
1940source = "registry+https://github.com/rust-lang/crates.io-index"
1941checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
1942dependencies = [
1943 "cc",
1944 "libc",
1945 "once_cell",
1946 "spin",
1947 "untrusted",
1948 "web-sys",
1949 "winapi",
1950]
1951
1952[[package]]
1953name = "rustc-demangle"
1954version = "0.1.23"
1955source = "registry+https://github.com/rust-lang/crates.io-index"
1956checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
1957
1958[[package]]
1959name = "rustix"
1960version = "0.37.23"
1961source = "registry+https://github.com/rust-lang/crates.io-index"
1962checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
1963dependencies = [
1964 "bitflags 1.3.2",
1965 "errno",
1966 "io-lifetimes",
1967 "libc",
1968 "linux-raw-sys 0.3.8",
1969 "windows-sys 0.48.0",
1970]
1971
1972[[package]]
758name = "rustix" 1973name = "rustix"
759version = "0.38.13" 1974version = "0.38.13"
760source = "registry+https://github.com/rust-lang/crates.io-index" 1975source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -763,23 +1978,148 @@ dependencies = [
763 "bitflags 2.4.0", 1978 "bitflags 2.4.0",
764 "errno", 1979 "errno",
765 "libc", 1980 "libc",
766 "linux-raw-sys", 1981 "linux-raw-sys 0.4.7",
767 "windows-sys 0.48.0", 1982 "windows-sys 0.48.0",
768] 1983]
769 1984
770[[package]] 1985[[package]]
1986name = "rustls"
1987version = "0.21.7"
1988source = "registry+https://github.com/rust-lang/crates.io-index"
1989checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8"
1990dependencies = [
1991 "log",
1992 "ring",
1993 "rustls-webpki",
1994 "sct",
1995]
1996
1997[[package]]
1998name = "rustls-pemfile"
1999version = "1.0.3"
2000source = "registry+https://github.com/rust-lang/crates.io-index"
2001checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
2002dependencies = [
2003 "base64",
2004]
2005
2006[[package]]
2007name = "rustls-webpki"
2008version = "0.101.4"
2009source = "registry+https://github.com/rust-lang/crates.io-index"
2010checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d"
2011dependencies = [
2012 "ring",
2013 "untrusted",
2014]
2015
2016[[package]]
771name = "ryu" 2017name = "ryu"
772version = "1.0.15" 2018version = "1.0.15"
773source = "registry+https://github.com/rust-lang/crates.io-index" 2019source = "registry+https://github.com/rust-lang/crates.io-index"
774checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 2020checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
775 2021
776[[package]] 2022[[package]]
2023name = "salsa20"
2024version = "0.10.2"
2025source = "registry+https://github.com/rust-lang/crates.io-index"
2026checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
2027dependencies = [
2028 "cipher 0.4.4",
2029]
2030
2031[[package]]
777name = "scopeguard" 2032name = "scopeguard"
778version = "1.2.0" 2033version = "1.2.0"
779source = "registry+https://github.com/rust-lang/crates.io-index" 2034source = "registry+https://github.com/rust-lang/crates.io-index"
780checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 2035checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
781 2036
782[[package]] 2037[[package]]
2038name = "scrypt"
2039version = "0.11.0"
2040source = "registry+https://github.com/rust-lang/crates.io-index"
2041checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
2042dependencies = [
2043 "password-hash",
2044 "pbkdf2",
2045 "salsa20",
2046 "sha2",
2047]
2048
2049[[package]]
2050name = "sct"
2051version = "0.7.0"
2052source = "registry+https://github.com/rust-lang/crates.io-index"
2053checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
2054dependencies = [
2055 "ring",
2056 "untrusted",
2057]
2058
2059[[package]]
2060name = "secp256k1"
2061version = "0.27.0"
2062source = "registry+https://github.com/rust-lang/crates.io-index"
2063checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f"
2064dependencies = [
2065 "bitcoin_hashes 0.12.0",
2066 "rand",
2067 "secp256k1-sys",
2068 "serde",
2069]
2070
2071[[package]]
2072name = "secp256k1-sys"
2073version = "0.8.1"
2074source = "registry+https://github.com/rust-lang/crates.io-index"
2075checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e"
2076dependencies = [
2077 "cc",
2078]
2079
2080[[package]]
2081name = "secret-service"
2082version = "3.0.1"
2083source = "registry+https://github.com/rust-lang/crates.io-index"
2084checksum = "5da1a5ad4d28c03536f82f77d9f36603f5e37d8869ac98f0a750d5b5686d8d95"
2085dependencies = [
2086 "aes 0.7.5",
2087 "block-modes",
2088 "futures-util",
2089 "generic-array",
2090 "hkdf",
2091 "num",
2092 "once_cell",
2093 "rand",
2094 "serde",
2095 "sha2",
2096 "zbus",
2097]
2098
2099[[package]]
2100name = "security-framework"
2101version = "2.9.2"
2102source = "registry+https://github.com/rust-lang/crates.io-index"
2103checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
2104dependencies = [
2105 "bitflags 1.3.2",
2106 "core-foundation",
2107 "core-foundation-sys",
2108 "libc",
2109 "security-framework-sys",
2110]
2111
2112[[package]]
2113name = "security-framework-sys"
2114version = "2.9.1"
2115source = "registry+https://github.com/rust-lang/crates.io-index"
2116checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
2117dependencies = [
2118 "core-foundation-sys",
2119 "libc",
2120]
2121
2122[[package]]
783name = "serde" 2123name = "serde"
784version = "1.0.188" 2124version = "1.0.188"
785source = "registry+https://github.com/rust-lang/crates.io-index" 2125source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -811,6 +2151,29 @@ dependencies = [
811] 2151]
812 2152
813[[package]] 2153[[package]]
2154name = "serde_repr"
2155version = "0.1.16"
2156source = "registry+https://github.com/rust-lang/crates.io-index"
2157checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
2158dependencies = [
2159 "proc-macro2",
2160 "quote",
2161 "syn 2.0.32",
2162]
2163
2164[[package]]
2165name = "serde_urlencoded"
2166version = "0.7.1"
2167source = "registry+https://github.com/rust-lang/crates.io-index"
2168checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
2169dependencies = [
2170 "form_urlencoded",
2171 "itoa",
2172 "ryu",
2173 "serde",
2174]
2175
2176[[package]]
814name = "serial_test" 2177name = "serial_test"
815version = "2.0.0" 2178version = "2.0.0"
816source = "registry+https://github.com/rust-lang/crates.io-index" 2179source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -836,12 +2199,53 @@ dependencies = [
836] 2199]
837 2200
838[[package]] 2201[[package]]
2202name = "sha1"
2203version = "0.10.5"
2204source = "registry+https://github.com/rust-lang/crates.io-index"
2205checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
2206dependencies = [
2207 "cfg-if",
2208 "cpufeatures",
2209 "digest",
2210]
2211
2212[[package]]
2213name = "sha2"
2214version = "0.10.7"
2215source = "registry+https://github.com/rust-lang/crates.io-index"
2216checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
2217dependencies = [
2218 "cfg-if",
2219 "cpufeatures",
2220 "digest",
2221]
2222
2223[[package]]
839name = "shell-words" 2224name = "shell-words"
840version = "1.1.0" 2225version = "1.1.0"
841source = "registry+https://github.com/rust-lang/crates.io-index" 2226source = "registry+https://github.com/rust-lang/crates.io-index"
842checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 2227checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
843 2228
844[[package]] 2229[[package]]
2230name = "signal-hook"
2231version = "0.3.17"
2232source = "registry+https://github.com/rust-lang/crates.io-index"
2233checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
2234dependencies = [
2235 "libc",
2236 "signal-hook-registry",
2237]
2238
2239[[package]]
2240name = "signal-hook-registry"
2241version = "1.4.1"
2242source = "registry+https://github.com/rust-lang/crates.io-index"
2243checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
2244dependencies = [
2245 "libc",
2246]
2247
2248[[package]]
845name = "slab" 2249name = "slab"
846version = "0.4.9" 2250version = "0.4.9"
847source = "registry+https://github.com/rust-lang/crates.io-index" 2251source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -857,6 +2261,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
857checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" 2261checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
858 2262
859[[package]] 2263[[package]]
2264name = "socket2"
2265version = "0.4.9"
2266source = "registry+https://github.com/rust-lang/crates.io-index"
2267checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
2268dependencies = [
2269 "libc",
2270 "winapi",
2271]
2272
2273[[package]]
2274name = "socket2"
2275version = "0.5.3"
2276source = "registry+https://github.com/rust-lang/crates.io-index"
2277checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877"
2278dependencies = [
2279 "libc",
2280 "windows-sys 0.48.0",
2281]
2282
2283[[package]]
2284name = "spin"
2285version = "0.5.2"
2286source = "registry+https://github.com/rust-lang/crates.io-index"
2287checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
2288
2289[[package]]
2290name = "static_assertions"
2291version = "1.1.0"
2292source = "registry+https://github.com/rust-lang/crates.io-index"
2293checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
2294
2295[[package]]
860name = "strip-ansi-escapes" 2296name = "strip-ansi-escapes"
861version = "0.2.0" 2297version = "0.2.0"
862source = "registry+https://github.com/rust-lang/crates.io-index" 2298source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -872,6 +2308,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
872checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 2308checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
873 2309
874[[package]] 2310[[package]]
2311name = "subtle"
2312version = "2.5.0"
2313source = "registry+https://github.com/rust-lang/crates.io-index"
2314checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
2315
2316[[package]]
875name = "syn" 2317name = "syn"
876version = "1.0.109" 2318version = "1.0.109"
877source = "registry+https://github.com/rust-lang/crates.io-index" 2319source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -900,9 +2342,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
900checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" 2342checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
901dependencies = [ 2343dependencies = [
902 "cfg-if", 2344 "cfg-if",
903 "fastrand", 2345 "fastrand 2.0.0",
904 "redox_syscall 0.3.5", 2346 "redox_syscall 0.3.5",
905 "rustix", 2347 "rustix 0.38.13",
906 "windows-sys 0.48.0", 2348 "windows-sys 0.48.0",
907] 2349]
908 2350
@@ -920,7 +2362,9 @@ dependencies = [
920 "assert_cmd", 2362 "assert_cmd",
921 "dialoguer", 2363 "dialoguer",
922 "directories", 2364 "directories",
923 "rexpect", 2365 "nostr",
2366 "once_cell",
2367 "rexpect 0.5.0 (git+https://github.com/phaer/rexpect.git?branch=skip-ansi-escape-codes)",
924 "strip-ansi-escapes", 2368 "strip-ansi-escapes",
925] 2369]
926 2370
@@ -945,18 +2389,204 @@ dependencies = [
945] 2389]
946 2390
947[[package]] 2391[[package]]
2392name = "tinyvec"
2393version = "1.6.0"
2394source = "registry+https://github.com/rust-lang/crates.io-index"
2395checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
2396dependencies = [
2397 "tinyvec_macros",
2398]
2399
2400[[package]]
2401name = "tinyvec_macros"
2402version = "0.1.1"
2403source = "registry+https://github.com/rust-lang/crates.io-index"
2404checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
2405
2406[[package]]
2407name = "tokio"
2408version = "1.32.0"
2409source = "registry+https://github.com/rust-lang/crates.io-index"
2410checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
2411dependencies = [
2412 "backtrace",
2413 "bytes",
2414 "libc",
2415 "mio",
2416 "pin-project-lite",
2417 "socket2 0.5.3",
2418 "windows-sys 0.48.0",
2419]
2420
2421[[package]]
2422name = "tokio-rustls"
2423version = "0.24.1"
2424source = "registry+https://github.com/rust-lang/crates.io-index"
2425checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
2426dependencies = [
2427 "rustls",
2428 "tokio",
2429]
2430
2431[[package]]
2432name = "tokio-socks"
2433version = "0.5.1"
2434source = "registry+https://github.com/rust-lang/crates.io-index"
2435checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0"
2436dependencies = [
2437 "either",
2438 "futures-util",
2439 "thiserror",
2440 "tokio",
2441]
2442
2443[[package]]
2444name = "tokio-util"
2445version = "0.7.8"
2446source = "registry+https://github.com/rust-lang/crates.io-index"
2447checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
2448dependencies = [
2449 "bytes",
2450 "futures-core",
2451 "futures-sink",
2452 "pin-project-lite",
2453 "tokio",
2454 "tracing",
2455]
2456
2457[[package]]
2458name = "toml_datetime"
2459version = "0.6.3"
2460source = "registry+https://github.com/rust-lang/crates.io-index"
2461checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
2462
2463[[package]]
2464name = "toml_edit"
2465version = "0.19.14"
2466source = "registry+https://github.com/rust-lang/crates.io-index"
2467checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
2468dependencies = [
2469 "indexmap 2.0.0",
2470 "toml_datetime",
2471 "winnow",
2472]
2473
2474[[package]]
2475name = "tower-service"
2476version = "0.3.2"
2477source = "registry+https://github.com/rust-lang/crates.io-index"
2478checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
2479
2480[[package]]
2481name = "tracing"
2482version = "0.1.37"
2483source = "registry+https://github.com/rust-lang/crates.io-index"
2484checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
2485dependencies = [
2486 "cfg-if",
2487 "pin-project-lite",
2488 "tracing-attributes",
2489 "tracing-core",
2490]
2491
2492[[package]]
2493name = "tracing-attributes"
2494version = "0.1.26"
2495source = "registry+https://github.com/rust-lang/crates.io-index"
2496checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
2497dependencies = [
2498 "proc-macro2",
2499 "quote",
2500 "syn 2.0.32",
2501]
2502
2503[[package]]
2504name = "tracing-core"
2505version = "0.1.31"
2506source = "registry+https://github.com/rust-lang/crates.io-index"
2507checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
2508dependencies = [
2509 "once_cell",
2510]
2511
2512[[package]]
2513name = "try-lock"
2514version = "0.2.4"
2515source = "registry+https://github.com/rust-lang/crates.io-index"
2516checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
2517
2518[[package]]
2519name = "typenum"
2520version = "1.16.0"
2521source = "registry+https://github.com/rust-lang/crates.io-index"
2522checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
2523
2524[[package]]
2525name = "uds_windows"
2526version = "1.0.2"
2527source = "registry+https://github.com/rust-lang/crates.io-index"
2528checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d"
2529dependencies = [
2530 "tempfile",
2531 "winapi",
2532]
2533
2534[[package]]
2535name = "unicode-bidi"
2536version = "0.3.13"
2537source = "registry+https://github.com/rust-lang/crates.io-index"
2538checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
2539
2540[[package]]
948name = "unicode-ident" 2541name = "unicode-ident"
949version = "1.0.11" 2542version = "1.0.11"
950source = "registry+https://github.com/rust-lang/crates.io-index" 2543source = "registry+https://github.com/rust-lang/crates.io-index"
951checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 2544checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
952 2545
953[[package]] 2546[[package]]
2547name = "unicode-normalization"
2548version = "0.1.22"
2549source = "registry+https://github.com/rust-lang/crates.io-index"
2550checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
2551dependencies = [
2552 "tinyvec",
2553]
2554
2555[[package]]
954name = "unicode-width" 2556name = "unicode-width"
955version = "0.1.10" 2557version = "0.1.10"
956source = "registry+https://github.com/rust-lang/crates.io-index" 2558source = "registry+https://github.com/rust-lang/crates.io-index"
957checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 2559checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
958 2560
959[[package]] 2561[[package]]
2562name = "universal-hash"
2563version = "0.5.1"
2564source = "registry+https://github.com/rust-lang/crates.io-index"
2565checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
2566dependencies = [
2567 "crypto-common",
2568 "subtle",
2569]
2570
2571[[package]]
2572name = "untrusted"
2573version = "0.7.1"
2574source = "registry+https://github.com/rust-lang/crates.io-index"
2575checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
2576
2577[[package]]
2578name = "url"
2579version = "2.4.1"
2580source = "registry+https://github.com/rust-lang/crates.io-index"
2581checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
2582dependencies = [
2583 "form_urlencoded",
2584 "idna",
2585 "percent-encoding",
2586 "serde",
2587]
2588
2589[[package]]
960name = "utf8parse" 2590name = "utf8parse"
961version = "0.2.1" 2591version = "0.2.1"
962source = "registry+https://github.com/rust-lang/crates.io-index" 2592source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -998,12 +2628,131 @@ dependencies = [
998] 2628]
999 2629
1000[[package]] 2630[[package]]
2631name = "waker-fn"
2632version = "1.1.0"
2633source = "registry+https://github.com/rust-lang/crates.io-index"
2634checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
2635
2636[[package]]
2637name = "want"
2638version = "0.3.1"
2639source = "registry+https://github.com/rust-lang/crates.io-index"
2640checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
2641dependencies = [
2642 "try-lock",
2643]
2644
2645[[package]]
1001name = "wasi" 2646name = "wasi"
1002version = "0.11.0+wasi-snapshot-preview1" 2647version = "0.11.0+wasi-snapshot-preview1"
1003source = "registry+https://github.com/rust-lang/crates.io-index" 2648source = "registry+https://github.com/rust-lang/crates.io-index"
1004checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2649checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
1005 2650
1006[[package]] 2651[[package]]
2652name = "wasm-bindgen"
2653version = "0.2.87"
2654source = "registry+https://github.com/rust-lang/crates.io-index"
2655checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
2656dependencies = [
2657 "cfg-if",
2658 "wasm-bindgen-macro",
2659]
2660
2661[[package]]
2662name = "wasm-bindgen-backend"
2663version = "0.2.87"
2664source = "registry+https://github.com/rust-lang/crates.io-index"
2665checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
2666dependencies = [
2667 "bumpalo",
2668 "log",
2669 "once_cell",
2670 "proc-macro2",
2671 "quote",
2672 "syn 2.0.32",
2673 "wasm-bindgen-shared",
2674]
2675
2676[[package]]
2677name = "wasm-bindgen-futures"
2678version = "0.4.37"
2679source = "registry+https://github.com/rust-lang/crates.io-index"
2680checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
2681dependencies = [
2682 "cfg-if",
2683 "js-sys",
2684 "wasm-bindgen",
2685 "web-sys",
2686]
2687
2688[[package]]
2689name = "wasm-bindgen-macro"
2690version = "0.2.87"
2691source = "registry+https://github.com/rust-lang/crates.io-index"
2692checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
2693dependencies = [
2694 "quote",
2695 "wasm-bindgen-macro-support",
2696]
2697
2698[[package]]
2699name = "wasm-bindgen-macro-support"
2700version = "0.2.87"
2701source = "registry+https://github.com/rust-lang/crates.io-index"
2702checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
2703dependencies = [
2704 "proc-macro2",
2705 "quote",
2706 "syn 2.0.32",
2707 "wasm-bindgen-backend",
2708 "wasm-bindgen-shared",
2709]
2710
2711[[package]]
2712name = "wasm-bindgen-shared"
2713version = "0.2.87"
2714source = "registry+https://github.com/rust-lang/crates.io-index"
2715checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
2716
2717[[package]]
2718name = "web-sys"
2719version = "0.3.64"
2720source = "registry+https://github.com/rust-lang/crates.io-index"
2721checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
2722dependencies = [
2723 "js-sys",
2724 "wasm-bindgen",
2725]
2726
2727[[package]]
2728name = "webpki-roots"
2729version = "0.25.2"
2730source = "registry+https://github.com/rust-lang/crates.io-index"
2731checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
2732
2733[[package]]
2734name = "winapi"
2735version = "0.3.9"
2736source = "registry+https://github.com/rust-lang/crates.io-index"
2737checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
2738dependencies = [
2739 "winapi-i686-pc-windows-gnu",
2740 "winapi-x86_64-pc-windows-gnu",
2741]
2742
2743[[package]]
2744name = "winapi-i686-pc-windows-gnu"
2745version = "0.4.0"
2746source = "registry+https://github.com/rust-lang/crates.io-index"
2747checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
2748
2749[[package]]
2750name = "winapi-x86_64-pc-windows-gnu"
2751version = "0.4.0"
2752source = "registry+https://github.com/rust-lang/crates.io-index"
2753checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
2754
2755[[package]]
1007name = "windows-sys" 2756name = "windows-sys"
1008version = "0.45.0" 2757version = "0.45.0"
1009source = "registry+https://github.com/rust-lang/crates.io-index" 2758source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1136,7 +2885,140 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1136checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2885checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
1137 2886
1138[[package]] 2887[[package]]
2888name = "winnow"
2889version = "0.5.15"
2890source = "registry+https://github.com/rust-lang/crates.io-index"
2891checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
2892dependencies = [
2893 "memchr",
2894]
2895
2896[[package]]
2897name = "winreg"
2898version = "0.50.0"
2899source = "registry+https://github.com/rust-lang/crates.io-index"
2900checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
2901dependencies = [
2902 "cfg-if",
2903 "windows-sys 0.48.0",
2904]
2905
2906[[package]]
2907name = "xdg-home"
2908version = "1.0.0"
2909source = "registry+https://github.com/rust-lang/crates.io-index"
2910checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd"
2911dependencies = [
2912 "nix 0.26.4",
2913 "winapi",
2914]
2915
2916[[package]]
2917name = "zbus"
2918version = "3.14.1"
2919source = "registry+https://github.com/rust-lang/crates.io-index"
2920checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948"
2921dependencies = [
2922 "async-broadcast",
2923 "async-executor",
2924 "async-fs",
2925 "async-io",
2926 "async-lock",
2927 "async-process",
2928 "async-recursion",
2929 "async-task",
2930 "async-trait",
2931 "blocking",
2932 "byteorder",
2933 "derivative",
2934 "enumflags2",
2935 "event-listener",
2936 "futures-core",
2937 "futures-sink",
2938 "futures-util",
2939 "hex",
2940 "nix 0.26.4",
2941 "once_cell",
2942 "ordered-stream",
2943 "rand",
2944 "serde",
2945 "serde_repr",
2946 "sha1",
2947 "static_assertions",
2948 "tracing",
2949 "uds_windows",
2950 "winapi",
2951 "xdg-home",
2952 "zbus_macros",
2953 "zbus_names",
2954 "zvariant",
2955]
2956
2957[[package]]
2958name = "zbus_macros"
2959version = "3.14.1"
2960source = "registry+https://github.com/rust-lang/crates.io-index"
2961checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d"
2962dependencies = [
2963 "proc-macro-crate",
2964 "proc-macro2",
2965 "quote",
2966 "regex",
2967 "syn 1.0.109",
2968 "zvariant_utils",
2969]
2970
2971[[package]]
2972name = "zbus_names"
2973version = "2.6.0"
2974source = "registry+https://github.com/rust-lang/crates.io-index"
2975checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9"
2976dependencies = [
2977 "serde",
2978 "static_assertions",
2979 "zvariant",
2980]
2981
2982[[package]]
1139name = "zeroize" 2983name = "zeroize"
1140version = "1.6.0" 2984version = "1.6.0"
1141source = "registry+https://github.com/rust-lang/crates.io-index" 2985source = "registry+https://github.com/rust-lang/crates.io-index"
1142checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" 2986checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
2987
2988[[package]]
2989name = "zvariant"
2990version = "3.15.0"
2991source = "registry+https://github.com/rust-lang/crates.io-index"
2992checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c"
2993dependencies = [
2994 "byteorder",
2995 "enumflags2",
2996 "libc",
2997 "serde",
2998 "static_assertions",
2999 "zvariant_derive",
3000]
3001
3002[[package]]
3003name = "zvariant_derive"
3004version = "3.15.0"
3005source = "registry+https://github.com/rust-lang/crates.io-index"
3006checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd"
3007dependencies = [
3008 "proc-macro-crate",
3009 "proc-macro2",
3010 "quote",
3011 "syn 1.0.109",
3012 "zvariant_utils",
3013]
3014
3015[[package]]
3016name = "zvariant_utils"
3017version = "1.0.1"
3018source = "registry+https://github.com/rust-lang/crates.io-index"
3019checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200"
3020dependencies = [
3021 "proc-macro2",
3022 "quote",
3023 "syn 1.0.109",
3024]
diff --git a/Cargo.toml b/Cargo.toml
index e745441..6d8ebaf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,16 +13,24 @@ categories = ["command-line-utilities","git"]
13 13
14[dependencies] 14[dependencies]
15anyhow = "1.0.75" 15anyhow = "1.0.75"
16chacha20poly1305 = "0.10.1"
16clap = { version = "4.3.19", features = ["derive"] } 17clap = { version = "4.3.19", features = ["derive"] }
17dialoguer = "0.10.4" 18dialoguer = "0.10.4"
18directories = "5.0.1" 19directories = "5.0.1"
20keyring = "2.0.5"
21nostr = "0.23.0"
22passwords = "3.1.13"
23scrypt = "0.11.0"
19serde = { version = "1.0.181", features = ["derive"] } 24serde = { version = "1.0.181", features = ["derive"] }
20serde_json = "1.0.105" 25serde_json = "1.0.105"
26zeroize = "1.6.0"
21 27
22[dev-dependencies] 28[dev-dependencies]
23assert_cmd = "2.0.12" 29assert_cmd = "2.0.12"
24duplicate = "1.0.0" 30duplicate = "1.0.0"
25mockall = "0.11.4" 31mockall = "0.11.4"
32once_cell = "1.18.0"
33rexpect = "0.5.0"
26serial_test = "2.0.0" 34serial_test = "2.0.0"
27test_utils = { path = "test_utils" } 35test_utils = { path = "test_utils" }
28 36
diff --git a/flake.nix b/flake.nix
index 7c36e2d..2fa8d8a 100644
--- a/flake.nix
+++ b/flake.nix
@@ -18,19 +18,13 @@
18 devShells.default = mkShell { 18 devShells.default = mkShell {
19 19
20 nativeBuildInputs = [ 20 nativeBuildInputs = [
21 # stable to be introduced when the following issue is resolved 21 # override rustfmt with nightly toolchain version to support unstable features
22 # ideally this wouldn't be pinned to a specific nightly version but
23 # selectLatestNightlyWith isn't support with mixed toolchains
22 # https://github.com/oxalica/rust-overlay/issues/136 24 # https://github.com/oxalica/rust-overlay/issues/136
23 # rust-bin.stable.latest.default 25 (lib.hiPrio rust-bin.nightly."2023-09-01".rustfmt)
24 # nightly for rustfmt 26 rust-bin.stable.latest.default
25 ( 27
26 rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override {
27 extensions = [
28 "rust-src"
29 "rustfmt"
30 "clippy"
31 ];
32 })
33 )
34 ]; 28 ];
35 29
36 buildInputs = [ 30 buildInputs = [
diff --git a/planning.md b/planning.md
new file mode 100644
index 0000000..d17d176
--- /dev/null
+++ b/planning.md
@@ -0,0 +1,78 @@
1/* !
2
3# Authentication and Key Management Requirements
4
5## User Experience
6
7For a smooth UX:
81. a private key should only need to be imported once
92. authentication to sign events should persist at least across multiple calls
10to the cli tool within a single terminal session.
11
12## Security
13
141. key material must be encrypted with a salted passphrase when stored on disk.
152. the passphase should only be accessable
16 a) by this specific cli tool; or alternatively
17 b) only within the terminal session
18
19
20# Implementation
21
22Every private key entired into the tool is encrypted with a salted user
23provided passphrase and stored on disk in the tool's configuration file
24alongside display_name and public key for identification.
25
26The private key of the current logged-in user is encrypted with a salted
27randomly generated token and stored on disk in the configuration file alongside
28the public key for identification. The token is stored in the OS's keyring
29using a rust crate called 'keyring'. On Linux this expires after a few days
30whilst on Windows and MacOS it never expires.
31
32Should the token be cycled? cycling the token would prevent an attacker who had
33access to only the token or the encrypted key from returning after the token
34had been cycled. This isn't worth it. An attacker is much more likely to have
35access to both simultainiously.
36
37logout should delete the key encrypted with the token and the token. It should
38give the option to clear encrypted key material for the current user or all
39users.
40
41*/
42
43init
44
45initialize repoisiotr
46
47
48replaceable event
49
50commit id
51
52search by initial commit / initial 5 commits
53name
54
55
56
57initialising a reposistory
58
59
60git nostr init
61 > intialise repo
62
63
64git nostr init - request patches / PRs, issues,
65 features to support
66 -- branch
67 -- patches / PRs
68 -- issues
69
70 -- override git push to also push to nostr.
71
72 settings
73 --git-repos - one or more git repositories where the latest commits can be pulled from
74 --name
75 --description
76
77
78git push nostr main
diff --git a/src/cli_interactor.rs b/src/cli_interactor.rs
index 2f28aee..d7de087 100644
--- a/src/cli_interactor.rs
+++ b/src/cli_interactor.rs
@@ -1,5 +1,5 @@
1use anyhow::{bail, Result}; 1use anyhow::Result;
2use dialoguer::{theme::ColorfulTheme, Input}; 2use dialoguer::{theme::ColorfulTheme, Input, Password};
3#[cfg(test)] 3#[cfg(test)]
4use mockall::*; 4use mockall::*;
5 5
@@ -11,6 +11,7 @@ pub struct Interactor {
11#[cfg_attr(test, automock)] 11#[cfg_attr(test, automock)]
12pub trait InteractorPrompt { 12pub trait InteractorPrompt {
13 fn input(&self, parms: PromptInputParms) -> Result<String>; 13 fn input(&self, parms: PromptInputParms) -> Result<String>;
14 fn password(&self, parms: PromptPasswordParms) -> Result<String>;
14} 15}
15impl InteractorPrompt for Interactor { 16impl InteractorPrompt for Interactor {
16 fn input(&self, parms: PromptInputParms) -> Result<String> { 17 fn input(&self, parms: PromptInputParms) -> Result<String> {
@@ -19,6 +20,15 @@ impl InteractorPrompt for Interactor {
19 .interact_text()?; 20 .interact_text()?;
20 Ok(input) 21 Ok(input)
21 } 22 }
23 fn password(&self, parms: PromptPasswordParms) -> Result<String> {
24 let mut p = Password::with_theme(&self.theme);
25 p.with_prompt(parms.prompt);
26 if parms.confirm {
27 p.with_confirmation("confirm password", "passwords didnt match...");
28 }
29 let pass: String = p.interact()?;
30 Ok(pass)
31 }
22} 32}
23 33
24#[derive(Default)] 34#[derive(Default)]
@@ -32,3 +42,20 @@ impl PromptInputParms {
32 self 42 self
33 } 43 }
34} 44}
45
46#[derive(Default)]
47pub struct PromptPasswordParms {
48 pub prompt: String,
49 pub confirm: bool,
50}
51
52impl PromptPasswordParms {
53 pub fn with_prompt<S: Into<String>>(mut self, prompt: S) -> Self {
54 self.prompt = prompt.into();
55 self
56 }
57 pub const fn with_confirm(mut self) -> Self {
58 self.confirm = true;
59 self
60 }
61}
diff --git a/src/config.rs b/src/config.rs
index b26dea0..f410934 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -4,6 +4,7 @@ use anyhow::{anyhow, Context, Result};
4use directories::ProjectDirs; 4use directories::ProjectDirs;
5#[cfg(test)] 5#[cfg(test)]
6use mockall::*; 6use mockall::*;
7use nostr::secp256k1::XOnlyPublicKey;
7use serde::{self, Deserialize, Serialize}; 8use serde::{self, Deserialize, Serialize};
8 9
9#[derive(Default)] 10#[derive(Default)]
@@ -59,7 +60,7 @@ impl ConfigManagement for ConfigManager {
59 } 60 }
60} 61}
61 62
62#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)] 63#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)]
63#[allow(clippy::module_name_repetitions)] 64#[allow(clippy::module_name_repetitions)]
64pub struct MyConfig { 65pub struct MyConfig {
65 pub version: u8, 66 pub version: u8,
@@ -68,44 +69,64 @@ pub struct MyConfig {
68 69
69#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] 70#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
70pub struct UserRef { 71pub struct UserRef {
71 pub nsec: String, 72 pub public_key: XOnlyPublicKey,
73 pub encrypted_key: String,
72} 74}
73 75
74#[cfg(test)] 76#[cfg(test)]
75mod tests { 77mod tests {
76 use anyhow::Result; 78 use anyhow::Result;
77 use serial_test::serial; 79 use serial_test::serial;
78 use test_utils::*;
79 80
80 use super::*; 81 use super::*;
81 82
83 fn backup_existing_config() -> Result<()> {
84 let config_path = get_dirs()?.config_dir().join("config.json");
85 let backup_config_path = get_dirs()?.config_dir().join("config-backup.json");
86 if config_path.exists() {
87 std::fs::rename(config_path, backup_config_path)?;
88 }
89 Ok(())
90 }
91
92 fn restore_config_backup() -> Result<()> {
93 let config_path = get_dirs()?.config_dir().join("config.json");
94 let backup_config_path = get_dirs()?.config_dir().join("config-backup.json");
95 if config_path.exists() {
96 std::fs::remove_file(&config_path)?;
97 }
98 if backup_config_path.exists() {
99 std::fs::rename(backup_config_path, config_path)?;
100 }
101 Ok(())
102 }
103
82 mod load { 104 mod load {
83 use super::*; 105 use super::*;
84 106
85 #[test] 107 #[test]
86 #[serial] 108 #[serial]
87 fn when_config_file_doesnt_exist_defaults_are_returned() -> Result<()> { 109 fn when_config_file_doesnt_exist_defaults_are_returned() -> Result<()> {
88 with_fresh_config(|| { 110 backup_existing_config()?;
89 assert_eq!(ConfigManager.load()?, MyConfig::default()); 111 let c = ConfigManager;
90 112 assert_eq!(c.load()?, MyConfig::default());
91 Ok(()) 113 restore_config_backup()?;
92 }) 114 Ok(())
93 } 115 }
94 116
95 #[test] 117 #[test]
96 #[serial] 118 #[serial]
97 fn when_config_file_exists_it_is_returned() -> Result<()> { 119 fn when_config_file_exists_it_is_returned() -> Result<()> {
98 with_fresh_config(|| { 120 backup_existing_config()?;
99 let c = ConfigManager; 121 let c = ConfigManager;
100 let new_config = MyConfig { 122 let new_config = MyConfig {
101 version: 255, 123 version: 255,
102 ..MyConfig::default() 124 ..MyConfig::default()
103 }; 125 };
104 c.save(&new_config)?; 126 c.save(&new_config)?;
105 assert_eq!(c.load()?, new_config); 127 assert_eq!(c.load()?, new_config);
106 128 restore_config_backup()?;
107 Ok(()) 129 Ok(())
108 })
109 } 130 }
110 } 131 }
111 132
@@ -115,38 +136,36 @@ mod tests {
115 #[test] 136 #[test]
116 #[serial] 137 #[serial]
117 fn when_config_file_doesnt_config_is_saved() -> Result<()> { 138 fn when_config_file_doesnt_config_is_saved() -> Result<()> {
118 with_fresh_config(|| { 139 backup_existing_config()?;
119 let c = ConfigManager; 140 let c = ConfigManager;
120 let new_config = MyConfig { 141 let new_config = MyConfig {
121 version: 255, 142 version: 255,
122 ..MyConfig::default() 143 ..MyConfig::default()
123 }; 144 };
124 c.save(&new_config)?; 145 c.save(&new_config)?;
125 assert_eq!(c.load()?, new_config); 146 assert_eq!(c.load().unwrap(), new_config);
126 147 restore_config_backup()?;
127 Ok(()) 148 Ok(())
128 })
129 } 149 }
130 150
131 #[test] 151 #[test]
132 #[serial] 152 #[serial]
133 fn when_config_file_exists_new_config_is_saved() -> Result<()> { 153 fn when_config_file_exists_new_config_is_saved() -> Result<()> {
134 with_fresh_config(|| { 154 backup_existing_config()?;
135 let c = ConfigManager; 155 let c = ConfigManager;
136 let config = MyConfig { 156 let config = MyConfig {
137 version: 255, 157 version: 255,
138 ..MyConfig::default() 158 ..MyConfig::default()
139 }; 159 };
140 c.save(&config)?; 160 c.save(&config)?;
141 let new_config = MyConfig { 161 let new_config = MyConfig {
142 version: 254, 162 version: 254,
143 ..MyConfig::default() 163 ..MyConfig::default()
144 }; 164 };
145 c.save(&new_config)?; 165 c.save(&new_config)?;
146 assert_eq!(c.load()?, new_config); 166 assert_eq!(c.load().unwrap(), new_config);
147 167 restore_config_backup()?;
148 Ok(()) 168 Ok(())
149 })
150 } 169 }
151 } 170 }
152} 171}
diff --git a/src/key_handling/encryption.rs b/src/key_handling/encryption.rs
new file mode 100644
index 0000000..0ef7f69
--- /dev/null
+++ b/src/key_handling/encryption.rs
@@ -0,0 +1,247 @@
1use anyhow::{anyhow, bail, ensure, Context, Result};
2use chacha20poly1305::{
3 aead::{rand_core::RngCore, Aead, AeadCore, KeyInit, OsRng, Payload},
4 XChaCha20Poly1305,
5};
6#[cfg(test)]
7use mockall::*;
8use nostr::{prelude::*, Keys};
9use rand::{distributions::Alphanumeric, thread_rng, Rng};
10use zeroize::Zeroize;
11
12#[derive(Default)]
13pub struct Encryptor;
14
15#[cfg_attr(test, automock)]
16pub trait EncryptDecrypt {
17 /// requires less CPU time if the password is long
18 fn encrypt_key(&self, keys: &Keys, password: &str) -> Result<String>;
19 fn decrypt_key(&self, encrypted_key: &str, password: &str) -> Result<Keys>;
20 /// generates a long random string
21 fn random_token(&self) -> String;
22}
23
24/// approach and code adapted from nostr gossip client
25impl EncryptDecrypt for Encryptor {
26 fn encrypt_key(&self, keys: &Keys, password: &str) -> Result<String> {
27 // Generate a random 16-byte salt
28 let salt = {
29 let mut salt: [u8; 16] = [0; 16];
30 OsRng.fill_bytes(&mut salt);
31 salt
32 };
33
34 let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
35
36 let log2_rounds: u8 = if password.len() > 20 {
37 // we have enough of entropy - no need to spend CPU time adding much more
38 1
39 } else {
40 // default (scrypt::Params::RECOMMENDED_LOG_N) is 17 but 30s is too long to wait
41 15
42 };
43
44 let associated_data: Vec<u8> = vec![1];
45
46 let ciphertext = {
47 let cipher = {
48 let symmetric_key = password_to_key(password, &salt, log2_rounds)
49 .context("failed create encryption key from password")?;
50 XChaCha20Poly1305::new((&symmetric_key).into())
51 };
52 cipher
53 .encrypt(
54 &nonce,
55 Payload {
56 msg: keys
57 .secret_key()
58 .context(
59 "supplied key should reveal secret key. Is this a public key only?",
60 )?
61 .display_secret()
62 .to_string()
63 .as_bytes(),
64 aad: &associated_data,
65 },
66 )
67 .map_err(|_| anyhow!("ChaChaPoly1305 failed to encrypt nsec with password"))?
68 };
69 // Combine salt, IV and ciphertext
70 let mut concatenation: Vec<u8> = Vec::new();
71 concatenation.push(0x1); // 1 byte version number
72 concatenation.push(log2_rounds); // 1 byte for scrypt N (rounds)
73 concatenation.extend(salt); // 16 bytes of salt
74 concatenation.extend(nonce); // 24 bytes of nonce
75 concatenation.extend(associated_data); // 1 byte of key security
76 concatenation.extend(ciphertext); // 48 bytes of ciphertext expected
77 // Total length is 91 = 1 + 1 + 16 + 24 + 1 + 48
78
79 bech32::encode(
80 "ncryptsec",
81 concatenation.to_base32(),
82 bech32::Variant::Bech32,
83 )
84 .context("encrypted nsec failed to encode")
85 }
86
87 fn decrypt_key(&self, encrypted_key: &str, password: &str) -> Result<nostr::Keys> {
88 let data =
89 bech32::decode(encrypted_key).context("failed to decode encrypted key as bech32")?;
90 if data.0 != "ncryptsec" {
91 bail!("encrypted key is in the wrong format - it doesnt start with ncryptsec");
92 }
93 let concatenation = Vec::<u8>::from_base32(&data.1)
94 .context("failed to convert bech32::decode output to Vec<u8>")?;
95
96 // Break into parts
97 let version: u8 = concatenation[0];
98 ensure!(version == 0x1, "encryption version is incorrect");
99 let log2_rounds: u8 = concatenation[1];
100 let salt: [u8; 16] = concatenation[2..2 + 16].try_into()?;
101 let nonce = &concatenation[2 + 16..2 + 16 + 24];
102 let associated_data = &concatenation[(2 + 16 + 24)..=(2 + 16 + 24)];
103 let ciphertext = &concatenation[2 + 16 + 24 + 1..];
104
105 let cipher = {
106 let symmetric_key = password_to_key(password, &salt, log2_rounds)?;
107 XChaCha20Poly1305::new((&symmetric_key).into())
108 };
109
110 let payload = Payload {
111 msg: ciphertext,
112 aad: associated_data,
113 };
114
115 let mut inner_secret = cipher
116 .decrypt(nonce.into(), payload)
117 .map_err(|_| anyhow!("failed to decrypt"))?;
118
119 if associated_data.is_empty() {
120 bail!("invalid encrypted key");
121 }
122
123 let key = Keys::from_sk_str(
124 std::str::from_utf8(&inner_secret).context("inner secret is not [u8]")?,
125 )
126 .context("incorrect password. Key decrypted with password did not produce a valid nsec.")?;
127
128 inner_secret.zeroize();
129
130 Ok(key)
131 }
132
133 fn random_token(&self) -> String {
134 thread_rng()
135 .sample_iter(&Alphanumeric)
136 .take(32)
137 .map(char::from)
138 .collect()
139 }
140}
141
142/// uses scrypt to stretch password into key
143fn password_to_key(password: &str, salt: &[u8; 16], log_n: u8) -> Result<[u8; 32]> {
144 let params = scrypt::Params::new(log_n, 8, 1, 32)
145 .context("scrypt failed to generate params to stretch password")?;
146 let mut key: [u8; 32] = [0; 32];
147 if log_n > 14 {
148 println!("this may take a few seconds...");
149 }
150
151 scrypt::scrypt(password.as_bytes(), salt, &params, &mut key)
152 .context("scrypt failed to stretch password")?;
153 Ok(key)
154}
155
156#[cfg(test)]
157mod tests {
158 use test_utils::*;
159
160 use super::*;
161
162 #[test]
163 fn encrypt_key_produces_string_prefixed_with() -> Result<()> {
164 let s = Encryptor.encrypt_key(&nostr::Keys::generate(), TEST_PASSWORD)?;
165 assert!(s.starts_with("ncryptsec"));
166 Ok(())
167 }
168
169 #[test]
170 // ensures password encryption hasn't changed
171 fn decrypts_with_strong_password_from_reference_string() -> Result<()> {
172 let encryptor = Encryptor;
173 let decrypted_key = encryptor.decrypt_key(TEST_KEY_1_ENCRYPTED, TEST_PASSWORD)?;
174
175 assert_eq!(
176 format!(
177 "{}",
178 TEST_KEY_1_KEYS.secret_key().unwrap().to_bech32().unwrap()
179 ),
180 format!(
181 "{}",
182 decrypted_key.secret_key().unwrap().to_bech32().unwrap()
183 ),
184 );
185 Ok(())
186 }
187
188 #[test]
189 // ensures password encryption hasn't changed
190 fn decrypts_with_weak_password_from_reference_string() -> Result<()> {
191 let encryptor = Encryptor;
192 let decrypted_key = encryptor.decrypt_key(TEST_KEY_1_ENCRYPTED_WEAK, TEST_WEAK_PASSWORD)?;
193
194 assert_eq!(
195 format!(
196 "{}",
197 TEST_KEY_1_KEYS.secret_key().unwrap().to_bech32().unwrap()
198 ),
199 format!(
200 "{}",
201 decrypted_key.secret_key().unwrap().to_bech32().unwrap()
202 ),
203 );
204 Ok(())
205 }
206
207 #[test]
208 fn decrypts_key_encrypted_using_encrypt_key() -> Result<()> {
209 let encryptor = Encryptor;
210 let key = nostr::Keys::generate();
211 let s = encryptor.encrypt_key(&key, TEST_PASSWORD)?;
212 let newkey = encryptor.decrypt_key(s.as_str(), TEST_PASSWORD)?;
213
214 assert_eq!(
215 format!("{}", key.secret_key().unwrap().to_bech32().unwrap()),
216 format!("{}", newkey.secret_key().unwrap().to_bech32().unwrap()),
217 );
218 Ok(())
219 }
220
221 #[test]
222 fn decrypt_key_successfully_decrypts_key_encrypted_using_encrypt_key() -> Result<()> {
223 let encryptor = Encryptor;
224 let key = nostr::Keys::generate();
225 let s = encryptor.encrypt_key(&key, TEST_PASSWORD)?;
226 let newkey = encryptor.decrypt_key(s.as_str(), TEST_PASSWORD)?;
227
228 assert_eq!(
229 format!("{}", key.secret_key().unwrap().to_bech32().unwrap()),
230 format!("{}", newkey.secret_key().unwrap().to_bech32().unwrap()),
231 );
232 Ok(())
233 }
234
235 #[test]
236 fn password_to_key_returns_ok_with_standard_password() {
237 let salt = {
238 let mut salt: [u8; 16] = [0; 16];
239 OsRng.fill_bytes(&mut salt);
240 salt
241 };
242
243 let log2_rounds: u8 = 1;
244
245 assert!(password_to_key(TEST_PASSWORD, &salt, log2_rounds).is_ok());
246 }
247}
diff --git a/src/key_handling/mod.rs b/src/key_handling/mod.rs
index 913bd46..bcb10df 100644
--- a/src/key_handling/mod.rs
+++ b/src/key_handling/mod.rs
@@ -1 +1,2 @@
1pub mod encryption;
1pub mod users; 2pub mod users;
diff --git a/src/key_handling/users.rs b/src/key_handling/users.rs
index bd1748a..1d2cc34 100644
--- a/src/key_handling/users.rs
+++ b/src/key_handling/users.rs
@@ -1,47 +1,90 @@
1use anyhow::{Context, Result}; 1use anyhow::{Context, Result};
2use nostr::prelude::*;
3use zeroize::Zeroize;
2 4
5use super::encryption::{EncryptDecrypt, Encryptor};
3use crate::{ 6use crate::{
4 cli_interactor::{Interactor, InteractorPrompt, PromptInputParms}, 7 cli_interactor::{Interactor, InteractorPrompt, PromptInputParms, PromptPasswordParms},
5 config::{ConfigManagement, ConfigManager, MyConfig, UserRef}, 8 config::{self, ConfigManagement, ConfigManager},
6}; 9};
7 10
8#[derive(Default)] 11#[derive(Default)]
9pub struct UserManager { 12pub struct UserManager {
10 config_manager: ConfigManager, 13 config_manager: ConfigManager,
11 interactor: Interactor, 14 interactor: Interactor,
15 encryptor: Encryptor,
12} 16}
13 17
14pub trait UserManagement { 18pub trait UserManagement {
15 fn add(&self, nsec: &Option<String>) -> Result<()>; 19 fn add(&self, nsec: &Option<String>, password: &Option<String>) -> Result<nostr::Keys>;
16} 20}
17 21
18#[cfg(test)] 22#[cfg(test)]
19use duplicate::duplicate_item; 23use duplicate::duplicate_item;
20#[cfg_attr(test, duplicate_item(UserManager; [UserManager]; [self::tests::MockUserManager]))] 24#[cfg_attr(test, duplicate_item(UserManager; [UserManager]; [self::tests::MockUserManager]))]
21impl UserManagement for UserManager { 25impl UserManagement for UserManager {
22 fn add(&self, nsec: &Option<String>) -> Result<()> { 26 fn add(&self, nsec: &Option<String>, password: &Option<String>) -> Result<nostr::Keys> {
23 let nsec = match nsec.clone() { 27 let mut prompt = "login with nsec (or hex private key)";
24 Some(nsec) => nsec, 28 let keys = loop {
29 let pk = match nsec.clone() {
30 Some(nsec) => nsec,
31 None => self
32 .interactor
33 .input(PromptInputParms::default().with_prompt(prompt))
34 .context("failed to get nsec input from interactor")?,
35 };
36 match Keys::from_sk_str(&pk) {
37 Ok(key) => {
38 break key;
39 }
40 Err(e) => {
41 if nsec.is_some() {
42 return Err(e).context(
43 "invalid nsec - supplied parameter could not be converted into a nostr private key",
44 );
45 }
46 prompt = "invalid nsec. try again with nsec (or hex private key)";
47 }
48 }
49 };
50
51 let mut pass = match password.clone() {
52 Some(pass) => pass,
25 None => self 53 None => self
26 .interactor 54 .interactor
27 .input( 55 .password(
28 PromptInputParms::default().with_prompt("login with nsec (or hex private key)"), 56 PromptPasswordParms::default()
57 .with_prompt("encrypt with password")
58 .with_confirm(),
29 ) 59 )
30 .context("failed to get nsec input from interactor.input")?, 60 .context("failed to get password input from interactor.password")?,
31 }; 61 };
32 62
63 let encrypted_secret_key = self
64 .encryptor
65 .encrypt_key(&keys, &pass)
66 .context("failed to encrypt nsec with password.")?;
67 pass.zeroize();
68
69 let user_ref = config::UserRef {
70 public_key: keys.public_key(),
71 encrypted_key: encrypted_secret_key,
72 };
73
74 // remove any duplicate entries for key before adding it to config
75 let mut cfg = self.config_manager.load().context("failed to load application config to find and remove any old versions of the user's encrypted key")?;
76 cfg.users = cfg
77 .users
78 .clone()
79 .into_iter()
80 .filter(|r| !r.public_key.eq(&keys.public_key()))
81 .collect();
82 cfg.users.push(user_ref);
33 self.config_manager 83 self.config_manager
34 .save(&MyConfig { 84 .save(&cfg)
35 users: vec![UserRef {
36 nsec: nsec.to_string(),
37 }],
38 ..MyConfig::default()
39 })
40 .context("failed to save application configuration with new user details in")?; 85 .context("failed to save application configuration with new user details in")?;
41 86
42 println!("logged in as {nsec}"); 87 Ok(keys)
43
44 Ok(())
45 } 88 }
46} 89}
47 90
@@ -50,12 +93,17 @@ mod tests {
50 use test_utils::*; 93 use test_utils::*;
51 94
52 use super::*; 95 use super::*;
53 use crate::{cli_interactor::MockInteractorPrompt, config::MockConfigManagement}; 96 use crate::{
97 cli_interactor::MockInteractorPrompt,
98 config::{MockConfigManagement, MyConfig, UserRef},
99 key_handling::encryption::MockEncryptDecrypt,
100 };
54 101
55 #[derive(Default)] 102 #[derive(Default)]
56 pub struct MockUserManager { 103 pub struct MockUserManager {
57 pub config_manager: MockConfigManagement, 104 pub config_manager: MockConfigManagement,
58 pub interactor: MockInteractorPrompt, 105 pub interactor: MockInteractorPrompt,
106 pub encryptor: MockEncryptDecrypt,
59 } 107 }
60 108
61 mod add { 109 mod add {
@@ -70,28 +118,88 @@ mod tests {
70 self.interactor 118 self.interactor
71 .expect_input() 119 .expect_input()
72 .returning(|_| Ok(TEST_KEY_1_NSEC.into())); 120 .returning(|_| Ok(TEST_KEY_1_NSEC.into()));
121 self.interactor
122 .expect_password()
123 .returning(|_| Ok(TEST_PASSWORD.into()));
124 self.encryptor
125 .expect_encrypt_key()
126 .returning(|_, _| Ok(TEST_KEY_1_ENCRYPTED.into()));
73 self 127 self
74 } 128 }
75 } 129 }
76 130
77 mod when_nsec_is_passed { 131 fn reuable_user_isnt_prompted(nsec: &str) {
132 let mut m = MockUserManager::default().add_return_expected_responses();
133 m.interactor = MockInteractorPrompt::default();
134 m.interactor.expect_input().never();
135 m.interactor.expect_password().never();
136 let _ = m.add(&Some(nsec.into()), &Some(TEST_PASSWORD.to_string()));
137 }
138
139 fn reuable_config_isnt_modified(nsec: &str) {
140 let mut m = MockUserManager::default();
141 m.config_manager.expect_save().never();
142 let _ = m.add(&Some(nsec.into()), &Some(TEST_PASSWORD.to_string()));
143 }
144
145 mod when_valid_nsec_and_password_is_passed {
78 use super::*; 146 use super::*;
79 147
80 #[test] 148 #[test]
81 fn user_isnt_prompted() { 149 fn user_isnt_prompted() {
150 reuable_user_isnt_prompted(TEST_KEY_1_NSEC);
151 }
152
153 #[test]
154 fn results_in_correct_keys() {
82 let mut m = MockUserManager::default().add_return_expected_responses(); 155 let mut m = MockUserManager::default().add_return_expected_responses();
83 m.interactor = MockInteractorPrompt::default(); 156 m.interactor = MockInteractorPrompt::default();
84 m.interactor.expect_input().never(); 157 m.interactor.expect_input().never();
85 158 m.interactor.expect_password().never();
86 let _ = m.add(&Some(TEST_KEY_1_NSEC.into())); 159 let r = m.add(
160 &Some(TEST_KEY_1_NSEC.into()),
161 &Some(TEST_PASSWORD.to_string()),
162 );
163 assert!(r.is_ok(), "should result in keys");
164 assert!(
165 r.is_ok_and(|k| k
166 .secret_key()
167 .is_ok_and(|k| k.display_secret().to_string().eq(TEST_KEY_1_SK_HEX))),
168 "keys should reflect nsec"
169 );
87 } 170 }
88 } 171 }
172 mod when_invalid_nsec_is_passed_with_password {
173 use super::*;
89 174
175 #[test]
176 fn user_isnt_prompted() {
177 reuable_user_isnt_prompted(TEST_INVALID_NSEC);
178 }
179
180 #[test]
181 fn config_isnt_modified() {
182 reuable_config_isnt_modified(TEST_INVALID_NSEC);
183 }
184
185 #[test]
186 fn results_in_an_error() {
187 let m = MockUserManager::default();
188 assert!(
189 m.add(
190 &Some(TEST_INVALID_NSEC.into()),
191 &Some(TEST_PASSWORD.to_string())
192 )
193 .is_err(),
194 "should result in an error"
195 );
196 }
197 }
90 mod when_no_nsec_is_passed { 198 mod when_no_nsec_is_passed {
91 use super::*; 199 use super::*;
92 200
93 #[test] 201 #[test]
94 fn prompt_for_nsec() { 202 fn prompt_for_nsec_and_password() {
95 let mut m = MockUserManager::default().add_return_expected_responses(); 203 let mut m = MockUserManager::default().add_return_expected_responses();
96 204
97 m.interactor = MockInteractorPrompt::new(); 205 m.interactor = MockInteractorPrompt::new();
@@ -100,12 +208,31 @@ mod tests {
100 .once() 208 .once()
101 .withf(|p| p.prompt.eq("login with nsec (or hex private key)")) 209 .withf(|p| p.prompt.eq("login with nsec (or hex private key)"))
102 .returning(|_| Ok(TEST_KEY_1_NSEC.into())); 210 .returning(|_| Ok(TEST_KEY_1_NSEC.into()));
211 m.interactor
212 .expect_password()
213 .once()
214 .withf(|p| p.prompt.eq("encrypt with password"))
215 .returning(|_| Ok(TEST_KEY_1_NSEC.into()));
103 216
104 let _ = m.add(&None); 217 let _ = m.add(&None, &None);
105 } 218 }
106 219
107 #[test] 220 #[test]
108 fn stored_in_config() { 221 fn results_in_correct_keys() {
222 let m = MockUserManager::default().add_return_expected_responses();
223
224 let r = m.add(&None, &None);
225 assert!(r.is_ok(), "should result in keys");
226 assert!(
227 r.is_ok_and(|k| k
228 .secret_key()
229 .is_ok_and(|k| k.display_secret().to_string().eq(TEST_KEY_1_SK_HEX))),
230 "keys should reflect nsec"
231 );
232 }
233
234 #[test]
235 fn stores_encrypted_key_in_config() {
109 let mut m = MockUserManager::default().add_return_expected_responses(); 236 let mut m = MockUserManager::default().add_return_expected_responses();
110 237
111 m.config_manager = MockConfigManagement::new(); 238 m.config_manager = MockConfigManagement::new();
@@ -114,10 +241,91 @@ mod tests {
114 .returning(|| Ok(MyConfig::default())); 241 .returning(|| Ok(MyConfig::default()));
115 m.config_manager 242 m.config_manager
116 .expect_save() 243 .expect_save()
117 .withf(|cfg| cfg.users.len().eq(&1) && cfg.users[0].nsec.eq(TEST_KEY_1_NSEC)) 244 .withf(|cfg| {
245 cfg.users.len().eq(&1)
246 && cfg.users[0].encrypted_key.eq(TEST_KEY_1_ENCRYPTED)
247 })
118 .returning(|_| Ok(())); 248 .returning(|_| Ok(()));
119 249
120 let _ = m.add(&None); 250 let _ = m.add(&None, &None);
251 }
252
253 #[test]
254 fn stored_key_encrypted_with_password() {
255 let mut m = MockUserManager::default().add_return_expected_responses();
256
257 m.encryptor = MockEncryptDecrypt::new();
258 m.encryptor
259 .expect_encrypt_key()
260 .once()
261 .withf(|k, p| {
262 k.eq(&Keys::from_sk_str(TEST_KEY_1_NSEC).unwrap()) && p.eq(TEST_PASSWORD)
263 })
264 .returning(|_, _| Ok(TEST_KEY_1_ENCRYPTED.into()));
265
266 let _ = m.add(&None, &None);
267 }
268
269 mod when_user_key_already_stored {
270 use super::*;
271 use crate::config::UserRef;
272
273 /// key overwritten as password may have changed
274 #[test]
275 fn key_not_saved_as_duplicate_but_encrypted_key_overwritten() {
276 let mut m = MockUserManager::default().add_return_expected_responses();
277
278 m.config_manager = MockConfigManagement::default();
279 m.config_manager.expect_load().returning(|| {
280 Ok(MyConfig {
281 users: vec![UserRef {
282 public_key: TEST_KEY_1_KEYS.public_key(),
283 // different key to TEST_KEY_1_ENCYPTED
284 encrypted_key: TEST_KEY_2_ENCRYPTED.into(),
285 }],
286 ..MyConfig::default()
287 })
288 });
289 m.config_manager
290 .expect_save()
291 .withf(|cfg| {
292 cfg.users.len() == 1
293 && cfg.users[0].encrypted_key.eq(TEST_KEY_1_ENCRYPTED)
294 })
295 .returning(|_| Ok(()));
296
297 let _ = m.add(&None, &None);
298 }
299 }
300
301 mod when_multiple_users_added {
302 use super::*;
303
304 #[test]
305 fn both_user_keys_are_stored() {
306 let mut m = MockUserManager::default().add_return_expected_responses();
307
308 m.config_manager = MockConfigManagement::default();
309 m.config_manager.expect_load().returning(|| {
310 Ok(MyConfig {
311 users: vec![UserRef {
312 public_key: TEST_KEY_2_KEYS.public_key(),
313 encrypted_key: TEST_KEY_2_ENCRYPTED.into(),
314 }],
315 ..MyConfig::default()
316 })
317 });
318 m.config_manager
319 .expect_save()
320 .withf(|cfg| {
321 cfg.users.len() == 2
322 // latest user stored at end of array
323 && cfg.users[1].encrypted_key.eq(TEST_KEY_1_ENCRYPTED)
324 })
325 .returning(|_| Ok(()));
326
327 let _ = m.add(&None, &None);
328 }
121 } 329 }
122 } 330 }
123 } 331 }
diff --git a/src/login.rs b/src/login.rs
index da19a75..a6ce76d 100644
--- a/src/login.rs
+++ b/src/login.rs
@@ -1,16 +1,85 @@
1use anyhow::{Context, Result}; 1use anyhow::{bail, Context, Result};
2use nostr::prelude::{FromSkStr, ToBech32};
3use zeroize::Zeroize;
2 4
3use crate::{ 5use crate::{
6 cli_interactor::{Interactor, InteractorPrompt, PromptPasswordParms},
4 config::{ConfigManagement, ConfigManager}, 7 config::{ConfigManagement, ConfigManager},
5 key_handling::users::{UserManagement, UserManager}, 8 key_handling::{
9 encryption::{EncryptDecrypt, Encryptor},
10 users::{UserManagement, UserManager},
11 },
6}; 12};
7 13
8pub fn launch(nsec: &Option<String>) -> Result<()> { 14/// handles the encrpytion and storage of key material
15pub fn launch(nsec: &Option<String>, password: &Option<String>) -> Result<nostr::Keys> {
16 // if nsec parameter
17 if let Some(nsec_unwrapped) = nsec {
18 // get key or fail without prompts
19 let key = nostr::Keys::from_sk_str(nsec_unwrapped).context("invalid nsec parameter")?;
20 println!(
21 "logged in as {}",
22 &key.public_key()
23 .to_bech32()
24 .context("public key should always produce bech32")?
25 );
26
27 // if password, add user to enable password login in future
28 if password.is_some() {
29 UserManager::default()
30 .add(nsec, password)
31 .context("could not store identity")?;
32 }
33 return Ok(key);
34 }
35
36 // if encrypted nsec stored, attempt password
9 let cfg = ConfigManager 37 let cfg = ConfigManager
10 .load() 38 .load()
11 .context("failed to load application config")?; 39 .context("failed to load application config")?;
12 if !cfg.users.is_empty() { 40 let key = if let Some(user) = cfg.users.last() {
13 println!("logged in as {}", cfg.users[0].nsec); 41 let mut pass = if let Some(p) = password.clone() {
14 } 42 p
15 UserManager::default().add(nsec) 43 } else {
44 println!(
45 "login as {}",
46 &user
47 .public_key
48 .to_bech32()
49 .context("public key should always produce bech32")?
50 );
51 Interactor::default()
52 .password(PromptPasswordParms::default().with_prompt("password"))
53 .context("failed to get password input from interactor.password")?
54 };
55
56 let key_result = Encryptor
57 .decrypt_key(&user.encrypted_key, pass.as_str())
58 .context("failed to decrypt key with provided password");
59 pass.zeroize();
60
61 key_result.context(format!(
62 "failed to log in as {}",
63 &user
64 .public_key
65 .to_bech32()
66 .context("public key should always produce bech32")?
67 ))?
68 } else {
69 // no nsec but password supplied
70 if password.is_some() {
71 bail!("no nsec available to decrypt with specified password");
72 }
73 // otherwise add new user with nsec and password prompts
74 UserManager::default()
75 .add(nsec, password)
76 .context("failed to add user")?
77 };
78 println!(
79 "logged in as {}",
80 &key.public_key()
81 .to_bech32()
82 .context("public key should always produce bech32")?
83 );
84 Ok(key)
16} 85}
diff --git a/src/main.rs b/src/main.rs
index d16f1a3..e6eac32 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -17,8 +17,11 @@ pub struct Cli {
17 #[command(subcommand)] 17 #[command(subcommand)]
18 command: Commands, 18 command: Commands,
19 /// nsec or hex private key 19 /// nsec or hex private key
20 #[arg(short, long)] 20 #[arg(short, long, global = true)]
21 nsec: Option<String>, 21 nsec: Option<String>,
22 /// password to decrypt nsec
23 #[arg(short, long, global = true)]
24 password: Option<String>,
22} 25}
23 26
24#[derive(Subcommand)] 27#[derive(Subcommand)]
diff --git a/src/sub_commands/login.rs b/src/sub_commands/login.rs
index d61f578..5391024 100644
--- a/src/sub_commands/login.rs
+++ b/src/sub_commands/login.rs
@@ -7,5 +7,6 @@ use crate::{login, Cli};
7pub struct SubCommandArgs; 7pub struct SubCommandArgs;
8 8
9pub fn launch(args: &Cli, _command_args: &SubCommandArgs) -> Result<()> { 9pub fn launch(args: &Cli, _command_args: &SubCommandArgs) -> Result<()> {
10 login::launch(&args.nsec) 10 let _ = login::launch(&args.nsec, &args.password)?;
11 Ok(())
11} 12}
diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml
index e1f6090..1a39957 100644
--- a/test_utils/Cargo.toml
+++ b/test_utils/Cargo.toml
@@ -8,5 +8,7 @@ anyhow = "1.0.75"
8assert_cmd = "2.0.12" 8assert_cmd = "2.0.12"
9dialoguer = "0.10.4" 9dialoguer = "0.10.4"
10directories = "5.0.1" 10directories = "5.0.1"
11nostr = "0.23.0"
12once_cell = "1.18.0"
11rexpect = { git = "https://github.com/phaer/rexpect.git", branch= "skip-ansi-escape-codes" } 13rexpect = { git = "https://github.com/phaer/rexpect.git", branch= "skip-ansi-escape-codes" }
12strip-ansi-escapes = "0.2.0" 14strip-ansi-escapes = "0.2.0"
diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs
index 495e8d2..1a4231a 100644
--- a/test_utils/src/lib.rs
+++ b/test_utils/src/lib.rs
@@ -3,14 +3,38 @@ use std::ffi::OsStr;
3use anyhow::{ensure, Context, Result}; 3use anyhow::{ensure, Context, Result};
4use dialoguer::theme::{ColorfulTheme, Theme}; 4use dialoguer::theme::{ColorfulTheme, Theme};
5use directories::ProjectDirs; 5use directories::ProjectDirs;
6use nostr::{self, prelude::FromSkStr};
7use once_cell::sync::Lazy;
6use rexpect::session::{Options, PtySession}; 8use rexpect::session::{Options, PtySession};
7use strip_ansi_escapes::strip_str; 9use strip_ansi_escapes::strip_str;
8 10
9pub static TEST_KEY_1_NSEC: &str = 11pub static TEST_KEY_1_NSEC: &str =
10 "nsec1ppsg5sm2aexq06juxmu9evtutr6jkwkhp98exxxvwamhru9lyx9s3rwseq"; 12 "nsec1ppsg5sm2aexq06juxmu9evtutr6jkwkhp98exxxvwamhru9lyx9s3rwseq";
13pub static TEST_KEY_1_SK_HEX: &str =
14 "08608a436aee4c07ea5c36f85cb17c58f52b3ad7094f9318cc777771f0bf218b";
15pub static TEST_KEY_1_NPUB: &str =
16 "npub175lyhnt6nn00qjw0v3navw9pxgv43txnku0tpxprl4h6mvpr6a5qlphudg";
17pub static TEST_KEY_1_DISPLAY_NAME: &str = "bob";
18pub static TEST_KEY_1_ENCRYPTED: &str = "ncryptsec1qyq607h3cykxc3f2a44u89cdk336fptccn3fm5pf3nmf93d3c86qpunc7r6klwcn6lyszjy72wxwqq9aljg4pm6atvjrds9e248yhv76xfnt464265kgnjsvg8rlg06wg4sp9uljzfpu8zuaztcvfn2j8ggdrg8mldh850cy75efsyqqansert9wqmn4e6khpgvfz7h5le9";
19pub static TEST_KEY_1_ENCRYPTED_WEAK: &str = "ncryptsec1qy8ke0tjqnn8wt3w6lnc86c27ry3qrptxctjfcgruryxy0at238kwyjwsswd7z88thysruzw3awlrsxjvw5uptcd7vt70ft9rtkx00m8cgy3khm4hxa5d2gfnc6athnfruy2eyl6pkas8k34jg85z7xjqqadzfzh9rp0fzxqtw0tvxksac3n8yc98uksvuf93e0lcvqy8j6";
20pub static TEST_KEY_1_KEYS: Lazy<nostr::Keys> =
21 Lazy::new(|| nostr::Keys::from_sk_str(TEST_KEY_1_NSEC).unwrap());
11 22
12pub static TEST_KEY_2_NSEC: &str = 23pub static TEST_KEY_2_NSEC: &str =
13 "nsec1ypglg6nj6ep0g2qmyfqcv2al502gje3jvpwye6mthmkvj93tqkesknv6qm"; 24 "nsec1ypglg6nj6ep0g2qmyfqcv2al502gje3jvpwye6mthmkvj93tqkesknv6qm";
25pub static TEST_KEY_2_NPUB: &str =
26 "npub1h2yz2eh0798nh25hvypenrz995nla9dktfuk565ljf3ghnkhdljsul834e";
27
28pub static TEST_KEY_2_DISPLAY_NAME: &str = "carole";
29pub static TEST_KEY_2_ENCRYPTED: &str = "...2";
30pub static TEST_KEY_2_KEYS: Lazy<nostr::Keys> =
31 Lazy::new(|| nostr::Keys::from_sk_str(TEST_KEY_2_NSEC).unwrap());
32
33pub static TEST_INVALID_NSEC: &str = "nsec1ppsg5sm2aex";
34pub static TEST_PASSWORD: &str = "769dfd£pwega8SHGv3!#Bsfd5t";
35pub static TEST_INVALID_PASSWORD: &str = "INVALID769dfd£pwega8SHGv3!";
36pub static TEST_WEAK_PASSWORD: &str = "fhaiuhfwe";
37pub static TEST_RANDOM_TOKEN: &str = "lkjh2398HLKJ43hrweiJ6FaPfdssgtrg";
14 38
15/// wrapper for a cli testing tool - currently wraps rexpect and dialoguer 39/// wrapper for a cli testing tool - currently wraps rexpect and dialoguer
16/// 40///
@@ -41,6 +65,16 @@ impl CliTester {
41 i.prompt(true).context("initial input prompt")?; 65 i.prompt(true).context("initial input prompt")?;
42 Ok(i) 66 Ok(i)
43 } 67 }
68
69 pub fn expect_password(&mut self, prompt: &str) -> Result<CliTesterPasswordPrompt> {
70 let mut i = CliTesterPasswordPrompt {
71 tester: self,
72 prompt: prompt.to_string(),
73 confirmation_prompt: "".to_string(),
74 };
75 i.prompt().context("initial password prompt")?;
76 Ok(i)
77 }
44} 78}
45 79
46pub struct CliTesterInputPrompt<'a> { 80pub struct CliTesterInputPrompt<'a> {
@@ -101,6 +135,70 @@ impl CliTesterInputPrompt<'_> {
101 } 135 }
102} 136}
103 137
138pub struct CliTesterPasswordPrompt<'a> {
139 tester: &'a mut CliTester,
140 prompt: String,
141 confirmation_prompt: String,
142}
143
144impl CliTesterPasswordPrompt<'_> {
145 fn prompt(&mut self) -> Result<&mut Self> {
146 let p = match self.confirmation_prompt.is_empty() {
147 true => self.prompt.as_str(),
148 false => self.confirmation_prompt.as_str(),
149 };
150
151 let mut s = String::new();
152 self.tester
153 .formatter
154 .format_password_prompt(&mut s, p)
155 .expect("diagluer theme formatter should succeed");
156
157 ensure!(s.contains(p), "dialoguer must be broken");
158
159 self.tester
160 .expect(format!("\r{}", sanatize(s)).as_str())
161 .context("expect password input prompt")?;
162 Ok(self)
163 }
164
165 pub fn with_confirmation(&mut self, prompt: &str) -> Result<&mut Self> {
166 self.confirmation_prompt = prompt.to_string();
167 Ok(self)
168 }
169
170 pub fn succeeds_with(&mut self, password: &str) -> Result<&mut Self> {
171 self.tester.send_line(password)?;
172
173 self.tester
174 .expect("\r\n")
175 .context("expect new lines after password input")?;
176
177 if !self.confirmation_prompt.is_empty() {
178 self.prompt()
179 .context("expect password confirmation prompt")?;
180 self.tester.send_line(password)?;
181 self.tester
182 .expect("\r\n\r")
183 .context("expect new lines after password confirmation input")?;
184 }
185
186 let mut s = String::new();
187 self.tester
188 .formatter
189 .format_password_prompt_selection(&mut s, self.prompt.as_str())
190 .expect("diagluer theme formatter should succeed");
191
192 ensure!(s.contains(self.prompt.as_str()), "dialoguer must be broken");
193
194 self.tester
195 .expect(format!("\r{}\r\n", sanatize(s)).as_str())
196 .context("expect password prompt success")?;
197
198 Ok(self)
199 }
200}
201
104impl CliTester { 202impl CliTester {
105 pub fn new<I, S>(args: I) -> Self 203 pub fn new<I, S>(args: I) -> Self
106 where 204 where
@@ -108,7 +206,17 @@ impl CliTester {
108 S: AsRef<OsStr>, 206 S: AsRef<OsStr>,
109 { 207 {
110 Self { 208 Self {
111 rexpect_session: rexpect_with(args).expect("rexpect to spawn new process"), 209 rexpect_session: rexpect_with(args, 2000).expect("rexpect to spawn new process"),
210 formatter: ColorfulTheme::default(),
211 }
212 }
213 pub fn new_with_timeout<I, S>(timeout_ms: u64, args: I) -> Self
214 where
215 I: IntoIterator<Item = S>,
216 S: AsRef<OsStr>,
217 {
218 Self {
219 rexpect_session: rexpect_with(args, timeout_ms).expect("rexpect to spawn new process"),
112 formatter: ColorfulTheme::default(), 220 formatter: ColorfulTheme::default(),
113 } 221 }
114 } 222 }
@@ -122,7 +230,7 @@ impl CliTester {
122 .process 230 .process
123 .exit() 231 .exit()
124 .expect("process to exit"); 232 .expect("process to exit");
125 self.rexpect_session = rexpect_with(args).expect("rexpect to spawn new process"); 233 self.rexpect_session = rexpect_with(args, 2000).expect("rexpect to spawn new process");
126 self 234 self
127 } 235 }
128 236
@@ -213,7 +321,7 @@ fn sanatize(s: String) -> String {
213 .collect::<String>() 321 .collect::<String>()
214} 322}
215 323
216pub fn rexpect_with<I, S>(args: I) -> Result<PtySession, rexpect::error::Error> 324pub fn rexpect_with<I, S>(args: I, timeout_ms: u64) -> Result<PtySession, rexpect::error::Error>
217where 325where
218 I: IntoIterator<Item = S>, 326 I: IntoIterator<Item = S>,
219 S: AsRef<std::ffi::OsStr>, 327 S: AsRef<std::ffi::OsStr>,
@@ -224,7 +332,7 @@ where
224 rexpect::session::spawn_with_options( 332 rexpect::session::spawn_with_options(
225 cmd, 333 cmd,
226 Options { 334 Options {
227 timeout_ms: Some(2000), 335 timeout_ms: Some(timeout_ms),
228 strip_ansi_escape_codes: true, 336 strip_ansi_escape_codes: true,
229 }, 337 },
230 ) 338 )
diff --git a/tests/login.rs b/tests/login.rs
index a7e1889..a75608d 100644
--- a/tests/login.rs
+++ b/tests/login.rs
@@ -3,6 +3,9 @@ use serial_test::serial;
3use test_utils::*; 3use test_utils::*;
4 4
5static EXPECTED_NSEC_PROMPT: &str = "login with nsec (or hex private key)"; 5static EXPECTED_NSEC_PROMPT: &str = "login with nsec (or hex private key)";
6static EXPECTED_SET_PASSWORD_PROMPT: &str = "encrypt with password";
7static EXPECTED_SET_PASSWORD_CONFIRM_PROMPT: &str = "confirm password";
8static EXPECTED_PASSWORD_PROMPT: &str = "password";
6 9
7fn standard_login() -> Result<CliTester> { 10fn standard_login() -> Result<CliTester> {
8 let mut p = CliTester::new(["login"]); 11 let mut p = CliTester::new(["login"]);
@@ -10,6 +13,10 @@ fn standard_login() -> Result<CliTester> {
10 p.expect_input_eventually(EXPECTED_NSEC_PROMPT)? 13 p.expect_input_eventually(EXPECTED_NSEC_PROMPT)?
11 .succeeds_with(TEST_KEY_1_NSEC)?; 14 .succeeds_with(TEST_KEY_1_NSEC)?;
12 15
16 p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)?
17 .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)?
18 .succeeds_with(TEST_PASSWORD)?;
19
13 p.expect_end_eventually()?; 20 p.expect_end_eventually()?;
14 Ok(p) 21 Ok(p)
15} 22}
@@ -19,11 +26,10 @@ mod when_first_time_login {
19 26
20 #[test] 27 #[test]
21 #[serial] 28 #[serial]
22 fn prompts_for_nsec() -> Result<()> { 29 fn prompts_for_nsec_and_password() -> Result<()> {
23 with_fresh_config(|| { 30 before()?;
24 standard_login()?; 31 standard_login()?;
25 Ok(()) 32 after()
26 })
27 } 33 }
28 34
29 #[test] 35 #[test]
@@ -35,36 +41,137 @@ mod when_first_time_login {
35 p.expect_input(EXPECTED_NSEC_PROMPT)? 41 p.expect_input(EXPECTED_NSEC_PROMPT)?
36 .succeeds_with(TEST_KEY_1_NSEC)?; 42 .succeeds_with(TEST_KEY_1_NSEC)?;
37 43
38 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NSEC).as_str()) 44 p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)?
45 .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)?
46 .succeeds_with(TEST_PASSWORD)?;
47
48 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
39 }) 49 })
40 } 50 }
41 51
42 #[test] 52 #[test]
43 #[serial] 53 #[serial]
44 fn next_time_returns_logged_in_as_npub() -> Result<()> { 54 fn succeeds_with_hex_secret_key_in_place_of_nsec() -> Result<()> {
55 with_fresh_config(|| {
56 let mut p = CliTester::new(["login"]);
57
58 p.expect_input(EXPECTED_NSEC_PROMPT)?
59 .succeeds_with(TEST_KEY_1_SK_HEX)?;
60
61 p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)?
62 .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)?
63 .succeeds_with(TEST_PASSWORD)?;
64
65 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
66 })
67 }
68
69 mod when_invalid_nsec {
70 use super::*;
71
72 #[test]
73 #[serial]
74 fn prompts_for_nsec_until_valid() -> Result<()> {
75 with_fresh_config(|| {
76 let invalid_nsec_response =
77 "invalid nsec. try again with nsec (or hex private key)";
78
79 let mut p = CliTester::new(["login"]);
80
81 p.expect_input(EXPECTED_NSEC_PROMPT)?
82 // this behaviour is intentional. rejecting the response with dialoguer hides
83 // the original input from the user so they cannot see the
84 // mistake they made.
85 .succeeds_with(TEST_INVALID_NSEC)?;
86
87 p.expect_input(invalid_nsec_response)?
88 .succeeds_with(TEST_INVALID_NSEC)?;
89
90 p.expect_input(invalid_nsec_response)?
91 .succeeds_with(TEST_KEY_1_NSEC)?;
92
93 p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)?
94 .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)?
95 .succeeds_with(TEST_PASSWORD)?;
96
97 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
98 })
99 }
100 }
101}
102
103mod when_second_time_login {
104 use super::*;
105
106 #[test]
107 #[serial]
108 fn prints_login_as_npub() -> Result<()> {
45 with_fresh_config(|| { 109 with_fresh_config(|| {
46 standard_login()?.exit()?; 110 standard_login()?.exit()?;
47 111
48 CliTester::new(["login"]) 112 CliTester::new(["login"])
49 .expect(format!("logged in as {}\r\n", TEST_KEY_1_NSEC).as_str())? 113 .expect(format!("login as {}\r\n", TEST_KEY_1_NPUB).as_str())?
50 .exit() 114 .exit()
51 }) 115 })
52 } 116 }
117
118 #[test]
119 #[serial]
120 fn prompts_for_password_and_succeeds_with_logged_in_as_npub() -> Result<()> {
121 with_fresh_config(|| {
122 standard_login()?.exit()?;
123
124 let mut p = CliTester::new(["login"]);
125
126 p.expect(format!("login as {}\r\n", TEST_KEY_1_NPUB).as_str())?
127 .expect_password(EXPECTED_PASSWORD_PROMPT)?
128 .succeeds_with(TEST_PASSWORD)?;
129
130 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
131 })
132 }
133
134 #[test]
135 #[serial]
136 fn when_invalid_password_exit_with_error() -> Result<()> {
137 with_fresh_config(|| {
138 standard_login()?.exit()?;
139
140 let mut p = CliTester::new(["login"]);
141
142 p.expect(format!("login as {}\r\n", TEST_KEY_1_NPUB).as_str())?
143 .expect_password(EXPECTED_PASSWORD_PROMPT)?
144 .succeeds_with(TEST_INVALID_PASSWORD)?;
145 p.expect_end_with(format!("Error: failed to log in as {}\r\n\r\nCaused by:\r\n 0: failed to decrypt key with provided password\r\n 1: failed to decrypt\r\n", TEST_KEY_1_NPUB).as_str())
146 })
147 }
53} 148}
54 149
55mod when_called_with_nsec_parameter { 150mod when_called_with_nsec_parameter_only {
56 use super::*; 151 use super::*;
57 152
58 #[test] 153 #[test]
59 #[serial] 154 #[serial]
60 fn valid_nsec_param_succeeds_without_prompts() -> Result<()> { 155 fn valid_nsec_param_succeeds_without_prompts() -> Result<()> {
61 with_fresh_config(|| { 156 with_fresh_config(|| {
62 CliTester::new(["--nsec", TEST_KEY_2_NSEC, "login"]) 157 CliTester::new(["login", "--nsec", TEST_KEY_1_NSEC])
63 .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_2_NSEC).as_str())?; 158 .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
159 })
160 }
161
162 #[test]
163 #[serial]
164 fn forgets_identity() -> Result<()> {
165 with_fresh_config(|| {
166 CliTester::new(["login", "--nsec", TEST_KEY_1_NSEC])
167 .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())?;
64 168
65 CliTester::new(["login"]) 169 let mut p = CliTester::new(["login"]);
66 .expect(format!("logged in as {}\r\n", TEST_KEY_2_NSEC).as_str())? 170
67 .exit() 171 p.expect_input(EXPECTED_NSEC_PROMPT)?
172 .succeeds_with(TEST_KEY_1_NSEC)?;
173
174 p.exit()
68 }) 175 })
69 } 176 }
70 177
@@ -77,69 +184,212 @@ mod when_called_with_nsec_parameter {
77 with_fresh_config(|| { 184 with_fresh_config(|| {
78 standard_login()?.exit()?; 185 standard_login()?.exit()?;
79 186
80 CliTester::new(["--nsec", TEST_KEY_2_NSEC, "login"]) 187 CliTester::new(["login", "--nsec", TEST_KEY_2_NSEC])
81 .expect(format!("logged in as {}\r\n", TEST_KEY_1_NSEC).as_str())? 188 .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_2_NPUB).as_str())
82 .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_2_NSEC).as_str())?;
83
84 CliTester::new(["login"])
85 .expect(format!("logged in as {}\r\n", TEST_KEY_2_NSEC).as_str())?
86 .exit()
87 }) 189 })
88 } 190 }
89 } 191 }
192 #[test]
193 #[serial]
194 fn invalid_nsec_param_fails_without_prompts() -> Result<()> {
195 with_fresh_config(|| {
196 CliTester::new(["login", "--nsec", TEST_INVALID_NSEC]).expect_end_with(
197 "Error: invalid nsec parameter\r\n\r\nCaused by:\r\n Invalid secret key\r\n",
198 )
199 })
200 }
90} 201}
91 202
92mod when_logged_in { 203mod when_called_with_nsec_and_password_parameter {
93 use super::*; 204 use super::*;
94 205
95 #[test] 206 #[test]
96 #[serial] 207 #[serial]
97 fn returns_logged_in_as_npub() -> Result<()> { 208 fn valid_nsec_param_succeeds_without_prompts() -> Result<()> {
98 with_fresh_config(|| { 209 with_fresh_config(|| {
99 standard_login()?.exit()?; 210 CliTester::new([
211 "login",
212 "--nsec",
213 TEST_KEY_1_NSEC,
214 "--password",
215 TEST_PASSWORD,
216 ])
217 .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
218 })
219 }
220
221 #[test]
222 #[serial]
223 fn remembers_identity() -> Result<()> {
224 with_fresh_config(|| {
225 CliTester::new([
226 "login",
227 "--nsec",
228 TEST_KEY_1_NSEC,
229 "--password",
230 TEST_PASSWORD,
231 ])
232 .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())?;
100 233
101 CliTester::new(["login"]) 234 CliTester::new(["login"])
102 .expect(format!("logged in as {}\r\n", TEST_KEY_1_NSEC).as_str())? 235 .expect(format!("login as {}\r\n", TEST_KEY_1_NPUB).as_str())?
103 .exit() 236 .exit()
104 }) 237 })
105 } 238 }
106 239
107 #[test] 240 #[test]
108 #[serial] 241 #[serial]
109 fn prompts_to_log_in_with_different_nsec() -> Result<()> { 242 fn parameters_can_be_called_globally() -> Result<()> {
110 with_fresh_config(|| { 243 with_fresh_config(|| {
111 standard_login()?.exit()?; 244 CliTester::new([
112 245 "--nsec",
113 let mut p = CliTester::new(["login"]); 246 TEST_KEY_1_NSEC,
114 p.expect(format!("logged in as {}\r\n", TEST_KEY_1_NSEC).as_str())?; 247 "--password",
115 248 TEST_PASSWORD,
116 p.expect_input(EXPECTED_NSEC_PROMPT)? 249 "login",
117 .succeeds_with(TEST_KEY_2_NSEC)?; 250 ])
118 251 .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
119 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_2_NSEC).as_str())
120 }) 252 })
121 } 253 }
254
122 mod when_logging_in_as_different_nsec { 255 mod when_logging_in_as_different_nsec {
123 use super::*; 256 use super::*;
124 257
125 #[test] 258 #[test]
126 #[serial] 259 #[serial]
127 fn confirmed_as_logged_in_as_additional_user() -> Result<()> { 260 fn valid_nsec_param_succeeds_without_prompts_and_logs_in() -> Result<()> {
128 with_fresh_config(|| { 261 with_fresh_config(|| {
129 standard_login()?.exit()?; 262 standard_login()?.exit()?;
130 263
131 let mut p = CliTester::new(["login"]); 264 CliTester::new([
132 p.expect(format!("logged in as {}\r\n", TEST_KEY_1_NSEC).as_str())?; 265 "login",
266 "--nsec",
267 TEST_KEY_2_NSEC,
268 "--password",
269 TEST_PASSWORD,
270 ])
271 .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_2_NPUB).as_str())
272 })
273 }
133 274
134 p.expect_input(EXPECTED_NSEC_PROMPT)? 275 #[test]
135 .succeeds_with(TEST_KEY_2_NSEC)?; 276 #[serial]
277 fn remembers_identity() -> Result<()> {
278 with_fresh_config(|| {
279 standard_login()?.exit()?;
136 280
137 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_2_NSEC).as_str())?; 281 CliTester::new([
282 "login",
283 "--nsec",
284 TEST_KEY_2_NSEC,
285 "--password",
286 TEST_PASSWORD,
287 ])
288 .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_2_NPUB).as_str())?;
138 289
139 CliTester::new(["login"]) 290 CliTester::new(["login"])
140 .expect(format!("logged in as {}\r\n", TEST_KEY_2_NSEC).as_str())? 291 .expect(format!("login as {}\r\n", TEST_KEY_2_NPUB).as_str())?
141 .exit() 292 .exit()
142 }) 293 })
143 } 294 }
144 } 295 }
296
297 mod when_provided_with_new_password {
298 use super::*;
299
300 #[test]
301 #[serial]
302 fn password_changes() -> Result<()> {
303 with_fresh_config(|| {
304 standard_login()?.exit()?;
305
306 CliTester::new([
307 "login",
308 "--nsec",
309 TEST_KEY_1_NSEC,
310 "--password",
311 TEST_INVALID_PASSWORD,
312 ])
313 .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())?;
314
315 CliTester::new(["--password", TEST_INVALID_PASSWORD, "login"])
316 .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
317 })
318 }
319 }
320
321 #[test]
322 #[serial]
323 fn invalid_nsec_param_fails_without_prompts() -> Result<()> {
324 with_fresh_config(|| {
325 CliTester::new([
326 "login",
327 "--nsec",
328 TEST_INVALID_NSEC,
329 "--password",
330 TEST_PASSWORD,
331 ])
332 .expect_end_with(
333 "Error: invalid nsec parameter\r\n\r\nCaused by:\r\n Invalid secret key\r\n",
334 )
335 })
336 }
337}
338
339mod when_called_with_password_parameter_only {
340 use super::*;
341
342 #[test]
343 #[serial]
344 fn when_nsec_stored_logs_in_without_prompts() -> Result<()> {
345 with_fresh_config(|| {
346 standard_login()?.exit()?;
347
348 CliTester::new(["login", "--password", TEST_PASSWORD])
349 .expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
350 })
351 }
352
353 #[test]
354 #[serial]
355 fn when_no_nsec_stored_logs_error() -> Result<()> {
356 with_fresh_config(|| {
357 CliTester::new(["login", "--password", TEST_PASSWORD])
358 .expect_end_with("Error: no nsec available to decrypt with specified password\r\n")
359 })
360 }
361}
362
363mod when_weak_password {
364 use super::*;
365
366 #[test]
367 #[serial]
368 // combined into a single test as it is computationally expensive to run
369 fn warns_it_might_take_a_few_seconds_then_succeeds_then_second_login_prompts_for_password_then_warns_again_then_succeeds()
370 -> Result<()> {
371 with_fresh_config(|| {
372 let mut p = CliTester::new_with_timeout(10000, ["login"]);
373 p.expect_input(EXPECTED_NSEC_PROMPT)?
374 .succeeds_with(TEST_KEY_1_NSEC)?;
375
376 p.expect_password(EXPECTED_SET_PASSWORD_PROMPT)?
377 .with_confirmation(EXPECTED_SET_PASSWORD_CONFIRM_PROMPT)?
378 .succeeds_with(TEST_WEAK_PASSWORD)?;
379
380 p.expect("this may take a few seconds...\r\n")?;
381
382 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())?;
383
384 p = CliTester::new_with_timeout(10000, ["login"]);
385
386 p.expect(format!("login as {}\r\n", TEST_KEY_1_NPUB).as_str())?
387 .expect_password(EXPECTED_PASSWORD_PROMPT)?
388 .succeeds_with(TEST_WEAK_PASSWORD)?;
389
390 p.expect("this may take a few seconds...\r\n")?;
391
392 p.expect_end_with(format!("logged in as {}\r\n", TEST_KEY_1_NPUB).as_str())
393 })
394 }
145} 395}