fabrica

hanna/fabrica

feat(ssh): russh server — publickey auth and exec pack dispatch

79ec208 · hanna committed on 2026-07-25

Add the SSH front-end. A russh 0.62 server accepts only publickey auth
(identity by the offered key's SHA256 fingerprint, matched against the
keys table; last_used_at bumped), handles only the exec channel, and
streams git-upload-pack/receive-pack over git::pack via the async Handle.
Read needs Read access, push needs Write. A shell request (or any other
command) gets the friendly banner and exit 1; an unregistered key is
rejected.

The host key (ed25519) is generated at ssh.host_key_path on first start
(mode 0600) and bridged to russh's ssh-key version via the OpenSSH PEM.
The root binary runs the SSH server alongside web. Pushes set FABRICA_*
identity + FABRICA_CONFIG so post-receive updates pushed_at.

Verified end-to-end: SSH clone, SSH push (hook fires), the shell banner,
and unregistered-key rejection. russh's aws-lc-rs backend requires the
OpenSSL license and cmake — admitted for the mandatory SSH crypto only
(see docs/decisions.md).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed by hannaSSH key fingerprint: SHA256:4g9cWhkAAw8gwqhJUcVbSnGEvdGwklW+1Aa/fMUu59k
14 files changed · +2046 −152UnifiedSplit
Cargo.lock +1336 −146
@@ -16,7 +16,17 @@ checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
16dependencies = [16dependencies = [
17 "bytes",17 "bytes",
18 "crypto-common 0.1.6",18 "crypto-common 0.1.6",
19 "generic-array",19 "generic-array 0.14.9",
20]
21
22[[package]]
23name = "aead"
24version = "0.6.1"
25source = "registry+https://github.com/rust-lang/crates.io-index"
26checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99"
27dependencies = [
28 "crypto-common 0.2.2",
29 "inout 0.2.2",
20]30]
2131
22[[package]]32[[package]]
@@ -26,22 +36,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
26checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"36checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
27dependencies = [37dependencies = [
28 "cfg-if",38 "cfg-if",
29 "cipher",39 "cipher 0.4.4",
30 "cpufeatures 0.2.17",40 "cpufeatures 0.2.17",
31]41]
3242
33[[package]]43[[package]]
44name = "aes"
45version = "0.9.1"
46source = "registry+https://github.com/rust-lang/crates.io-index"
47checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138"
48dependencies = [
49 "cipher 0.5.2",
50 "cpubits",
51 "cpufeatures 0.3.0",
52 "zeroize",
53]
54
55[[package]]
34name = "aes-gcm"56name = "aes-gcm"
35version = "0.10.3"57version = "0.10.3"
36source = "registry+https://github.com/rust-lang/crates.io-index"58source = "registry+https://github.com/rust-lang/crates.io-index"
37checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"59checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
38dependencies = [60dependencies = [
39 "aead",61 "aead 0.5.2",
40 "aes",62 "aes 0.8.4",
41 "cipher",63 "cipher 0.4.4",
42 "ctr",64 "ctr 0.9.2",
43 "ghash",65 "ghash 0.5.1",
66 "subtle",
67]
68
69[[package]]
70name = "aes-gcm"
71version = "0.11.0"
72source = "registry+https://github.com/rust-lang/crates.io-index"
73checksum = "fdf011db2e21ce0d575593d749db5554b47fed37aff429e4dc50bc91ac93a028"
74dependencies = [
75 "aead 0.6.1",
76 "aes 0.9.1",
77 "cipher 0.5.2",
78 "ctr 0.10.1",
79 "ghash 0.6.0",
44 "subtle",80 "subtle",
81 "zeroize",
45]82]
4683
47[[package]]84[[package]]
@@ -50,7 +87,7 @@ version = "0.2.1"
50source = "registry+https://github.com/rust-lang/crates.io-index"87source = "registry+https://github.com/rust-lang/crates.io-index"
51checksum = "69fa2b352dcefb5f7f3a5fb840e02665d311d878955380515e4fd50095dd3d8c"88checksum = "69fa2b352dcefb5f7f3a5fb840e02665d311d878955380515e4fd50095dd3d8c"
52dependencies = [89dependencies = [
53 "aes",90 "aes 0.8.4",
54]91]
5592
56[[package]]93[[package]]
@@ -94,6 +131,15 @@ dependencies = [
94]131]
95132
96[[package]]133[[package]]
134name = "android_system_properties"
135version = "0.1.5"
136source = "registry+https://github.com/rust-lang/crates.io-index"
137checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
138dependencies = [
139 "libc",
140]
141
142[[package]]
97name = "anstream"143name = "anstream"
98version = "1.0.0"144version = "1.0.0"
99source = "registry+https://github.com/rust-lang/crates.io-index"145source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -160,13 +206,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
160checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"206checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
161dependencies = [207dependencies = [
162 "base64ct",208 "base64ct",
163 "blake2",209 "blake2 0.10.6",
164 "cpufeatures 0.2.17",210 "cpufeatures 0.2.17",
165 "password-hash",211 "password-hash 0.5.0",
166 "zeroize",212 "zeroize",
167]213]
168214
169[[package]]215[[package]]
216name = "argon2"
217version = "0.6.0-rc.8"
218source = "registry+https://github.com/rust-lang/crates.io-index"
219checksum = "7af50940b73bf4e16c15c448a2b121c63f2d68e3e54b6a8731673cb4aa0cdff5"
220dependencies = [
221 "base64ct",
222 "blake2 0.11.0-rc.6",
223 "cpufeatures 0.3.0",
224 "password-hash 0.6.1",
225]
226
227[[package]]
170name = "async-compression"228name = "async-compression"
171version = "0.4.42"229version = "0.4.42"
172source = "registry+https://github.com/rust-lang/crates.io-index"230source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -217,7 +275,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
217name = "auth"275name = "auth"
218version = "0.1.0"276version = "0.1.0"
219dependencies = [277dependencies = [
220 "argon2",278 "argon2 0.5.3",
221 "base64",279 "base64",
222 "hmac 0.12.1",280 "hmac 0.12.1",
223 "model",281 "model",
@@ -235,6 +293,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
235checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"293checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
236294
237[[package]]295[[package]]
296name = "aws-lc-rs"
297version = "1.17.3"
298source = "registry+https://github.com/rust-lang/crates.io-index"
299checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1"
300dependencies = [
301 "aws-lc-sys",
302 "untrusted",
303 "zeroize",
304]
305
306[[package]]
307name = "aws-lc-sys"
308version = "0.43.0"
309source = "registry+https://github.com/rust-lang/crates.io-index"
310checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c"
311dependencies = [
312 "cc",
313 "cmake",
314 "dunce",
315 "fs_extra",
316 "pkg-config",
317]
318
319[[package]]
238name = "axum"320name = "axum"
239version = "0.8.9"321version = "0.8.9"
240source = "registry+https://github.com/rust-lang/crates.io-index"322source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -315,6 +397,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
315checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"397checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
316398
317[[package]]399[[package]]
400name = "base16ct"
401version = "1.0.0"
402source = "registry+https://github.com/rust-lang/crates.io-index"
403checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6"
404
405[[package]]
318name = "base64"406name = "base64"
319version = "0.22.1"407version = "0.22.1"
320source = "registry+https://github.com/rust-lang/crates.io-index"408source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -327,6 +415,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
327checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"415checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
328416
329[[package]]417[[package]]
418name = "bcrypt-pbkdf"
419version = "0.11.0"
420source = "registry+https://github.com/rust-lang/crates.io-index"
421checksum = "144e573728da132683b9488acd528274c790e07fc06ff81ee29f9d8f8b1041e0"
422dependencies = [
423 "blowfish 0.10.0",
424 "pbkdf2",
425 "sha2 0.11.0",
426]
427
428[[package]]
330name = "bitfields"429name = "bitfields"
331version = "1.0.3"430version = "1.0.3"
332source = "registry+https://github.com/rust-lang/crates.io-index"431source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -378,12 +477,21 @@ dependencies = [
378]477]
379478
380[[package]]479[[package]]
480name = "blake2"
481version = "0.11.0-rc.6"
482source = "registry+https://github.com/rust-lang/crates.io-index"
483checksum = "061f1a09225e328e1ffbb378d2d49923c0ca5fee19fb5ac1cc9c1e9d52b93690"
484dependencies = [
485 "digest 0.11.3",
486]
487
488[[package]]
381name = "block-buffer"489name = "block-buffer"
382version = "0.10.4"490version = "0.10.4"
383source = "registry+https://github.com/rust-lang/crates.io-index"491source = "registry+https://github.com/rust-lang/crates.io-index"
384checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"492checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
385dependencies = [493dependencies = [
386 "generic-array",494 "generic-array 0.14.9",
387]495]
388496
389[[package]]497[[package]]
@@ -393,6 +501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
393checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"501checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
394dependencies = [502dependencies = [
395 "hybrid-array",503 "hybrid-array",
504 "zeroize",
396]505]
397506
398[[package]]507[[package]]
@@ -401,7 +510,16 @@ version = "0.3.3"
401source = "registry+https://github.com/rust-lang/crates.io-index"510source = "registry+https://github.com/rust-lang/crates.io-index"
402checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"511checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
403dependencies = [512dependencies = [
404 "generic-array",513 "generic-array 0.14.9",
514]
515
516[[package]]
517name = "block-padding"
518version = "0.4.2"
519source = "registry+https://github.com/rust-lang/crates.io-index"
520checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b"
521dependencies = [
522 "hybrid-array",
405]523]
406524
407[[package]]525[[package]]
@@ -411,7 +529,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
411checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"529checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
412dependencies = [530dependencies = [
413 "byteorder",531 "byteorder",
414 "cipher",532 "cipher 0.4.4",
533]
534
535[[package]]
536name = "blowfish"
537version = "0.10.0"
538source = "registry+https://github.com/rust-lang/crates.io-index"
539checksum = "62ce3946557b35e71d1bbe07ec385073ce9eda05043f95de134eb578fcf1a298"
540dependencies = [
541 "byteorder",
542 "cipher 0.5.2",
415]543]
416544
417[[package]]545[[package]]
@@ -473,7 +601,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
473checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30"601checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30"
474dependencies = [602dependencies = [
475 "byteorder",603 "byteorder",
476 "cipher",604 "cipher 0.4.4",
477]605]
478606
479[[package]]607[[package]]
@@ -491,7 +619,16 @@ version = "0.11.1"
491source = "registry+https://github.com/rust-lang/crates.io-index"619source = "registry+https://github.com/rust-lang/crates.io-index"
492checksum = "26b07d673db1ccf000e90f54b819db9e75a8348d6eb056e9b8ab53231b7a9911"620checksum = "26b07d673db1ccf000e90f54b819db9e75a8348d6eb056e9b8ab53231b7a9911"
493dependencies = [621dependencies = [
494 "cipher",622 "cipher 0.4.4",
623]
624
625[[package]]
626name = "cbc"
627version = "0.2.1"
628source = "registry+https://github.com/rust-lang/crates.io-index"
629checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896"
630dependencies = [
631 "cipher 0.5.2",
495]632]
496633
497[[package]]634[[package]]
@@ -512,7 +649,7 @@ version = "0.8.2"
512source = "registry+https://github.com/rust-lang/crates.io-index"649source = "registry+https://github.com/rust-lang/crates.io-index"
513checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330"650checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330"
514dependencies = [651dependencies = [
515 "cipher",652 "cipher 0.4.4",
516]653]
517654
518[[package]]655[[package]]
@@ -522,14 +659,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
522checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"659checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
523660
524[[package]]661[[package]]
662name = "cfg_aliases"
663version = "0.2.2"
664source = "registry+https://github.com/rust-lang/crates.io-index"
665checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
666
667[[package]]
525name = "chacha20"668name = "chacha20"
526version = "0.10.1"669version = "0.10.1"
527source = "registry+https://github.com/rust-lang/crates.io-index"670source = "registry+https://github.com/rust-lang/crates.io-index"
528checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"671checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
529dependencies = [672dependencies = [
530 "cfg-if",673 "cfg-if",
674 "cipher 0.5.2",
531 "cpufeatures 0.3.0",675 "cpufeatures 0.3.0",
532 "rand_core 0.10.1",676 "rand_core 0.10.1",
677 "zeroize",
678]
679
680[[package]]
681name = "chrono"
682version = "0.4.45"
683source = "registry+https://github.com/rust-lang/crates.io-index"
684checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
685dependencies = [
686 "iana-time-zone",
687 "js-sys",
688 "num-traits",
689 "wasm-bindgen",
690 "windows-link",
533]691]
534692
535[[package]]693[[package]]
@@ -539,7 +697,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
539checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"697checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
540dependencies = [698dependencies = [
541 "crypto-common 0.1.6",699 "crypto-common 0.1.6",
542 "inout",700 "inout 0.1.4",
701]
702
703[[package]]
704name = "cipher"
705version = "0.5.2"
706source = "registry+https://github.com/rust-lang/crates.io-index"
707checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
708dependencies = [
709 "block-buffer 0.12.1",
710 "crypto-common 0.2.2",
711 "inout 0.2.2",
712 "zeroize",
543]713]
544714
545[[package]]715[[package]]
@@ -607,12 +777,21 @@ version = "0.7.2"
607source = "registry+https://github.com/rust-lang/crates.io-index"777source = "registry+https://github.com/rust-lang/crates.io-index"
608checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa"778checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa"
609dependencies = [779dependencies = [
610 "cipher",780 "cipher 0.4.4",
611 "dbl",781 "dbl",
612 "digest 0.10.7",782 "digest 0.10.7",
613]783]
614784
615[[package]]785[[package]]
786name = "cmake"
787version = "0.1.58"
788source = "registry+https://github.com/rust-lang/crates.io-index"
789checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
790dependencies = [
791 "cc",
792]
793
794[[package]]
616name = "cmov"795name = "cmov"
617version = "0.5.4"796version = "0.5.4"
618source = "registry+https://github.com/rust-lang/crates.io-index"797source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -738,6 +917,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
738checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"917checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
739918
740[[package]]919[[package]]
920name = "cpubits"
921version = "0.1.1"
922source = "registry+https://github.com/rust-lang/crates.io-index"
923checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
924
925[[package]]
741name = "cpufeatures"926name = "cpufeatures"
742version = "0.2.17"927version = "0.2.17"
743source = "registry+https://github.com/rust-lang/crates.io-index"928source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -824,19 +1009,36 @@ version = "0.5.5"
824source = "registry+https://github.com/rust-lang/crates.io-index"1009source = "registry+https://github.com/rust-lang/crates.io-index"
825checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"1010checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
826dependencies = [1011dependencies = [
827 "generic-array",1012 "generic-array 0.14.9",
828 "rand_core 0.6.4",1013 "rand_core 0.6.4",
829 "subtle",1014 "subtle",
830 "zeroize",1015 "zeroize",
831]1016]
8321017
833[[package]]1018[[package]]
1019name = "crypto-bigint"
1020version = "0.7.5"
1021source = "registry+https://github.com/rust-lang/crates.io-index"
1022checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271"
1023dependencies = [
1024 "cpubits",
1025 "ctutils",
1026 "getrandom 0.4.3",
1027 "hybrid-array",
1028 "num-traits",
1029 "rand_core 0.10.1",
1030 "serdect 0.4.3",
1031 "subtle",
1032 "zeroize",
1033]
1034
1035[[package]]
834name = "crypto-common"1036name = "crypto-common"
835version = "0.1.6"1037version = "0.1.6"
836source = "registry+https://github.com/rust-lang/crates.io-index"1038source = "registry+https://github.com/rust-lang/crates.io-index"
837checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"1039checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
838dependencies = [1040dependencies = [
839 "generic-array",1041 "generic-array 0.14.9",
840 "rand_core 0.6.4",1042 "rand_core 0.6.4",
841 "typenum",1043 "typenum",
842]1044]
@@ -847,7 +1049,19 @@ version = "0.2.2"
847source = "registry+https://github.com/rust-lang/crates.io-index"1049source = "registry+https://github.com/rust-lang/crates.io-index"
848checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"1050checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
849dependencies = [1051dependencies = [
1052 "getrandom 0.4.3",
850 "hybrid-array",1053 "hybrid-array",
1054 "rand_core 0.10.1",
1055]
1056
1057[[package]]
1058name = "crypto-primes"
1059version = "0.7.2"
1060source = "registry+https://github.com/rust-lang/crates.io-index"
1061checksum = "3633a51a39c69ebbaa4feaa694bd83d241e4093901c84a0963b19d9bb3f0cf8f"
1062dependencies = [
1063 "crypto-bigint 0.7.5",
1064 "rand_core 0.10.1",
851]1065]
8521066
853[[package]]1067[[package]]
@@ -867,7 +1081,16 @@ version = "0.9.2"
867source = "registry+https://github.com/rust-lang/crates.io-index"1081source = "registry+https://github.com/rust-lang/crates.io-index"
868checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"1082checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
869dependencies = [1083dependencies = [
870 "cipher",1084 "cipher 0.4.4",
1085]
1086
1087[[package]]
1088name = "ctr"
1089version = "0.10.1"
1090source = "registry+https://github.com/rust-lang/crates.io-index"
1091checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21"
1092dependencies = [
1093 "cipher 0.5.2",
871]1094]
8721095
873[[package]]1096[[package]]
@@ -877,6 +1100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
877checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"1100checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
878dependencies = [1101dependencies = [
879 "cmov",1102 "cmov",
1103 "subtle",
880]1104]
8811105
882[[package]]1106[[package]]
@@ -889,7 +1113,24 @@ dependencies = [
889 "cpufeatures 0.2.17",1113 "cpufeatures 0.2.17",
890 "curve25519-dalek-derive",1114 "curve25519-dalek-derive",
891 "digest 0.10.7",1115 "digest 0.10.7",
892 "fiat-crypto",1116 "fiat-crypto 0.2.9",
1117 "rustc_version",
1118 "subtle",
1119 "zeroize",
1120]
1121
1122[[package]]
1123name = "curve25519-dalek"
1124version = "5.0.0"
1125source = "registry+https://github.com/rust-lang/crates.io-index"
1126checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23"
1127dependencies = [
1128 "cfg-if",
1129 "cpufeatures 0.3.0",
1130 "curve25519-dalek-derive",
1131 "digest 0.11.3",
1132 "fiat-crypto 0.3.0",
1133 "rand_core 0.10.1",
893 "rustc_version",1134 "rustc_version",
894 "subtle",1135 "subtle",
895 "zeroize",1136 "zeroize",
@@ -912,13 +1153,13 @@ version = "0.1.1"
912source = "registry+https://github.com/rust-lang/crates.io-index"1153source = "registry+https://github.com/rust-lang/crates.io-index"
913checksum = "b4c0cf476284b03eb6c10e78787b21c7abb7d7d43cb2f02532ba6b831ed892fa"1154checksum = "b4c0cf476284b03eb6c10e78787b21c7abb7d7d43cb2f02532ba6b831ed892fa"
914dependencies = [1155dependencies = [
915 "crypto-bigint",1156 "crypto-bigint 0.5.5",
916 "elliptic-curve",1157 "elliptic-curve 0.13.8",
917 "pkcs8",1158 "pkcs8 0.10.2",
918 "rand_core 0.6.4",1159 "rand_core 0.6.4",
919 "serdect 0.3.0",1160 "serdect 0.3.0",
920 "sha3",1161 "sha3 0.10.9",
921 "signature",1162 "signature 2.2.0",
922 "subtle",1163 "subtle",
923 "zeroize",1164 "zeroize",
924]1165]
@@ -959,12 +1200,29 @@ dependencies = [
959]1200]
9601201
961[[package]]1202[[package]]
1203name = "data-encoding"
1204version = "2.11.0"
1205source = "registry+https://github.com/rust-lang/crates.io-index"
1206checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
1207
1208[[package]]
962name = "dbl"1209name = "dbl"
963version = "0.3.2"1210version = "0.3.2"
964source = "registry+https://github.com/rust-lang/crates.io-index"1211source = "registry+https://github.com/rust-lang/crates.io-index"
965checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9"1212checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9"
966dependencies = [1213dependencies = [
967 "generic-array",1214 "generic-array 0.14.9",
1215]
1216
1217[[package]]
1218name = "delegate"
1219version = "0.13.5"
1220source = "registry+https://github.com/rust-lang/crates.io-index"
1221checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370"
1222dependencies = [
1223 "proc-macro2",
1224 "quote",
1225 "syn 2.0.119",
968]1226]
9691227
970[[package]]1228[[package]]
@@ -974,7 +1232,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
974checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"1232checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
975dependencies = [1233dependencies = [
976 "const-oid 0.9.6",1234 "const-oid 0.9.6",
977 "pem-rfc7468",1235 "pem-rfc7468 0.7.0",
1236 "zeroize",
1237]
1238
1239[[package]]
1240name = "der"
1241version = "0.8.1"
1242source = "registry+https://github.com/rust-lang/crates.io-index"
1243checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d"
1244dependencies = [
1245 "const-oid 0.10.2",
1246 "pem-rfc7468 1.0.0",
978 "zeroize",1247 "zeroize",
979]1248]
9801249
@@ -1047,7 +1316,16 @@ version = "0.8.1"
1047source = "registry+https://github.com/rust-lang/crates.io-index"1316source = "registry+https://github.com/rust-lang/crates.io-index"
1048checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e"1317checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e"
1049dependencies = [1318dependencies = [
1050 "cipher",1319 "cipher 0.4.4",
1320]
1321
1322[[package]]
1323name = "des"
1324version = "0.9.0"
1325source = "registry+https://github.com/rust-lang/crates.io-index"
1326checksum = "916a94e407b54f9034d71dd748234cd1e516ced6284009906ae246f177eafe5a"
1327dependencies = [
1328 "cipher 0.5.2",
1051]1329]
10521330
1053[[package]]1331[[package]]
@@ -1100,10 +1378,10 @@ dependencies = [
1100 "digest 0.10.7",1378 "digest 0.10.7",
1101 "num-bigint-dig",1379 "num-bigint-dig",
1102 "num-traits",1380 "num-traits",
1103 "pkcs8",1381 "pkcs8 0.10.2",
1104 "rfc6979",1382 "rfc6979 0.4.0",
1105 "sha2 0.10.9",1383 "sha2 0.10.9",
1106 "signature",1384 "signature 2.2.0",
1107 "zeroize",1385 "zeroize",
1108]1386]
11091387
@@ -1123,15 +1401,21 @@ dependencies = [
1123]1401]
11241402
1125[[package]]1403[[package]]
1404name = "dunce"
1405version = "1.0.5"
1406source = "registry+https://github.com/rust-lang/crates.io-index"
1407checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
1408
1409[[package]]
1126name = "eax"1410name = "eax"
1127version = "0.5.0"1411version = "0.5.0"
1128source = "registry+https://github.com/rust-lang/crates.io-index"1412source = "registry+https://github.com/rust-lang/crates.io-index"
1129checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28"1413checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28"
1130dependencies = [1414dependencies = [
1131 "aead",1415 "aead 0.5.2",
1132 "cipher",1416 "cipher 0.4.4",
1133 "cmac",1417 "cmac",
1134 "ctr",1418 "ctr 0.9.2",
1135 "subtle",1419 "subtle",
1136]1420]
11371421
@@ -1141,12 +1425,27 @@ version = "0.16.9"
1141source = "registry+https://github.com/rust-lang/crates.io-index"1425source = "registry+https://github.com/rust-lang/crates.io-index"
1142checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"1426checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
1143dependencies = [1427dependencies = [
1144 "der",1428 "der 0.7.10",
1145 "digest 0.10.7",1429 "digest 0.10.7",
1146 "elliptic-curve",1430 "elliptic-curve 0.13.8",
1147 "rfc6979",1431 "rfc6979 0.4.0",
1148 "signature",1432 "signature 2.2.0",
1149 "spki",1433 "spki 0.7.3",
1434]
1435
1436[[package]]
1437name = "ecdsa"
1438version = "0.17.0"
1439source = "registry+https://github.com/rust-lang/crates.io-index"
1440checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0"
1441dependencies = [
1442 "der 0.8.1",
1443 "digest 0.11.3",
1444 "elliptic-curve 0.14.1",
1445 "rfc6979 0.6.0",
1446 "signature 3.0.0",
1447 "spki 0.8.0",
1448 "zeroize",
1150]1449]
11511450
1152[[package]]1451[[package]]
@@ -1155,8 +1454,18 @@ version = "2.2.3"
1155source = "registry+https://github.com/rust-lang/crates.io-index"1454source = "registry+https://github.com/rust-lang/crates.io-index"
1156checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"1455checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
1157dependencies = [1456dependencies = [
1158 "pkcs8",1457 "pkcs8 0.10.2",
1159 "signature",1458 "signature 2.2.0",
1459]
1460
1461[[package]]
1462name = "ed25519"
1463version = "3.0.0"
1464source = "registry+https://github.com/rust-lang/crates.io-index"
1465checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a"
1466dependencies = [
1467 "pkcs8 0.11.0",
1468 "signature 3.0.0",
1160]1469]
11611470
1162[[package]]1471[[package]]
@@ -1165,8 +1474,8 @@ version = "2.2.0"
1165source = "registry+https://github.com/rust-lang/crates.io-index"1474source = "registry+https://github.com/rust-lang/crates.io-index"
1166checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"1475checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
1167dependencies = [1476dependencies = [
1168 "curve25519-dalek",1477 "curve25519-dalek 4.1.3",
1169 "ed25519",1478 "ed25519 2.2.3",
1170 "rand_core 0.6.4",1479 "rand_core 0.6.4",
1171 "serde",1480 "serde",
1172 "sha2 0.10.9",1481 "sha2 0.10.9",
@@ -1175,6 +1484,22 @@ dependencies = [
1175]1484]
11761485
1177[[package]]1486[[package]]
1487name = "ed25519-dalek"
1488version = "3.0.0"
1489source = "registry+https://github.com/rust-lang/crates.io-index"
1490checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de"
1491dependencies = [
1492 "curve25519-dalek 5.0.0",
1493 "ed25519 3.0.0",
1494 "rand_core 0.10.1",
1495 "serde",
1496 "sha2 0.11.0",
1497 "signature 3.0.0",
1498 "subtle",
1499 "zeroize",
1500]
1501
1502[[package]]
1178name = "either"1503name = "either"
1179version = "1.17.0"1504version = "1.17.0"
1180source = "registry+https://github.com/rust-lang/crates.io-index"1505source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1189,18 +1514,18 @@ version = "0.13.8"
1189source = "registry+https://github.com/rust-lang/crates.io-index"1514source = "registry+https://github.com/rust-lang/crates.io-index"
1190checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"1515checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
1191dependencies = [1516dependencies = [
1192 "base16ct",1517 "base16ct 0.2.0",
1193 "base64ct",1518 "base64ct",
1194 "crypto-bigint",1519 "crypto-bigint 0.5.5",
1195 "digest 0.10.7",1520 "digest 0.10.7",
1196 "ff",1521 "ff 0.13.1",
1197 "generic-array",1522 "generic-array 0.14.9",
1198 "group",1523 "group 0.13.0",
1199 "hkdf 0.12.4",1524 "hkdf 0.12.4",
1200 "pem-rfc7468",1525 "pem-rfc7468 0.7.0",
1201 "pkcs8",1526 "pkcs8 0.10.2",
1202 "rand_core 0.6.4",1527 "rand_core 0.6.4",
1203 "sec1",1528 "sec1 0.7.3",
1204 "serde_json",1529 "serde_json",
1205 "serdect 0.2.0",1530 "serdect 0.2.0",
1206 "subtle",1531 "subtle",
@@ -1209,6 +1534,28 @@ dependencies = [
1209]1534]
12101535
1211[[package]]1536[[package]]
1537name = "elliptic-curve"
1538version = "0.14.1"
1539source = "registry+https://github.com/rust-lang/crates.io-index"
1540checksum = "9d65aa39b3a5c1c9c1b745c9a019234bb7a21b77abcb4f4d266d706e2d577d65"
1541dependencies = [
1542 "base16ct 1.0.0",
1543 "crypto-bigint 0.7.5",
1544 "crypto-common 0.2.2",
1545 "digest 0.11.3",
1546 "ff 0.14.0",
1547 "group 0.14.0",
1548 "hkdf 0.13.0",
1549 "hybrid-array",
1550 "pem-rfc7468 1.0.0",
1551 "pkcs8 0.11.0",
1552 "rand_core 0.10.1",
1553 "sec1 0.8.1",
1554 "subtle",
1555 "zeroize",
1556]
1557
1558[[package]]
1212name = "email-encoding"1559name = "email-encoding"
1213version = "0.4.1"1560version = "0.4.1"
1214source = "registry+https://github.com/rust-lang/crates.io-index"1561source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1237,6 +1584,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1237checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"1584checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
12381585
1239[[package]]1586[[package]]
1587name = "enum_dispatch"
1588version = "0.3.13"
1589source = "registry+https://github.com/rust-lang/crates.io-index"
1590checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
1591dependencies = [
1592 "once_cell",
1593 "proc-macro2",
1594 "quote",
1595 "syn 2.0.119",
1596]
1597
1598[[package]]
1240name = "equivalent"1599name = "equivalent"
1241version = "1.0.2"1600version = "1.0.2"
1242source = "registry+https://github.com/rust-lang/crates.io-index"1601source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1280,6 +1639,7 @@ dependencies = [
1280 "anyhow",1639 "anyhow",
1281 "cli",1640 "cli",
1282 "config",1641 "config",
1642 "ssh",
1283 "store",1643 "store",
1284 "tokio",1644 "tokio",
1285 "web",1645 "web",
@@ -1303,12 +1663,28 @@ dependencies = [
1303]1663]
13041664
1305[[package]]1665[[package]]
1666name = "ff"
1667version = "0.14.0"
1668source = "registry+https://github.com/rust-lang/crates.io-index"
1669checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f"
1670dependencies = [
1671 "rand_core 0.10.1",
1672 "subtle",
1673]
1674
1675[[package]]
1306name = "fiat-crypto"1676name = "fiat-crypto"
1307version = "0.2.9"1677version = "0.2.9"
1308source = "registry+https://github.com/rust-lang/crates.io-index"1678source = "registry+https://github.com/rust-lang/crates.io-index"
1309checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"1679checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
13101680
1311[[package]]1681[[package]]
1682name = "fiat-crypto"
1683version = "0.3.0"
1684source = "registry+https://github.com/rust-lang/crates.io-index"
1685checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
1686
1687[[package]]
1312name = "figment"1688name = "figment"
1313version = "0.10.19"1689version = "0.10.19"
1314source = "registry+https://github.com/rust-lang/crates.io-index"1690source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1395,12 +1771,33 @@ dependencies = [
1395]1771]
13961772
1397[[package]]1773[[package]]
1774name = "fs_extra"
1775version = "1.3.0"
1776source = "registry+https://github.com/rust-lang/crates.io-index"
1777checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
1778
1779[[package]]
1398name = "funty"1780name = "funty"
1399version = "2.0.0"1781version = "2.0.0"
1400source = "registry+https://github.com/rust-lang/crates.io-index"1782source = "registry+https://github.com/rust-lang/crates.io-index"
1401checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"1783checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
14021784
1403[[package]]1785[[package]]
1786name = "futures"
1787version = "0.3.33"
1788source = "registry+https://github.com/rust-lang/crates.io-index"
1789checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218"
1790dependencies = [
1791 "futures-channel",
1792 "futures-core",
1793 "futures-executor",
1794 "futures-io",
1795 "futures-sink",
1796 "futures-task",
1797 "futures-util",
1798]
1799
1800[[package]]
1404name = "futures-channel"1801name = "futures-channel"
1405version = "0.3.33"1802version = "0.3.33"
1406source = "registry+https://github.com/rust-lang/crates.io-index"1803source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1445,6 +1842,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1445checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"1842checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
14461843
1447[[package]]1844[[package]]
1845name = "futures-macro"
1846version = "0.3.33"
1847source = "registry+https://github.com/rust-lang/crates.io-index"
1848checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
1849dependencies = [
1850 "proc-macro2",
1851 "quote",
1852 "syn 2.0.119",
1853]
1854
1855[[package]]
1448name = "futures-sink"1856name = "futures-sink"
1449version = "0.3.33"1857version = "0.3.33"
1450source = "registry+https://github.com/rust-lang/crates.io-index"1858source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1462,8 +1870,10 @@ version = "0.3.33"
1462source = "registry+https://github.com/rust-lang/crates.io-index"1870source = "registry+https://github.com/rust-lang/crates.io-index"
1463checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"1871checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
1464dependencies = [1872dependencies = [
1873 "futures-channel",
1465 "futures-core",1874 "futures-core",
1466 "futures-io",1875 "futures-io",
1876 "futures-macro",
1467 "futures-sink",1877 "futures-sink",
1468 "futures-task",1878 "futures-task",
1469 "memchr",1879 "memchr",
@@ -1483,6 +1893,17 @@ dependencies = [
1483]1893]
14841894
1485[[package]]1895[[package]]
1896name = "generic-array"
1897version = "1.4.4"
1898source = "registry+https://github.com/rust-lang/crates.io-index"
1899checksum = "ab4e5aa225bc56696909483320f0ff9b600f1a971b52e07a17d70f3d9b43254b"
1900dependencies = [
1901 "generic-array 0.14.9",
1902 "rustversion",
1903 "typenum",
1904]
1905
1906[[package]]
1486name = "getrandom"1907name = "getrandom"
1487version = "0.2.17"1908version = "0.2.17"
1488source = "registry+https://github.com/rust-lang/crates.io-index"1909source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1512,9 +1933,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1512checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"1933checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
1513dependencies = [1934dependencies = [
1514 "cfg-if",1935 "cfg-if",
1936 "js-sys",
1515 "libc",1937 "libc",
1516 "r-efi 6.0.0",1938 "r-efi 6.0.0",
1517 "rand_core 0.10.1",1939 "rand_core 0.10.1",
1940 "wasm-bindgen",
1518]1941]
15191942
1520[[package]]1943[[package]]
@@ -1524,7 +1947,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1524checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"1947checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
1525dependencies = [1948dependencies = [
1526 "opaque-debug",1949 "opaque-debug",
1527 "polyval",1950 "polyval 0.6.2",
1951]
1952
1953[[package]]
1954name = "ghash"
1955version = "0.6.0"
1956source = "registry+https://github.com/rust-lang/crates.io-index"
1957checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5"
1958dependencies = [
1959 "polyval 0.7.3",
1528]1960]
15291961
1530[[package]]1962[[package]]
@@ -1538,7 +1970,7 @@ dependencies = [
1538 "moka",1970 "moka",
1539 "pgp",1971 "pgp",
1540 "rand_core 0.6.4",1972 "rand_core 0.6.4",
1541 "ssh-key",1973 "ssh-key 0.6.7",
1542 "tempfile",1974 "tempfile",
1543 "thiserror 2.0.19",1975 "thiserror 2.0.19",
1544 "tokio",1976 "tokio",
@@ -1576,12 +2008,23 @@ version = "0.13.0"
1576source = "registry+https://github.com/rust-lang/crates.io-index"2008source = "registry+https://github.com/rust-lang/crates.io-index"
1577checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"2009checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
1578dependencies = [2010dependencies = [
1579 "ff",2011 "ff 0.13.1",
1580 "rand_core 0.6.4",2012 "rand_core 0.6.4",
1581 "subtle",2013 "subtle",
1582]2014]
15832015
1584[[package]]2016[[package]]
2017name = "group"
2018version = "0.14.0"
2019source = "registry+https://github.com/rust-lang/crates.io-index"
2020checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4"
2021dependencies = [
2022 "ff 0.14.0",
2023 "rand_core 0.10.1",
2024 "subtle",
2025]
2026
2027[[package]]
1585name = "hashbrown"2028name = "hashbrown"
1586version = "0.16.1"2029version = "0.16.1"
1587source = "registry+https://github.com/rust-lang/crates.io-index"2030source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1620,6 +2063,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1620checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"2063checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
16212064
1622[[package]]2065[[package]]
2066name = "hex-literal"
2067version = "1.1.0"
2068source = "registry+https://github.com/rust-lang/crates.io-index"
2069checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1"
2070
2071[[package]]
1623name = "highlight"2072name = "highlight"
1624version = "0.1.0"2073version = "0.1.0"
1625dependencies = [2074dependencies = [
@@ -1735,7 +2184,10 @@ version = "0.4.13"
1735source = "registry+https://github.com/rust-lang/crates.io-index"2184source = "registry+https://github.com/rust-lang/crates.io-index"
1736checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"2185checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
1737dependencies = [2186dependencies = [
2187 "ctutils",
2188 "subtle",
1738 "typenum",2189 "typenum",
2190 "zeroize",
1739]2191]
17402192
1741[[package]]2193[[package]]
@@ -1774,6 +2226,30 @@ dependencies = [
1774]2226]
17752227
1776[[package]]2228[[package]]
2229name = "iana-time-zone"
2230version = "0.1.65"
2231source = "registry+https://github.com/rust-lang/crates.io-index"
2232checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
2233dependencies = [
2234 "android_system_properties",
2235 "core-foundation-sys",
2236 "iana-time-zone-haiku",
2237 "js-sys",
2238 "log",
2239 "wasm-bindgen",
2240 "windows-core",
2241]
2242
2243[[package]]
2244name = "iana-time-zone-haiku"
2245version = "0.1.2"
2246source = "registry+https://github.com/rust-lang/crates.io-index"
2247checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
2248dependencies = [
2249 "cc",
2250]
2251
2252[[package]]
1777name = "icu_collections"2253name = "icu_collections"
1778version = "2.1.1"2254version = "2.1.1"
1779source = "registry+https://github.com/rust-lang/crates.io-index"2255source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1860,7 +2336,7 @@ version = "0.5.1"
1860source = "registry+https://github.com/rust-lang/crates.io-index"2336source = "registry+https://github.com/rust-lang/crates.io-index"
1861checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477"2337checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477"
1862dependencies = [2338dependencies = [
1863 "cipher",2339 "cipher 0.4.4",
1864]2340]
18652341
1866[[package]]2342[[package]]
@@ -1929,7 +2405,17 @@ version = "0.1.4"
1929source = "registry+https://github.com/rust-lang/crates.io-index"2405source = "registry+https://github.com/rust-lang/crates.io-index"
1930checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"2406checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
1931dependencies = [2407dependencies = [
1932 "generic-array",2408 "generic-array 0.14.9",
2409]
2410
2411[[package]]
2412name = "inout"
2413version = "0.2.2"
2414source = "registry+https://github.com/rust-lang/crates.io-index"
2415checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
2416dependencies = [
2417 "block-padding 0.4.2",
2418 "hybrid-array",
1933]2419]
19342420
1935[[package]]2421[[package]]
@@ -1945,6 +2431,18 @@ dependencies = [
1945]2431]
19462432
1947[[package]]2433[[package]]
2434name = "internal-russh-num-bigint"
2435version = "0.5.0"
2436source = "registry+https://github.com/rust-lang/crates.io-index"
2437checksum = "ae8e22120c32fb4d19ec55fba35015f57095cd95a2e3b732e44457f5915b2ee8"
2438dependencies = [
2439 "num-integer",
2440 "num-traits",
2441 "rand 0.10.2",
2442 "rand_core 0.10.1",
2443]
2444
2445[[package]]
1948name = "is_terminal_polyfill"2446name = "is_terminal_polyfill"
1949version = "1.70.2"2447version = "1.70.2"
1950source = "registry+https://github.com/rust-lang/crates.io-index"2448source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1990,11 +2488,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1990checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"2488checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
1991dependencies = [2489dependencies = [
1992 "cfg-if",2490 "cfg-if",
1993 "ecdsa",2491 "ecdsa 0.16.9",
1994 "elliptic-curve",2492 "elliptic-curve 0.13.8",
1995 "once_cell",2493 "once_cell",
1996 "sha2 0.10.9",2494 "sha2 0.10.9",
1997 "signature",2495 "signature 2.2.0",
1998]2496]
19992497
2000[[package]]2498[[package]]
@@ -2007,6 +2505,26 @@ dependencies = [
2007]2505]
20082506
2009[[package]]2507[[package]]
2508name = "keccak"
2509version = "0.2.0"
2510source = "registry+https://github.com/rust-lang/crates.io-index"
2511checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa"
2512dependencies = [
2513 "cfg-if",
2514 "cpufeatures 0.3.0",
2515]
2516
2517[[package]]
2518name = "kem"
2519version = "0.3.0"
2520source = "registry+https://github.com/rust-lang/crates.io-index"
2521checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd"
2522dependencies = [
2523 "crypto-common 0.2.2",
2524 "rand_core 0.10.1",
2525]
2526
2527[[package]]
2010name = "lazy_static"2528name = "lazy_static"
2011version = "1.5.0"2529version = "1.5.0"
2012source = "registry+https://github.com/rust-lang/crates.io-index"2530source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2209,6 +2727,12 @@ dependencies = [
2209]2727]
22102728
2211[[package]]2729[[package]]
2730name = "md5"
2731version = "0.8.1"
2732source = "registry+https://github.com/rust-lang/crates.io-index"
2733checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c"
2734
2735[[package]]
2212name = "memchr"2736name = "memchr"
2213version = "2.8.3"2737version = "2.8.3"
2214source = "registry+https://github.com/rust-lang/crates.io-index"2738source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2252,6 +2776,20 @@ dependencies = [
2252]2776]
22532777
2254[[package]]2778[[package]]
2779name = "ml-kem"
2780version = "0.3.2"
2781source = "registry+https://github.com/rust-lang/crates.io-index"
2782checksum = "5e15f3e5b957493873e396a66914e83e616b6afe335cdef7efe5c6e1216aba66"
2783dependencies = [
2784 "hybrid-array",
2785 "kem",
2786 "module-lattice",
2787 "pkcs8 0.11.0",
2788 "rand_core 0.10.1",
2789 "sha3 0.11.0",
2790]
2791
2792[[package]]
2255name = "model"2793name = "model"
2256version = "0.1.0"2794version = "0.1.0"
2257dependencies = [2795dependencies = [
@@ -2259,6 +2797,17 @@ dependencies = [
2259]2797]
22602798
2261[[package]]2799[[package]]
2800name = "module-lattice"
2801version = "0.2.3"
2802source = "registry+https://github.com/rust-lang/crates.io-index"
2803checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe"
2804dependencies = [
2805 "ctutils",
2806 "hybrid-array",
2807 "num-traits",
2808]
2809
2810[[package]]
2262name = "moka"2811name = "moka"
2263version = "0.12.15"2812version = "0.12.15"
2264source = "registry+https://github.com/rust-lang/crates.io-index"2813source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2299,6 +2848,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2299checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"2848checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
23002849
2301[[package]]2850[[package]]
2851name = "nix"
2852version = "0.31.3"
2853source = "registry+https://github.com/rust-lang/crates.io-index"
2854checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
2855dependencies = [
2856 "bitflags",
2857 "cfg-if",
2858 "cfg_aliases",
2859 "libc",
2860]
2861
2862[[package]]
2302name = "nom"2863name = "nom"
2303version = "8.0.0"2864version = "8.0.0"
2304source = "registry+https://github.com/rust-lang/crates.io-index"2865source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2317,6 +2878,16 @@ dependencies = [
2317]2878]
23182879
2319[[package]]2880[[package]]
2881name = "num-bigint"
2882version = "0.4.8"
2883source = "registry+https://github.com/rust-lang/crates.io-index"
2884checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
2885dependencies = [
2886 "num-integer",
2887 "num-traits",
2888]
2889
2890[[package]]
2320name = "num-bigint-dig"2891name = "num-bigint-dig"
2321version = "0.8.6"2892version = "0.8.6"
2322source = "registry+https://github.com/rust-lang/crates.io-index"2893source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2395,9 +2966,9 @@ version = "0.1.0"
2395source = "registry+https://github.com/rust-lang/crates.io-index"2966source = "registry+https://github.com/rust-lang/crates.io-index"
2396checksum = "c196e0276c471c843dd5777e7543a36a298a4be942a2a688d8111cd43390dedb"2967checksum = "c196e0276c471c843dd5777e7543a36a298a4be942a2a688d8111cd43390dedb"
2397dependencies = [2968dependencies = [
2398 "aead",2969 "aead 0.5.2",
2399 "cipher",2970 "cipher 0.4.4",
2400 "ctr",2971 "ctr 0.9.2",
2401 "subtle",2972 "subtle",
2402]2973]
24032974
@@ -2468,39 +3039,100 @@ version = "0.13.2"
2468source = "registry+https://github.com/rust-lang/crates.io-index"3039source = "registry+https://github.com/rust-lang/crates.io-index"
2469checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"3040checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
2470dependencies = [3041dependencies = [
2471 "ecdsa",3042 "ecdsa 0.16.9",
2472 "elliptic-curve",3043 "elliptic-curve 0.13.8",
2473 "primeorder",3044 "primeorder 0.13.6",
2474 "sha2 0.10.9",3045 "sha2 0.10.9",
2475]3046]
24763047
2477[[package]]3048[[package]]
3049name = "p256"
3050version = "0.14.0"
3051source = "registry+https://github.com/rust-lang/crates.io-index"
3052checksum = "d2c9239b2dbc807adbbe147e8cf72ea7450c3a0aabe62cb8e75ff4ec22e1f72a"
3053dependencies = [
3054 "ecdsa 0.17.0",
3055 "elliptic-curve 0.14.1",
3056 "primefield",
3057 "primeorder 0.14.0",
3058 "sha2 0.11.0",
3059]
3060
3061[[package]]
2478name = "p384"3062name = "p384"
2479version = "0.13.1"3063version = "0.13.1"
2480source = "registry+https://github.com/rust-lang/crates.io-index"3064source = "registry+https://github.com/rust-lang/crates.io-index"
2481checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"3065checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
2482dependencies = [3066dependencies = [
2483 "ecdsa",3067 "ecdsa 0.16.9",
2484 "elliptic-curve",3068 "elliptic-curve 0.13.8",
2485 "primeorder",3069 "primeorder 0.13.6",
2486 "sha2 0.10.9",3070 "sha2 0.10.9",
2487]3071]
24883072
2489[[package]]3073[[package]]
3074name = "p384"
3075version = "0.14.0"
3076source = "registry+https://github.com/rust-lang/crates.io-index"
3077checksum = "d17b851e6b3e378ab4ecb07fa2ed23f4d15f075735f8fec9fa1e7bdce5f8301f"
3078dependencies = [
3079 "ecdsa 0.17.0",
3080 "elliptic-curve 0.14.1",
3081 "fiat-crypto 0.3.0",
3082 "primefield",
3083 "primeorder 0.14.0",
3084 "sha2 0.11.0",
3085]
3086
3087[[package]]
2490name = "p521"3088name = "p521"
2491version = "0.13.3"3089version = "0.13.3"
2492source = "registry+https://github.com/rust-lang/crates.io-index"3090source = "registry+https://github.com/rust-lang/crates.io-index"
2493checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2"3091checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2"
2494dependencies = [3092dependencies = [
2495 "base16ct",3093 "base16ct 0.2.0",
2496 "ecdsa",3094 "ecdsa 0.16.9",
2497 "elliptic-curve",3095 "elliptic-curve 0.13.8",
2498 "primeorder",3096 "primeorder 0.13.6",
2499 "rand_core 0.6.4",3097 "rand_core 0.6.4",
2500 "sha2 0.10.9",3098 "sha2 0.10.9",
2501]3099]
25023100
2503[[package]]3101[[package]]
3102name = "p521"
3103version = "0.14.0"
3104source = "registry+https://github.com/rust-lang/crates.io-index"
3105checksum = "4ad64cc32c2dc466317c12ee5853e61f159f9eab1fe7efade0395dc2e7b43449"
3106dependencies = [
3107 "base16ct 1.0.0",
3108 "ecdsa 0.17.0",
3109 "elliptic-curve 0.14.1",
3110 "primefield",
3111 "primeorder 0.14.0",
3112 "sha2 0.11.0",
3113]
3114
3115[[package]]
3116name = "pageant"
3117version = "0.2.1"
3118source = "registry+https://github.com/rust-lang/crates.io-index"
3119checksum = "4f3a5ae18f65a85c67a77d18d42d3606c07948e3c17c1e5f74852b26589e88a5"
3120dependencies = [
3121 "base16ct 1.0.0",
3122 "byteorder",
3123 "bytes",
3124 "delegate",
3125 "futures",
3126 "log",
3127 "rand 0.10.2",
3128 "sha2 0.11.0",
3129 "thiserror 2.0.19",
3130 "tokio",
3131 "windows",
3132 "windows-strings",
3133]
3134
3135[[package]]
2504name = "parking"3136name = "parking"
2505version = "2.2.1"3137version = "2.2.1"
2506source = "registry+https://github.com/rust-lang/crates.io-index"3138source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2541,6 +3173,25 @@ dependencies = [
2541]3173]
25423174
2543[[package]]3175[[package]]
3176name = "password-hash"
3177version = "0.6.1"
3178source = "registry+https://github.com/rust-lang/crates.io-index"
3179checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b"
3180dependencies = [
3181 "phc",
3182]
3183
3184[[package]]
3185name = "pbkdf2"
3186version = "0.13.0"
3187source = "registry+https://github.com/rust-lang/crates.io-index"
3188checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629"
3189dependencies = [
3190 "digest 0.11.3",
3191 "hmac 0.13.0",
3192]
3193
3194[[package]]
2544name = "pear"3195name = "pear"
2545version = "0.2.9"3196version = "0.2.9"
2546source = "registry+https://github.com/rust-lang/crates.io-index"3197source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2573,6 +3224,15 @@ dependencies = [
2573]3224]
25743225
2575[[package]]3226[[package]]
3227name = "pem-rfc7468"
3228version = "1.0.0"
3229source = "registry+https://github.com/rust-lang/crates.io-index"
3230checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9"
3231dependencies = [
3232 "base64ct",
3233]
3234
3235[[package]]
2576name = "percent-encoding"3236name = "percent-encoding"
2577version = "2.3.2"3237version = "2.3.2"
2578source = "registry+https://github.com/rust-lang/crates.io-index"3238source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2584,15 +3244,15 @@ version = "0.20.0"
2584source = "registry+https://github.com/rust-lang/crates.io-index"3244source = "registry+https://github.com/rust-lang/crates.io-index"
2585checksum = "1cfa4743b28656065ff4c0ba09e46b357a65e8c00fc2341e89084b82f87cbdf1"3245checksum = "1cfa4743b28656065ff4c0ba09e46b357a65e8c00fc2341e89084b82f87cbdf1"
2586dependencies = [3246dependencies = [
2587 "aead",3247 "aead 0.5.2",
2588 "aes",3248 "aes 0.8.4",
2589 "aes-gcm",3249 "aes-gcm 0.10.3",
2590 "aes-kw",3250 "aes-kw",
2591 "argon2",3251 "argon2 0.5.3",
2592 "base64",3252 "base64",
2593 "bitfields",3253 "bitfields",
2594 "block-padding",3254 "block-padding 0.3.3",
2595 "blowfish",3255 "blowfish 0.9.1",
2596 "buffer-redux",3256 "buffer-redux",
2597 "byteorder",3257 "byteorder",
2598 "bytes",3258 "bytes",
@@ -2600,22 +3260,22 @@ dependencies = [
2600 "camellia",3260 "camellia",
2601 "cast5",3261 "cast5",
2602 "cfb-mode",3262 "cfb-mode",
2603 "cipher",3263 "cipher 0.4.4",
2604 "const-oid 0.9.6",3264 "const-oid 0.9.6",
2605 "crc24",3265 "crc24",
2606 "curve25519-dalek",3266 "curve25519-dalek 4.1.3",
2607 "cx448",3267 "cx448",
2608 "derive_builder",3268 "derive_builder",
2609 "derive_more",3269 "derive_more",
2610 "des",3270 "des 0.8.1",
2611 "digest 0.10.7",3271 "digest 0.10.7",
2612 "dsa",3272 "dsa",
2613 "eax",3273 "eax",
2614 "ecdsa",3274 "ecdsa 0.16.9",
2615 "ed25519-dalek",3275 "ed25519-dalek 2.2.0",
2616 "elliptic-curve",3276 "elliptic-curve 0.13.8",
2617 "flate2",3277 "flate2",
2618 "generic-array",3278 "generic-array 0.14.9",
2619 "hex",3279 "hex",
2620 "hkdf 0.12.4",3280 "hkdf 0.12.4",
2621 "idea",3281 "idea",
@@ -2628,18 +3288,18 @@ dependencies = [
2628 "num-traits",3288 "num-traits",
2629 "num_enum",3289 "num_enum",
2630 "ocb3",3290 "ocb3",
2631 "p256",3291 "p256 0.13.2",
2632 "p384",3292 "p384 0.13.1",
2633 "p521",3293 "p521 0.13.3",
2634 "rand 0.8.7",3294 "rand 0.8.7",
2635 "replace_with",3295 "replace_with",
2636 "ripemd",3296 "ripemd",
2637 "rsa",3297 "rsa 0.9.10",
2638 "sha1 0.10.7",3298 "sha1 0.10.7",
2639 "sha1-checked",3299 "sha1-checked",
2640 "sha2 0.10.9",3300 "sha2 0.10.9",
2641 "sha3",3301 "sha3 0.10.9",
2642 "signature",3302 "signature 2.2.0",
2643 "smallvec",3303 "smallvec",
2644 "snafu",3304 "snafu",
2645 "subtle",3305 "subtle",
@@ -2649,6 +3309,16 @@ dependencies = [
2649]3309]
26503310
2651[[package]]3311[[package]]
3312name = "phc"
3313version = "0.6.1"
3314source = "registry+https://github.com/rust-lang/crates.io-index"
3315checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892"
3316dependencies = [
3317 "base64ct",
3318 "ctutils",
3319]
3320
3321[[package]]
2652name = "phf"3322name = "phf"
2653version = "0.13.1"3323version = "0.13.1"
2654source = "registry+https://github.com/rust-lang/crates.io-index"3324source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2699,9 +3369,36 @@ version = "0.7.5"
2699source = "registry+https://github.com/rust-lang/crates.io-index"3369source = "registry+https://github.com/rust-lang/crates.io-index"
2700checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"3370checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
2701dependencies = [3371dependencies = [
2702 "der",3372 "der 0.7.10",
2703 "pkcs8",3373 "pkcs8 0.10.2",
2704 "spki",3374 "spki 0.7.3",
3375]
3376
3377[[package]]
3378name = "pkcs1"
3379version = "0.8.0-rc.4"
3380source = "registry+https://github.com/rust-lang/crates.io-index"
3381checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e"
3382dependencies = [
3383 "der 0.8.1",
3384 "spki 0.8.0",
3385]
3386
3387[[package]]
3388name = "pkcs5"
3389version = "0.8.1"
3390source = "registry+https://github.com/rust-lang/crates.io-index"
3391checksum = "63d440a804ec8d6fafbb6b84471e013286658d373248927692ab3366686220ca"
3392dependencies = [
3393 "aes 0.9.1",
3394 "aes-gcm 0.11.0",
3395 "cbc",
3396 "der 0.8.1",
3397 "pbkdf2",
3398 "rand_core 0.10.1",
3399 "scrypt",
3400 "sha2 0.11.0",
3401 "spki 0.8.0",
2705]3402]
27063403
2707[[package]]3404[[package]]
@@ -2710,8 +3407,20 @@ version = "0.10.2"
2710source = "registry+https://github.com/rust-lang/crates.io-index"3407source = "registry+https://github.com/rust-lang/crates.io-index"
2711checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"3408checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
2712dependencies = [3409dependencies = [
2713 "der",3410 "der 0.7.10",
2714 "spki",3411 "spki 0.7.3",
3412]
3413
3414[[package]]
3415name = "pkcs8"
3416version = "0.11.0"
3417source = "registry+https://github.com/rust-lang/crates.io-index"
3418checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
3419dependencies = [
3420 "der 0.8.1",
3421 "pkcs5",
3422 "rand_core 0.10.1",
3423 "spki 0.8.0",
2715]3424]
27163425
2717[[package]]3426[[package]]
@@ -2721,6 +3430,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2721checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"3430checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
27223431
2723[[package]]3432[[package]]
3433name = "poly1305"
3434version = "0.9.1"
3435source = "registry+https://github.com/rust-lang/crates.io-index"
3436checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c"
3437dependencies = [
3438 "cpufeatures 0.3.0",
3439 "universal-hash 0.6.1",
3440 "zeroize",
3441]
3442
3443[[package]]
2724name = "polyval"3444name = "polyval"
2725version = "0.6.2"3445version = "0.6.2"
2726source = "registry+https://github.com/rust-lang/crates.io-index"3446source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2729,7 +3449,18 @@ dependencies = [
2729 "cfg-if",3449 "cfg-if",
2730 "cpufeatures 0.2.17",3450 "cpufeatures 0.2.17",
2731 "opaque-debug",3451 "opaque-debug",
2732 "universal-hash",3452 "universal-hash 0.5.1",
3453]
3454
3455[[package]]
3456name = "polyval"
3457version = "0.7.3"
3458source = "registry+https://github.com/rust-lang/crates.io-index"
3459checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd"
3460dependencies = [
3461 "cpubits",
3462 "cpufeatures 0.3.0",
3463 "universal-hash 0.6.1",
2733]3464]
27343465
2735[[package]]3466[[package]]
@@ -2769,12 +3500,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2769checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"3500checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
27703501
2771[[package]]3502[[package]]
3503name = "primefield"
3504version = "0.14.0"
3505source = "registry+https://github.com/rust-lang/crates.io-index"
3506checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4"
3507dependencies = [
3508 "crypto-bigint 0.7.5",
3509 "crypto-common 0.2.2",
3510 "ff 0.14.0",
3511 "rand_core 0.10.1",
3512 "subtle",
3513 "zeroize",
3514]
3515
3516[[package]]
2772name = "primeorder"3517name = "primeorder"
2773version = "0.13.6"3518version = "0.13.6"
2774source = "registry+https://github.com/rust-lang/crates.io-index"3519source = "registry+https://github.com/rust-lang/crates.io-index"
2775checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"3520checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
2776dependencies = [3521dependencies = [
2777 "elliptic-curve",3522 "elliptic-curve 0.13.8",
3523]
3524
3525[[package]]
3526name = "primeorder"
3527version = "0.14.0"
3528source = "registry+https://github.com/rust-lang/crates.io-index"
3529checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06"
3530dependencies = [
3531 "elliptic-curve 0.14.1",
3532 "once_cell",
3533 "primefield",
3534 "serdect 0.4.3",
3535 "wnaf",
2778]3536]
27793537
2780[[package]]3538[[package]]
@@ -2963,6 +3721,16 @@ dependencies = [
2963]3721]
29643722
2965[[package]]3723[[package]]
3724name = "rfc6979"
3725version = "0.6.0"
3726source = "registry+https://github.com/rust-lang/crates.io-index"
3727checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b"
3728dependencies = [
3729 "crypto-bigint 0.7.5",
3730 "hmac 0.13.0",
3731]
3732
3733[[package]]
2966name = "ripemd"3734name = "ripemd"
2967version = "0.1.3"3735version = "0.1.3"
2968source = "registry+https://github.com/rust-lang/crates.io-index"3736source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2975,42 +3743,157 @@ dependencies = [
2975name = "rpassword"3743name = "rpassword"
2976version = "7.5.4"3744version = "7.5.4"
2977source = "registry+https://github.com/rust-lang/crates.io-index"3745source = "registry+https://github.com/rust-lang/crates.io-index"
2978checksum = "2da316a15f47e3d053de9cb2c439650bd8fa4aaeb9365f2e5f27f492ff73c196"3746checksum = "2da316a15f47e3d053de9cb2c439650bd8fa4aaeb9365f2e5f27f492ff73c196"
3747dependencies = [
3748 "libc",
3749 "rtoolbox",
3750 "windows-sys 0.61.2",
3751]
3752
3753[[package]]
3754name = "rsa"
3755version = "0.9.10"
3756source = "registry+https://github.com/rust-lang/crates.io-index"
3757checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
3758dependencies = [
3759 "const-oid 0.9.6",
3760 "digest 0.10.7",
3761 "num-bigint-dig",
3762 "num-integer",
3763 "num-traits",
3764 "pkcs1 0.7.5",
3765 "pkcs8 0.10.2",
3766 "rand_core 0.6.4",
3767 "sha2 0.10.9",
3768 "signature 2.2.0",
3769 "spki 0.7.3",
3770 "subtle",
3771 "zeroize",
3772]
3773
3774[[package]]
3775name = "rsa"
3776version = "0.10.0-rc.18"
3777source = "registry+https://github.com/rust-lang/crates.io-index"
3778checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf"
3779dependencies = [
3780 "const-oid 0.10.2",
3781 "crypto-bigint 0.7.5",
3782 "crypto-primes",
3783 "digest 0.11.3",
3784 "pkcs1 0.8.0-rc.4",
3785 "pkcs8 0.11.0",
3786 "rand_core 0.10.1",
3787 "sha2 0.11.0",
3788 "signature 3.0.0",
3789 "spki 0.8.0",
3790 "zeroize",
3791]
3792
3793[[package]]
3794name = "rtoolbox"
3795version = "0.0.5"
3796source = "registry+https://github.com/rust-lang/crates.io-index"
3797checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844"
2979dependencies = [3798dependencies = [
2980 "libc",3799 "libc",
2981 "rtoolbox",3800 "windows-sys 0.59.0",
2982 "windows-sys 0.61.2",
2983]3801]
29843802
2985[[package]]3803[[package]]
2986name = "rsa"3804name = "russh"
2987version = "0.9.10"3805version = "0.62.4"
2988source = "registry+https://github.com/rust-lang/crates.io-index"3806source = "registry+https://github.com/rust-lang/crates.io-index"
2989checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"3807checksum = "b8b67b5a0d8068c89dcbe9d95df986af7a851d1f3c604525274c37468e60464f"
2990dependencies = [3808dependencies = [
2991 "const-oid 0.9.6",3809 "aes 0.9.1",
2992 "digest 0.10.7",3810 "aws-lc-rs",
2993 "num-bigint-dig",3811 "bitflags",
2994 "num-integer",3812 "block-padding 0.4.2",
2995 "num-traits",3813 "byteorder",
2996 "pkcs1",3814 "bytes",
2997 "pkcs8",3815 "cbc",
2998 "rand_core 0.6.4",3816 "cipher 0.5.2",
2999 "sha2 0.10.9",3817 "crypto-bigint 0.7.5",
3000 "signature",3818 "ctr 0.10.1",
3001 "spki",3819 "curve25519-dalek 5.0.0",
3820 "data-encoding",
3821 "delegate",
3822 "der 0.8.1",
3823 "digest 0.11.3",
3824 "ecdsa 0.17.0",
3825 "ed25519-dalek 3.0.0",
3826 "elliptic-curve 0.14.1",
3827 "enum_dispatch",
3828 "flate2",
3829 "futures",
3830 "generic-array 1.4.4",
3831 "getrandom 0.4.3",
3832 "ghash 0.6.0",
3833 "hex-literal",
3834 "hmac 0.13.0",
3835 "inout 0.2.2",
3836 "internal-russh-num-bigint",
3837 "keccak 0.2.0",
3838 "log",
3839 "md5",
3840 "ml-kem",
3841 "module-lattice",
3842 "num-bigint",
3843 "p256 0.14.0",
3844 "p384 0.14.0",
3845 "p521 0.14.0",
3846 "pageant",
3847 "pbkdf2",
3848 "pkcs1 0.8.0-rc.4",
3849 "pkcs5",
3850 "pkcs8 0.11.0",
3851 "polyval 0.7.3",
3852 "rand 0.10.2",
3853 "rand_core 0.10.1",
3854 "rsa 0.10.0-rc.18",
3855 "russh-cryptovec",
3856 "russh-util",
3857 "salsa20",
3858 "scrypt",
3859 "sec1 0.8.1",
3860 "sha1 0.11.0",
3861 "sha2 0.11.0",
3862 "sha3 0.12.0",
3863 "signature 3.0.0",
3864 "spki 0.8.0",
3865 "ssh-encoding 0.3.0",
3866 "ssh-key 0.7.0-rc.11",
3002 "subtle",3867 "subtle",
3868 "thiserror 2.0.19",
3869 "tokio",
3870 "typenum",
3871 "universal-hash 0.6.1",
3003 "zeroize",3872 "zeroize",
3004]3873]
30053874
3006[[package]]3875[[package]]
3007name = "rtoolbox"3876name = "russh-cryptovec"
3008version = "0.0.5"3877version = "0.62.0"
3009source = "registry+https://github.com/rust-lang/crates.io-index"3878source = "registry+https://github.com/rust-lang/crates.io-index"
3010checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844"3879checksum = "3aec6cb630dbe85d72ffd7bcd95f07e1bd69f9f270ee8adfa1afe443a6331438"
3011dependencies = [3880dependencies = [
3012 "libc",3881 "log",
3013 "windows-sys 0.59.0",3882 "nix",
3883 "ssh-encoding 0.3.0",
3884 "windows-sys 0.61.2",
3885]
3886
3887[[package]]
3888name = "russh-util"
3889version = "0.52.0"
3890source = "registry+https://github.com/rust-lang/crates.io-index"
3891checksum = "668424a5dde0bcb45b55ba7de8476b93831b4aa2fa6947e145f3b053e22c60b6"
3892dependencies = [
3893 "chrono",
3894 "tokio",
3895 "wasm-bindgen",
3896 "wasm-bindgen-futures",
3014]3897]
30153898
3016[[package]]3899[[package]]
@@ -3089,6 +3972,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
3089checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"3972checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
30903973
3091[[package]]3974[[package]]
3975name = "salsa20"
3976version = "0.11.0"
3977source = "registry+https://github.com/rust-lang/crates.io-index"
3978checksum = "2f874456e72520ff1375a06c588eaf074b0f01f9e9e1aada45bd9b7954a6e42c"
3979dependencies = [
3980 "cfg-if",
3981 "cipher 0.5.2",
3982]
3983
3984[[package]]
3092name = "same-file"3985name = "same-file"
3093version = "1.0.6"3986version = "1.0.6"
3094source = "registry+https://github.com/rust-lang/crates.io-index"3987source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3113,21 +4006,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
3113checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"4006checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
31144007
3115[[package]]4008[[package]]
4009name = "scrypt"
4010version = "0.12.0"
4011source = "registry+https://github.com/rust-lang/crates.io-index"
4012checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0"
4013dependencies = [
4014 "cfg-if",
4015 "pbkdf2",
4016 "salsa20",
4017 "sha2 0.11.0",
4018]
4019
4020[[package]]
3116name = "sec1"4021name = "sec1"
3117version = "0.7.3"4022version = "0.7.3"
3118source = "registry+https://github.com/rust-lang/crates.io-index"4023source = "registry+https://github.com/rust-lang/crates.io-index"
3119checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"4024checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
3120dependencies = [4025dependencies = [
3121 "base16ct",4026 "base16ct 0.2.0",
3122 "der",4027 "der 0.7.10",
3123 "generic-array",4028 "generic-array 0.14.9",
3124 "pkcs8",4029 "pkcs8 0.10.2",
3125 "serdect 0.2.0",4030 "serdect 0.2.0",
3126 "subtle",4031 "subtle",
3127 "zeroize",4032 "zeroize",
3128]4033]
31294034
3130[[package]]4035[[package]]
4036name = "sec1"
4037version = "0.8.1"
4038source = "registry+https://github.com/rust-lang/crates.io-index"
4039checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d"
4040dependencies = [
4041 "base16ct 1.0.0",
4042 "ctutils",
4043 "der 0.8.1",
4044 "hybrid-array",
4045 "subtle",
4046 "zeroize",
4047]
4048
4049[[package]]
3131name = "security-framework"4050name = "security-framework"
3132version = "3.7.0"4051version = "3.7.0"
3133source = "registry+https://github.com/rust-lang/crates.io-index"4052source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3237,7 +4156,7 @@ version = "0.2.0"
3237source = "registry+https://github.com/rust-lang/crates.io-index"4156source = "registry+https://github.com/rust-lang/crates.io-index"
3238checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177"4157checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177"
3239dependencies = [4158dependencies = [
3240 "base16ct",4159 "base16ct 0.2.0",
3241 "serde",4160 "serde",
3242]4161]
32434162
@@ -3247,7 +4166,17 @@ version = "0.3.0"
3247source = "registry+https://github.com/rust-lang/crates.io-index"4166source = "registry+https://github.com/rust-lang/crates.io-index"
3248checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53"4167checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53"
3249dependencies = [4168dependencies = [
3250 "base16ct",4169 "base16ct 0.2.0",
4170 "serde",
4171]
4172
4173[[package]]
4174name = "serdect"
4175version = "0.4.3"
4176source = "registry+https://github.com/rust-lang/crates.io-index"
4177checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e"
4178dependencies = [
4179 "base16ct 1.0.0",
3251 "serde",4180 "serde",
3252]4181]
32534182
@@ -3313,7 +4242,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
3313checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874"4242checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874"
3314dependencies = [4243dependencies = [
3315 "digest 0.10.7",4244 "digest 0.10.7",
3316 "keccak",4245 "keccak 0.1.6",
4246]
4247
4248[[package]]
4249name = "sha3"
4250version = "0.11.0"
4251source = "registry+https://github.com/rust-lang/crates.io-index"
4252checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1"
4253dependencies = [
4254 "digest 0.11.3",
4255 "keccak 0.2.0",
4256]
4257
4258[[package]]
4259name = "sha3"
4260version = "0.12.0"
4261source = "registry+https://github.com/rust-lang/crates.io-index"
4262checksum = "bc9bad02c26382724b2d2692c6f179285e4b54eeecd7968f52a50059c3c11759"
4263dependencies = [
4264 "digest 0.11.3",
4265 "keccak 0.2.0",
4266 "sponge-cursor",
3317]4267]
33184268
3319[[package]]4269[[package]]
@@ -3352,6 +4302,16 @@ dependencies = [
3352]4302]
33534303
3354[[package]]4304[[package]]
4305name = "signature"
4306version = "3.0.0"
4307source = "registry+https://github.com/rust-lang/crates.io-index"
4308checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5"
4309dependencies = [
4310 "digest 0.11.3",
4311 "rand_core 0.10.1",
4312]
4313
4314[[package]]
3355name = "simd-adler32"4315name = "simd-adler32"
3356version = "0.3.10"4316version = "0.3.10"
3357source = "registry+https://github.com/rust-lang/crates.io-index"4317source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3431,10 +4391,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
3431checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"4391checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
3432dependencies = [4392dependencies = [
3433 "base64ct",4393 "base64ct",
3434 "der",4394 "der 0.7.10",
3435]4395]
34364396
3437[[package]]4397[[package]]
4398name = "spki"
4399version = "0.8.0"
4400source = "registry+https://github.com/rust-lang/crates.io-index"
4401checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f"
4402dependencies = [
4403 "base64ct",
4404 "der 0.8.1",
4405]
4406
4407[[package]]
4408name = "sponge-cursor"
4409version = "0.1.0"
4410source = "registry+https://github.com/rust-lang/crates.io-index"
4411checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620"
4412
4413[[package]]
3438name = "sqlx"4414name = "sqlx"
3439version = "0.9.0"4415version = "0.9.0"
3440source = "registry+https://github.com/rust-lang/crates.io-index"4416source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3534,7 +4510,7 @@ dependencies = [
3534 "either",4510 "either",
3535 "futures-core",4511 "futures-core",
3536 "futures-util",4512 "futures-util",
3537 "generic-array",4513 "generic-array 0.14.9",
3538 "log",4514 "log",
3539 "percent-encoding",4515 "percent-encoding",
3540 "serde",4516 "serde",
@@ -3607,6 +4583,20 @@ dependencies = [
3607[[package]]4583[[package]]
3608name = "ssh"4584name = "ssh"
3609version = "0.1.0"4585version = "0.1.0"
4586dependencies = [
4587 "auth",
4588 "config",
4589 "git",
4590 "model",
4591 "rand_core 0.6.4",
4592 "russh",
4593 "ssh-key 0.6.7",
4594 "store",
4595 "tempfile",
4596 "thiserror 2.0.19",
4597 "tokio",
4598 "tracing",
4599]
36104600
3611[[package]]4601[[package]]
3612name = "ssh-cipher"4602name = "ssh-cipher"
@@ -3614,8 +4604,26 @@ version = "0.2.0"
3614source = "registry+https://github.com/rust-lang/crates.io-index"4604source = "registry+https://github.com/rust-lang/crates.io-index"
3615checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f"4605checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f"
3616dependencies = [4606dependencies = [
3617 "cipher",4607 "cipher 0.4.4",
3618 "ssh-encoding",4608 "ssh-encoding 0.2.0",
4609]
4610
4611[[package]]
4612name = "ssh-cipher"
4613version = "0.3.0"
4614source = "registry+https://github.com/rust-lang/crates.io-index"
4615checksum = "d801accda99469cde6d73da741422610fdf6508a72d9a69d1b55cb241c720597"
4616dependencies = [
4617 "aead 0.6.1",
4618 "aes 0.9.1",
4619 "aes-gcm 0.11.0",
4620 "chacha20",
4621 "cipher 0.5.2",
4622 "ctutils",
4623 "des 0.9.0",
4624 "poly1305",
4625 "ssh-encoding 0.3.0",
4626 "zeroize",
3619]4627]
36204628
3621[[package]]4629[[package]]
@@ -3625,33 +4633,74 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
3625checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15"4633checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15"
3626dependencies = [4634dependencies = [
3627 "base64ct",4635 "base64ct",
3628 "pem-rfc7468",4636 "pem-rfc7468 0.7.0",
3629 "sha2 0.10.9",4637 "sha2 0.10.9",
3630]4638]
36314639
3632[[package]]4640[[package]]
4641name = "ssh-encoding"
4642version = "0.3.0"
4643source = "registry+https://github.com/rust-lang/crates.io-index"
4644checksum = "7b54d0ed0498daf3f78d82e00e28c8eec9d75a067c4cfbcc7a0f7d0f4077749e"
4645dependencies = [
4646 "base64ct",
4647 "bytes",
4648 "crypto-bigint 0.7.5",
4649 "ctutils",
4650 "digest 0.11.3",
4651 "pem-rfc7468 1.0.0",
4652 "zeroize",
4653]
4654
4655[[package]]
3633name = "ssh-key"4656name = "ssh-key"
3634version = "0.6.7"4657version = "0.6.7"
3635source = "registry+https://github.com/rust-lang/crates.io-index"4658source = "registry+https://github.com/rust-lang/crates.io-index"
3636checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3"4659checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3"
3637dependencies = [4660dependencies = [
3638 "ed25519-dalek",4661 "ed25519-dalek 2.2.0",
3639 "num-bigint-dig",4662 "num-bigint-dig",
3640 "p256",4663 "p256 0.13.2",
3641 "p384",4664 "p384 0.13.1",
3642 "p521",4665 "p521 0.13.3",
3643 "rand_core 0.6.4",4666 "rand_core 0.6.4",
3644 "rsa",4667 "rsa 0.9.10",
3645 "sec1",4668 "sec1 0.7.3",
3646 "sha2 0.10.9",4669 "sha2 0.10.9",
3647 "signature",4670 "signature 2.2.0",
3648 "ssh-cipher",4671 "ssh-cipher 0.2.0",
3649 "ssh-encoding",4672 "ssh-encoding 0.2.0",
3650 "subtle",4673 "subtle",
3651 "zeroize",4674 "zeroize",
3652]4675]
36534676
3654[[package]]4677[[package]]
4678name = "ssh-key"
4679version = "0.7.0-rc.11"
4680source = "registry+https://github.com/rust-lang/crates.io-index"
4681checksum = "f9a32fae177b74a22aa9c5b01bf7e68b33545be32d9e381e248058d2adc15ce3"
4682dependencies = [
4683 "argon2 0.6.0-rc.8",
4684 "bcrypt-pbkdf",
4685 "ctutils",
4686 "ed25519-dalek 3.0.0",
4687 "hex",
4688 "hmac 0.13.0",
4689 "p256 0.14.0",
4690 "p384 0.14.0",
4691 "p521 0.14.0",
4692 "rand_core 0.10.1",
4693 "rsa 0.10.0-rc.18",
4694 "sec1 0.8.1",
4695 "sha1 0.11.0",
4696 "sha2 0.11.0",
4697 "signature 3.0.0",
4698 "ssh-cipher 0.3.0",
4699 "ssh-encoding 0.3.0",
4700 "zeroize",
4701]
4702
4703[[package]]
3655name = "stable_deref_trait"4704name = "stable_deref_trait"
3656version = "1.2.1"4705version = "1.2.1"
3657source = "registry+https://github.com/rust-lang/crates.io-index"4706source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4156,7 +5205,7 @@ version = "0.7.1"
4156source = "registry+https://github.com/rust-lang/crates.io-index"5205source = "registry+https://github.com/rust-lang/crates.io-index"
4157checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013"5206checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013"
4158dependencies = [5207dependencies = [
4159 "cipher",5208 "cipher 0.4.4",
4160]5209]
41615210
4162[[package]]5211[[package]]
@@ -4246,6 +5295,22 @@ dependencies = [
4246]5295]
42475296
4248[[package]]5297[[package]]
5298name = "universal-hash"
5299version = "0.6.1"
5300source = "registry+https://github.com/rust-lang/crates.io-index"
5301checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96"
5302dependencies = [
5303 "crypto-common 0.2.2",
5304 "ctutils",
5305]
5306
5307[[package]]
5308name = "untrusted"
5309version = "0.7.1"
5310source = "registry+https://github.com/rust-lang/crates.io-index"
5311checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
5312
5313[[package]]
4249name = "url"5314name = "url"
4250version = "2.5.8"5315version = "2.5.8"
4251source = "registry+https://github.com/rust-lang/crates.io-index"5316source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4343,6 +5408,16 @@ dependencies = [
4343]5408]
43445409
4345[[package]]5410[[package]]
5411name = "wasm-bindgen-futures"
5412version = "0.4.76"
5413source = "registry+https://github.com/rust-lang/crates.io-index"
5414checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d"
5415dependencies = [
5416 "js-sys",
5417 "wasm-bindgen",
5418]
5419
5420[[package]]
4346name = "wasm-bindgen-macro"5421name = "wasm-bindgen-macro"
4347version = "0.2.126"5422version = "0.2.126"
4348source = "registry+https://github.com/rust-lang/crates.io-index"5423source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4444,12 +5519,107 @@ dependencies = [
4444]5519]
44455520
4446[[package]]5521[[package]]
5522name = "windows"
5523version = "0.62.2"
5524source = "registry+https://github.com/rust-lang/crates.io-index"
5525checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
5526dependencies = [
5527 "windows-collections",
5528 "windows-core",
5529 "windows-future",
5530 "windows-numerics",
5531]
5532
5533[[package]]
5534name = "windows-collections"
5535version = "0.3.2"
5536source = "registry+https://github.com/rust-lang/crates.io-index"
5537checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
5538dependencies = [
5539 "windows-core",
5540]
5541
5542[[package]]
5543name = "windows-core"
5544version = "0.62.2"
5545source = "registry+https://github.com/rust-lang/crates.io-index"
5546checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
5547dependencies = [
5548 "windows-implement",
5549 "windows-interface",
5550 "windows-link",
5551 "windows-result",
5552 "windows-strings",
5553]
5554
5555[[package]]
5556name = "windows-future"
5557version = "0.3.2"
5558source = "registry+https://github.com/rust-lang/crates.io-index"
5559checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
5560dependencies = [
5561 "windows-core",
5562 "windows-link",
5563 "windows-threading",
5564]
5565
5566[[package]]
5567name = "windows-implement"
5568version = "0.60.2"
5569source = "registry+https://github.com/rust-lang/crates.io-index"
5570checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
5571dependencies = [
5572 "proc-macro2",
5573 "quote",
5574 "syn 2.0.119",
5575]
5576
5577[[package]]
5578name = "windows-interface"
5579version = "0.59.3"
5580source = "registry+https://github.com/rust-lang/crates.io-index"
5581checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
5582dependencies = [
5583 "proc-macro2",
5584 "quote",
5585 "syn 2.0.119",
5586]
5587
5588[[package]]
4447name = "windows-link"5589name = "windows-link"
4448version = "0.2.1"5590version = "0.2.1"
4449source = "registry+https://github.com/rust-lang/crates.io-index"5591source = "registry+https://github.com/rust-lang/crates.io-index"
4450checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"5592checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
44515593
4452[[package]]5594[[package]]
5595name = "windows-numerics"
5596version = "0.3.1"
5597source = "registry+https://github.com/rust-lang/crates.io-index"
5598checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
5599dependencies = [
5600 "windows-core",
5601 "windows-link",
5602]
5603
5604[[package]]
5605name = "windows-result"
5606version = "0.4.1"
5607source = "registry+https://github.com/rust-lang/crates.io-index"
5608checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
5609dependencies = [
5610 "windows-link",
5611]
5612
5613[[package]]
5614name = "windows-strings"
5615version = "0.5.1"
5616source = "registry+https://github.com/rust-lang/crates.io-index"
5617checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
5618dependencies = [
5619 "windows-link",
5620]
5621
5622[[package]]
4453name = "windows-sys"5623name = "windows-sys"
4454version = "0.59.0"5624version = "0.59.0"
4455source = "registry+https://github.com/rust-lang/crates.io-index"5625source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4484,6 +5654,15 @@ dependencies = [
4484]5654]
44855655
4486[[package]]5656[[package]]
5657name = "windows-threading"
5658version = "0.2.1"
5659source = "registry+https://github.com/rust-lang/crates.io-index"
5660checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
5661dependencies = [
5662 "windows-link",
5663]
5664
5665[[package]]
4487name = "windows_aarch64_gnullvm"5666name = "windows_aarch64_gnullvm"
4488version = "0.52.6"5667version = "0.52.6"
4489source = "registry+https://github.com/rust-lang/crates.io-index"5668source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4547,6 +5726,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
4547checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"5726checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
45485727
4549[[package]]5728[[package]]
5729name = "wnaf"
5730version = "0.14.0"
5731source = "registry+https://github.com/rust-lang/crates.io-index"
5732checksum = "ab12e7090f27e2ffd9322651492942d50c2926094af30601e1964337db39daf1"
5733dependencies = [
5734 "ff 0.14.0",
5735 "group 0.14.0",
5736 "hybrid-array",
5737]
5738
5739[[package]]
4550name = "writeable"5740name = "writeable"
4551version = "0.6.3"5741version = "0.6.3"
4552source = "registry+https://github.com/rust-lang/crates.io-index"5742source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4567,7 +5757,7 @@ version = "2.0.1"
4567source = "registry+https://github.com/rust-lang/crates.io-index"5757source = "registry+https://github.com/rust-lang/crates.io-index"
4568checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"5758checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
4569dependencies = [5759dependencies = [
4570 "curve25519-dalek",5760 "curve25519-dalek 4.1.3",
4571 "rand_core 0.6.4",5761 "rand_core 0.6.4",
4572 "serde",5762 "serde",
4573 "zeroize",5763 "zeroize",
Cargo.toml +3 −0
@@ -18,6 +18,7 @@ path = "src/main.rs"
18# `serve` back to us so that no library crate has to depend on `web`.18# `serve` back to us so that no library crate has to depend on `web`.
19cli = { path = "crates/cli" }19cli = { path = "crates/cli" }
20web = { path = "crates/web" }20web = { path = "crates/web" }
21ssh = { path = "crates/ssh" }
21config = { path = "crates/config" }22config = { path = "crates/config" }
22store = { path = "crates/store" }23store = { path = "crates/store" }
23anyhow = { workspace = true }24anyhow = { workspace = true }
@@ -150,6 +151,8 @@ ammonia = "4"
150# gzip-encoded upload-pack requests.151# gzip-encoded upload-pack requests.
151tokio-util = { version = "0.7", features = ["io"] }152tokio-util = { version = "0.7", features = ["io"] }
152flate2 = "1"153flate2 = "1"
154# SSH server (default features: aws-lc-rs backend, no ring; re-exports ssh-key).
155russh = "0.62"
153tracing = "0.1"156tracing = "0.1"
154tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "json"] }157tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "json"] }
155158
crates/cli/src/hook_cmd.rs +4 −4
@@ -44,10 +44,10 @@ fn post_receive() -> anyhow::Result<()> {
44 let mut input = String::new();44 let mut input = String::new();
45 std::io::stdin().read_to_string(&mut input)?;45 std::io::stdin().read_to_string(&mut input)?;
4646
47 let git_dir = std::env::var_os("GIT_DIR")47 // git runs post-receive with cwd = the repo dir and often GIT_DIR=".";
48 .map(PathBuf::from)48 // canonicalize so we get the absolute `{id}.git` directory either way.
49 .or_else(|| std::env::current_dir().ok())49 let git_dir = std::env::var_os("GIT_DIR").map_or_else(|| PathBuf::from("."), PathBuf::from);
50 .ok_or_else(|| anyhow::anyhow!("cannot determine GIT_DIR"))?;50 let git_dir = std::fs::canonicalize(&git_dir).unwrap_or(git_dir);
5151
52 // The bare repo dir is `{id}.git`; the stem is the repo's ULID.52 // The bare repo dir is `{id}.git`; the stem is the repo's ULID.
53 let Some(repo_id) = git_dir53 let Some(repo_id) = git_dir
crates/ssh/Cargo.toml +16 −0
@@ -10,6 +10,22 @@ repository.workspace = true
10publish.workspace = true10publish.workspace = true
1111
12[dependencies]12[dependencies]
13config = { workspace = true }
14store = { workspace = true }
15git = { workspace = true }
16auth = { workspace = true }
17model = { workspace = true }
18ssh-key = { workspace = true }
19rand_core = { workspace = true }
20# SSH server. Default features use the aws-lc-rs crypto backend (no `ring`); it
21# re-exports ssh-key as russh::keys. See docs/decisions.md for the license note.
22russh = { workspace = true }
23tokio = { workspace = true }
24tracing = { workspace = true }
25thiserror = { workspace = true }
26
27[dev-dependencies]
28tempfile = { workspace = true }
1329
14[lints]30[lints]
15workspace = true31workspace = true
crates/ssh/src/command.rs +114 −0
@@ -0,0 +1,114 @@
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Parsing the SSH `exec` command line.
6//!
7//! Only the three git pack commands are accepted, each with exactly one quoted
8//! path argument. Anything else — including an interactive shell — is refused with
9//! a friendly banner by the caller.
10
11use git::pack::Service;
12
13/// A parsed, accepted git command: the service and its (raw, un-normalized) path.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct GitCommand {
16 /// The git service requested.
17 pub service: Service,
18 /// The repository path argument, as given (still needs normalizing).
19 pub path: String,
20}
21
22/// Parse an exec command line into an accepted [`GitCommand`], or `None` if it is
23/// not one of the three git pack commands with a single path argument.
24#[must_use]
25pub fn parse(command: &str) -> Option<GitCommand> {
26 let words = shell_split(command)?;
27 let (name, rest) = words.split_first()?;
28 if rest.len() != 1 {
29 return None;
30 }
31 let service = match name.as_str() {
32 "git-upload-pack" => Service::UploadPack,
33 "git-receive-pack" => Service::ReceivePack,
34 "git-upload-archive" => Service::UploadArchive,
35 _ => return None,
36 };
37 Some(GitCommand {
38 service,
39 path: rest[0].clone(),
40 })
41}
42
43/// Split a command line into words, honouring single and double quotes (git sends
44/// the path single-quoted). Returns `None` on an unterminated quote.
45fn shell_split(input: &str) -> Option<Vec<String>> {
46 let mut words = Vec::new();
47 let mut current = String::new();
48 let mut has_word = false;
49 let mut chars = input.chars().peekable();
50
51 while let Some(c) = chars.next() {
52 match c {
53 ' ' | '\t' => {
54 if has_word {
55 words.push(std::mem::take(&mut current));
56 has_word = false;
57 }
58 }
59 '\'' | '"' => {
60 has_word = true;
61 let quote = c;
62 loop {
63 match chars.next() {
64 Some(q) if q == quote => break,
65 Some(other) => current.push(other),
66 None => return None, // unterminated quote
67 }
68 }
69 }
70 other => {
71 has_word = true;
72 current.push(other);
73 }
74 }
75 }
76 if has_word {
77 words.push(current);
78 }
79 Some(words)
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn parses_the_three_git_commands() {
88 assert_eq!(
89 parse("git-upload-pack 'owner/repo.git'"),
90 Some(GitCommand {
91 service: Service::UploadPack,
92 path: "owner/repo.git".to_string()
93 })
94 );
95 assert_eq!(
96 parse("git-receive-pack '/owner/repo'").map(|c| c.service),
97 Some(Service::ReceivePack)
98 );
99 assert_eq!(
100 parse("git-upload-archive 'x'").map(|c| c.service),
101 Some(Service::UploadArchive)
102 );
103 }
104
105 #[test]
106 fn rejects_shells_and_extra_args() {
107 assert!(parse("bash").is_none());
108 assert!(parse("").is_none());
109 assert!(parse("git-upload-pack").is_none());
110 assert!(parse("git-upload-pack a b").is_none());
111 assert!(parse("scp -t /tmp").is_none());
112 assert!(parse("git-upload-pack 'unterminated").is_none());
113 }
114}
crates/ssh/src/hostkey.rs +70 −0
@@ -0,0 +1,70 @@
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! The server host key: generated on first start, loaded thereafter.
6//!
7//! Returned as the OpenSSH-format PEM string rather than a typed key so the caller
8//! (`server`) parses it with russh's own re-exported `ssh-key` version — the
9//! workspace pins a different `ssh-key` patch, and the two key types are distinct,
10//! but the OpenSSH text format bridges them.
11
12use std::fs;
13use std::os::unix::fs::PermissionsExt;
14use std::path::Path;
15
16use rand_core::OsRng;
17use ssh_key::{Algorithm, LineEnding, PrivateKey};
18
19use crate::SshError;
20
21/// Load the ed25519 host key from `path` as an OpenSSH PEM, generating and
22/// persisting one (mode `0600`) if it does not yet exist.
23///
24/// # Errors
25///
26/// Returns [`SshError`] if the key file cannot be read, generated, or written.
27pub fn load_or_generate(path: &Path) -> Result<String, SshError> {
28 if path.exists() {
29 return Ok(fs::read_to_string(path)?);
30 }
31
32 let key = PrivateKey::random(&mut OsRng, Algorithm::Ed25519)
33 .map_err(|e| SshError::HostKey(format!("generating host key: {e}")))?;
34 if let Some(parent) = path.parent() {
35 fs::create_dir_all(parent)?;
36 }
37 let pem = key
38 .to_openssh(LineEnding::LF)
39 .map_err(|e| SshError::HostKey(format!("encoding host key: {e}")))?;
40 fs::write(path, pem.as_bytes())?;
41 fs::set_permissions(path, fs::Permissions::from_mode(0o600))?;
42 Ok(pem.to_string())
43}
44
45#[cfg(test)]
46mod tests {
47 #![allow(clippy::unwrap_used)]
48
49 use super::*;
50
51 #[test]
52 fn generates_then_loads_a_stable_host_key() {
53 let dir = tempfile::tempdir().unwrap();
54 let path = dir.path().join("ssh").join("host_ed25519");
55
56 let first_pem = load_or_generate(&path).unwrap();
57 let first = PrivateKey::from_openssh(first_pem.as_str()).unwrap();
58 assert_eq!(first.algorithm(), Algorithm::Ed25519);
59 // The file is created with owner-only permissions.
60 let mode = fs::metadata(&path).unwrap().permissions().mode();
61 assert_eq!(mode & 0o777, 0o600);
62
63 // A second load returns the same key (same public fingerprint).
64 let second = PrivateKey::from_openssh(load_or_generate(&path).unwrap().as_str()).unwrap();
65 assert_eq!(
66 first.public_key().fingerprint(ssh_key::HashAlg::Sha256),
67 second.public_key().fingerprint(ssh_key::HashAlg::Sha256)
68 );
69 }
70}
crates/ssh/src/lib.rs +27 −0
@@ -3,3 +3,30 @@
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44
5//! The SSH front-end: publickey auth and `exec` dispatch into pack processes.5//! The SSH front-end: publickey auth and `exec` dispatch into pack processes.
6//!
7//! A `russh` server accepts only `publickey` auth (matching the offered key's
8//! SHA256 fingerprint against the `keys` table), handles only the `exec` channel
9//! request, and streams the channel data to and from a spawned `git`
10//! upload-pack/receive-pack process via [`git::pack`]. Anything else — a shell,
11//! a PTY, an unrecognized command — gets a friendly banner and a non-zero exit.
12
13mod command;
14mod hostkey;
15mod resolve;
16mod server;
17
18pub use crate::server::serve;
19
20/// An error from the SSH layer.
21#[derive(Debug, thiserror::Error)]
22pub enum SshError {
23 /// A filesystem or socket I/O error.
24 #[error(transparent)]
25 Io(#[from] std::io::Error),
26 /// The host key could not be generated, read, or written.
27 #[error("host key error: {0}")]
28 HostKey(String),
29 /// The russh server failed to start or run.
30 #[error("ssh server error: {0}")]
31 Server(String),
32}
crates/ssh/src/resolve.rs +56 −0
@@ -0,0 +1,56 @@
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Normalizing and resolving the repository path from an SSH command.
6
7/// Normalize an SSH repo path into `(owner, repo_path)`.
8///
9/// Strips a leading `/` and a trailing `.git`, and rejects `..`, absolute, or
10/// empty paths. The remainder is `owner/group…/repo`; the owner is the first
11/// segment and the repo path is everything after it.
12#[must_use]
13pub fn normalize(raw: &str) -> Option<(String, String)> {
14 let trimmed = raw.trim().trim_matches('\'');
15 let without_lead = trimmed.strip_prefix('/').unwrap_or(trimmed);
16 let path = without_lead.strip_suffix(".git").unwrap_or(without_lead);
17
18 if path.is_empty() || path.starts_with('/') {
19 return None;
20 }
21 // Reject traversal in any segment.
22 if path
23 .split('/')
24 .any(|seg| seg == ".." || seg == "." || seg.is_empty())
25 {
26 return None;
27 }
28 let (owner, repo_path) = path.split_once('/')?;
29 Some((owner.to_string(), repo_path.to_string()))
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35
36 #[test]
37 fn strips_lead_and_git_suffix() {
38 assert_eq!(
39 normalize("/hanna/backend/api.git"),
40 Some(("hanna".to_string(), "backend/api".to_string()))
41 );
42 assert_eq!(
43 normalize("hanna/proj"),
44 Some(("hanna".to_string(), "proj".to_string()))
45 );
46 }
47
48 #[test]
49 fn rejects_traversal_and_malformed() {
50 assert!(normalize("../etc/passwd").is_none());
51 assert!(normalize("hanna/../secret").is_none());
52 assert!(normalize("/absolute//path").is_none());
53 assert!(normalize("").is_none());
54 assert!(normalize("noowner").is_none()); // no repo path
55 }
56}
crates/ssh/src/server.rs +355 −0
@@ -0,0 +1,355 @@
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! The russh server: publickey auth, exec dispatch into pack processes.
6
7use std::sync::Arc;
8use std::time::Duration;
9
10use config::Config;
11use model::KeyKind;
12use russh::keys::{HashAlg, PublicKey};
13use russh::server::{Auth, ChannelOpenHandle, Handler, Msg, Server as _, Session};
14use russh::{Channel, ChannelId, MethodKind, MethodSet};
15use store::Store;
16use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
17use tokio::process::ChildStdin;
18use tokio::sync::Mutex;
19
20use crate::SshError;
21use crate::{command, resolve};
22
23/// State shared across every connection.
24struct Shared {
25 store: Store,
26 config: Arc<Config>,
27 /// The `--config` path to hand hooks via `FABRICA_CONFIG`.
28 config_path: Option<String>,
29}
30
31/// The russh `Server` factory.
32struct GitServer {
33 shared: Arc<Shared>,
34}
35
36impl russh::server::Server for GitServer {
37 type Handler = GitHandler;
38
39 fn new_client(&mut self, _peer: Option<std::net::SocketAddr>) -> GitHandler {
40 GitHandler {
41 shared: Arc::clone(&self.shared),
42 identity: None,
43 stdin: Arc::new(Mutex::new(None)),
44 }
45 }
46}
47
48/// The authenticated identity, bound after publickey auth.
49#[derive(Clone)]
50struct Identity {
51 user_id: String,
52 username: String,
53 is_admin: bool,
54 key_id: String,
55}
56
57/// One connection's handler.
58struct GitHandler {
59 shared: Arc<Shared>,
60 identity: Option<Identity>,
61 /// The running pack subprocess's stdin, written to as client data arrives.
62 stdin: Arc<Mutex<Option<ChildStdin>>>,
63}
64
65impl Handler for GitHandler {
66 type Error = russh::Error;
67
68 async fn auth_publickey(
69 &mut self,
70 _user: &str,
71 public_key: &PublicKey,
72 ) -> Result<Auth, Self::Error> {
73 // Identify by key fingerprint, not the SSH username (everyone is `git@`).
74 let fingerprint = public_key.fingerprint(HashAlg::Sha256).to_string();
75 let Ok(Some(key)) = self
76 .shared
77 .store
78 .key_by_fingerprint(KeyKind::Ssh, &fingerprint)
79 .await
80 else {
81 return Ok(Auth::reject());
82 };
83 let Ok(Some(user)) = self.shared.store.user_by_id(&key.user_id).await else {
84 return Ok(Auth::reject());
85 };
86 if user.disabled_at.is_some() {
87 return Ok(Auth::reject());
88 }
89 let _ = self.shared.store.touch_key_used(&key.id).await;
90 self.identity = Some(Identity {
91 user_id: user.id,
92 username: user.username,
93 is_admin: user.is_admin,
94 key_id: key.id,
95 });
96 Ok(Auth::Accept)
97 }
98
99 async fn channel_open_session(
100 &mut self,
101 _channel: Channel<Msg>,
102 reply: ChannelOpenHandle,
103 _session: &mut Session,
104 ) -> Result<(), Self::Error> {
105 // Dropping the handle rejects the channel, so accept it explicitly.
106 reply.accept().await;
107 Ok(())
108 }
109
110 async fn exec_request(
111 &mut self,
112 channel: ChannelId,
113 data: &[u8],
114 session: &mut Session,
115 ) -> Result<(), Self::Error> {
116 session.channel_success(channel)?;
117 let command = String::from_utf8_lossy(data).into_owned();
118 if let Err(banner) = self.spawn_exec(channel, &command, session).await {
119 let _ = session.extended_data(channel, 1, banner.into_bytes());
120 let _ = session.exit_status_request(channel, 1);
121 let _ = session.eof(channel);
122 let _ = session.close(channel);
123 }
124 Ok(())
125 }
126
127 async fn shell_request(
128 &mut self,
129 channel: ChannelId,
130 session: &mut Session,
131 ) -> Result<(), Self::Error> {
132 session.channel_success(channel)?;
133 // Use the async handle so the banner/close actually flush before return.
134 let handle = session.handle();
135 let _ = handle
136 .extended_data(channel, 1, self.no_shell_banner().into_bytes())
137 .await;
138 let _ = handle.exit_status_request(channel, 1).await;
139 let _ = handle.eof(channel).await;
140 let _ = handle.close(channel).await;
141 Ok(())
142 }
143
144 async fn data(
145 &mut self,
146 _channel: ChannelId,
147 data: &[u8],
148 _session: &mut Session,
149 ) -> Result<(), Self::Error> {
150 if let Some(stdin) = self.stdin.lock().await.as_mut() {
151 let _ = stdin.write_all(data).await;
152 }
153 Ok(())
154 }
155
156 async fn channel_eof(
157 &mut self,
158 _channel: ChannelId,
159 _session: &mut Session,
160 ) -> Result<(), Self::Error> {
161 // Close the child's stdin so upload-pack/receive-pack sees EOF.
162 *self.stdin.lock().await = None;
163 Ok(())
164 }
165}
166
167impl GitHandler {
168 /// The friendly shell-access banner.
169 fn no_shell_banner(&self) -> String {
170 let who = self
171 .identity
172 .as_ref()
173 .map_or("there", |i| i.username.as_str());
174 format!("Hi {who}! fabrica does not provide shell access.\n")
175 }
176
177 /// Authorize the exec command and, on success, spawn the pack process and start
178 /// streaming. On failure returns the banner to write to stderr.
179 async fn spawn_exec(
180 &mut self,
181 channel: ChannelId,
182 command: &str,
183 session: &mut Session,
184 ) -> Result<(), String> {
185 let Some(identity) = self.identity.clone() else {
186 return Err("Authentication required.".to_string());
187 };
188 let parsed = command::parse(command).ok_or_else(|| self.no_shell_banner())?;
189 let (owner, repo_path) = resolve::normalize(&parsed.path)
190 .ok_or_else(|| "Invalid repository path.".to_string())?;
191
192 let repo = self
193 .resolve_repo(&owner, &repo_path, &identity, parsed.service)
194 .await?;
195
196 let repo_dir = git::repo_path(&self.shared.config.storage.repo_dir, &repo.id)
197 .map_err(|_| "Repository is unavailable.".to_string())?;
198
199 let env = git::pack::PackEnv {
200 home: self.shared.config.storage.data_dir.join("tmp"),
201 config_path: self.shared.config_path.clone(),
202 user_id: Some(identity.user_id),
203 user_name: Some(identity.username),
204 repo_id: Some(repo.id),
205 key_id: Some(identity.key_id),
206 protocol: None,
207 };
208 let mut child = git::pack::spawn(
209 &self.shared.config.git.binary,
210 parsed.service,
211 &[],
212 &repo_dir,
213 &env,
214 )
215 .map_err(|e| format!("Failed to start git: {e}"))?;
216
217 let (Some(stdin), Some(mut stdout), Some(mut stderr)) =
218 (child.stdin.take(), child.stdout.take(), child.stderr.take())
219 else {
220 return Err("Internal error.".to_string());
221 };
222 *self.stdin.lock().await = Some(stdin);
223
224 let handle = session.handle();
225 tokio::spawn(async move {
226 let mut obuf = vec![0u8; 32 * 1024];
227 let mut ebuf = vec![0u8; 32 * 1024];
228 let (mut out_done, mut err_done) = (false, false);
229 while !(out_done && err_done) {
230 tokio::select! {
231 r = stdout.read(&mut obuf), if !out_done => match r {
232 Ok(0) | Err(_) => out_done = true,
233 Ok(n) => { let _ = handle.data(channel, obuf[..n].to_vec()).await; }
234 },
235 r = stderr.read(&mut ebuf), if !err_done => match r {
236 Ok(0) | Err(_) => err_done = true,
237 Ok(n) => { let _ = handle.extended_data(channel, 1, ebuf[..n].to_vec()).await; }
238 },
239 }
240 }
241 let code = child
242 .wait()
243 .await
244 .ok()
245 .and_then(|s| s.code())
246 .and_then(|c| u32::try_from(c).ok())
247 .unwrap_or(0);
248 let _ = handle.exit_status_request(channel, code).await;
249 let _ = handle.eof(channel).await;
250 let _ = handle.close(channel).await;
251 });
252 Ok(())
253 }
254
255 /// Resolve and authorize the repo for the requested service.
256 async fn resolve_repo(
257 &self,
258 owner: &str,
259 repo_path: &str,
260 identity: &Identity,
261 service: git::pack::Service,
262 ) -> Result<model::Repo, String> {
263 let not_found = || "Repository not found.".to_string();
264 let owner_user = self
265 .shared
266 .store
267 .user_by_username(owner)
268 .await
269 .ok()
270 .flatten()
271 .ok_or_else(not_found)?;
272 let repo = self
273 .shared
274 .store
275 .repo_by_owner_path(&owner_user.id, repo_path)
276 .await
277 .ok()
278 .flatten()
279 .ok_or_else(not_found)?;
280
281 let collaborator = self
282 .shared
283 .store
284 .collaborator_permission(&repo.id, &identity.user_id)
285 .await
286 .ok()
287 .flatten()
288 .and_then(|p| auth::Permission::parse(&p));
289 let viewer = auth::Viewer {
290 id: identity.user_id.clone(),
291 is_admin: identity.is_admin,
292 };
293 let access = auth::access(
294 Some(&viewer),
295 &repo,
296 collaborator,
297 self.shared.config.instance.allow_anonymous,
298 );
299
300 let required = match service {
301 git::pack::Service::ReceivePack => auth::Access::Write,
302 _ => auth::Access::Read,
303 };
304 if access >= required {
305 Ok(repo)
306 } else if access >= auth::Access::Read {
307 Err("You do not have write access to this repository.".to_string())
308 } else {
309 // No read access at all: do not confirm the repo exists.
310 Err(not_found())
311 }
312 }
313}
314
315/// Run the SSH server until the process exits. A no-op when `ssh.enabled` is false.
316///
317/// # Errors
318///
319/// Returns [`SshError`] if the host key cannot be prepared or the server fails to
320/// bind or run.
321pub async fn serve(
322 config: Arc<Config>,
323 store: Store,
324 config_path: Option<String>,
325) -> Result<(), SshError> {
326 if !config.ssh.enabled {
327 tracing::info!("ssh disabled (ssh.enabled = false)");
328 return Ok(());
329 }
330
331 let host_pem = crate::hostkey::load_or_generate(&config.ssh.host_key_path)?;
332 let host_key = russh::keys::PrivateKey::from_openssh(host_pem.as_str())
333 .map_err(|e| SshError::HostKey(format!("parsing host key: {e}")))?;
334 let russh_config = Arc::new(russh::server::Config {
335 methods: MethodSet::from(&[MethodKind::PublicKey][..]),
336 keys: vec![host_key],
337 inactivity_timeout: Some(Duration::from_secs(3600)),
338 auth_rejection_time: Duration::from_secs(2),
339 ..russh::server::Config::default()
340 });
341
342 let shared = Arc::new(Shared {
343 store,
344 config: Arc::clone(&config),
345 config_path,
346 });
347 let addr = (config.ssh.address.clone(), config.ssh.port);
348 tracing::info!(address = %config.ssh.address, port = config.ssh.port, "ssh server listening");
349
350 let mut server = GitServer { shared };
351 server
352 .run_on_address(russh_config, addr)
353 .await
354 .map_err(|e| SshError::Server(e.to_string()))
355}
crates/store/src/keys.rs +14 −0
@@ -156,6 +156,20 @@ impl Store {
156 Ok(row.as_ref().map(map_key).transpose()?)156 Ok(row.as_ref().map(map_key).transpose()?)
157 }157 }
158158
159 /// Stamp a key's `last_used_at` with the current time (SSH auth binding).
160 ///
161 /// # Errors
162 ///
163 /// Returns [`StoreError::Query`] on a database failure.
164 pub async fn touch_key_used(&self, key_id: &str) -> Result<(), StoreError> {
165 sqlx::query("UPDATE keys SET last_used_at = $1 WHERE id = $2")
166 .bind(now_ms())
167 .bind(key_id)
168 .execute(&self.pool)
169 .await?;
170 Ok(())
171 }
172
159 /// Delete a key by id. Returns `true` if a row was removed.173 /// Delete a key by id. Returns `true` if a row was removed.
160 ///174 ///
161 /// # Errors175 /// # Errors
deny.toml +5 −0
@@ -22,6 +22,11 @@ allow = [
22 "Unicode-3.0",22 "Unicode-3.0",
23 "Zlib",23 "Zlib",
24 "CC0-1.0",24 "CC0-1.0",
25 # SSH transport requires a crypto backend (russh -> aws-lc-rs). aws-lc-rs and
26 # aws-lc-sys carry the OpenSSL license in their SPDX expression; SSH is not
27 # optional for a git server, so this one addition is accepted. See
28 # docs/decisions.md.
29 "OpenSSL",
25]30]
26confidence-threshold = 0.931confidence-threshold = 0.9
2732
docs/decisions.md +26 −0
@@ -473,3 +473,29 @@ lands with the API phase. `fabrica hook post-receive` derives the repo id from t
473`GIT_DIR` basename and loads config from `FABRICA_CONFIG` (set by the SSH/HTTP473`GIT_DIR` basename and loads config from `FABRICA_CONFIG` (set by the SSH/HTTP
474server on the child) or the default location; a local push with neither set no-ops474server on the child) or the default location; a local push with neither set no-ops
475cleanly, and it always exits 0 so a hook error never rejects an accepted push.475cleanly, and it always exits 0 so a hook error never rejects an accepted push.
476
477## 2026-07-25 — SSH via russh; the OpenSSL license is admitted for the crypto backend
478
479**Decision:** `crates/ssh` uses `russh` 0.62 with its **default** crypto backend
480`aws-lc-rs` (explicitly **not** `ring`). `deny.toml` gains the **`OpenSSL`** license,
481and `flake.nix` gains `cmake` (aws-lc-sys builds AWS-LC via cmake).
482
483**Alternatives:** the `ring` backend (also carries OpenSSL-family terms and is not
484allowlisted); not shipping SSH at all.
485
486**Rationale:** SSH is not optional for a git server (the DoD requires push over
487SSH), the spec names russh, and russh cannot function without a crypto backend.
488Its only backends are `aws-lc-rs` (default) and `ring`; both pull the OpenSSL
489license into the SPDX graph, so *some* license admission is unavoidable to have SSH
490at all. `aws-lc-rs` is the default and keeps `ring` out. Admitting `OpenSSL` — a
491permissive license — for the crypto backend is the minimal, contained concession,
492justified by SSH being mandatory. This is the one place the otherwise-strict
493allowlist is widened, and it is scoped to the transport crypto.
494
495**Implementation notes:** russh merged `russh-keys` into `russh::keys` and
496re-exports `ssh-key` at a patch level distinct from the workspace's, so the two
497`PrivateKey` types differ; the host key bridges them via the OpenSSH PEM text
498(generate/load with the workspace `ssh-key`, parse with `russh::keys`). Identity is
499by key fingerprint, not the SSH username (everyone connects as `git@`);
500`channel_open_session` must explicitly `reply.accept()` in 0.62 or the channel is
501rejected; exec output streams through the `Clone + Send` `Handle` from a task.
flake.nix +3 −0
@@ -53,6 +53,8 @@
53 nativeBuildInputs = [53 nativeBuildInputs = [
54 pkgs.pkg-config54 pkgs.pkg-config
55 pkgs.makeWrapper55 pkgs.makeWrapper
56 # aws-lc-sys (russh's crypto backend) builds AWS-LC via cmake.
57 pkgs.cmake
56 ];58 ];
57 buildInputs = [59 buildInputs = [
58 pkgs.libgit260 pkgs.libgit2
@@ -161,6 +163,7 @@
161 pkgs.cargo-deny163 pkgs.cargo-deny
162 pkgs.just164 pkgs.just
163 pkgs.pkg-config165 pkgs.pkg-config
166 pkgs.cmake
164 ];167 ];
165 buildInputs = commonArgs.buildInputs;168 buildInputs = commonArgs.buildInputs;
166 LIBGIT2_NO_VENDOR = "1";169 LIBGIT2_NO_VENDOR = "1";
src/serve.rs +17 −2
@@ -54,8 +54,23 @@ pub fn run(req: ServeRequest) -> anyhow::Result<ExitCode> {
54 )54 )
55 .parse()?;55 .parse()?;
5656
57 let state = web::AppState::build(Arc::new(loaded.config), store, secret)?;57 let config = Arc::new(loaded.config);
58 web::serve(state, addr).await?;58
59 // Start the SSH server alongside the web server. Its lifetime is tied to
60 // the process: when web's graceful shutdown returns, we abort it.
61 let ssh_config = Arc::clone(&config);
62 let ssh_store = store.clone();
63 let ssh_config_path = req.config_path.as_ref().map(|p| p.display().to_string());
64 let ssh_task = tokio::spawn(async move {
65 if let Err(err) = ssh::serve(ssh_config, ssh_store, ssh_config_path).await {
66 eprintln!("ssh server error: {err}");
67 }
68 });
69
70 let state = web::AppState::build(config, store, secret)?;
71 let result = web::serve(state, addr).await;
72 ssh_task.abort();
73 result?;
59 Ok(ExitCode::SUCCESS)74 Ok(ExitCode::SUCCESS)
60 })75 })
61}76}