fabrica

hanna/fabrica

feat(git): verify SSH and GPG commit signatures

7a05468 · hanna committed on 2026-07-25

Read the gpgsig header via libgit2's extract_signature and dispatch on
the armor banner. SSH signatures (ssh-key) embed their own public key, so
we verify against it with namespace "git" and classify by whether its
SHA256 fingerprint is registered — cleanly separating Verified,
UnknownKey, and Invalid. GPG detached signatures (rpgp) carry no key, so
they verify only against a registered key; an unregistered signer is
reported UnknownKey from the signature's issuer fingerprint.

All four SignatureState variants are produced and tested: an in-process
ed25519 key drives the SSH cases (incl. an end-to-end commit_signed round
trip), and a pinned static gpg fixture drives the GPG cases.

rpgp replaces the spec's sequoia-openpgp, which is LGPL and would fail the
license gate (same rationale as avoiding ring); both ssh-key and rpgp are
pure-Rust and MIT/Apache-2.0. KeyKind moves to the model crate. A moka
SignatureCache is provided for the web layer to reuse; expiry/revocation
rejection is a documented best-effort gap. 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
8 files changed · +1786 −6UnifiedSplit
Cargo.lock +1209 −5
@@ -3,6 +3,57 @@
3version = 43version = 4
44
5[[package]]5[[package]]
6name = "adler2"
7version = "2.0.1"
8source = "registry+https://github.com/rust-lang/crates.io-index"
9checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
10
11[[package]]
12name = "aead"
13version = "0.5.2"
14source = "registry+https://github.com/rust-lang/crates.io-index"
15checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
16dependencies = [
17 "bytes",
18 "crypto-common 0.1.6",
19 "generic-array",
20]
21
22[[package]]
23name = "aes"
24version = "0.8.4"
25source = "registry+https://github.com/rust-lang/crates.io-index"
26checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
27dependencies = [
28 "cfg-if",
29 "cipher",
30 "cpufeatures 0.2.17",
31]
32
33[[package]]
34name = "aes-gcm"
35version = "0.10.3"
36source = "registry+https://github.com/rust-lang/crates.io-index"
37checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
38dependencies = [
39 "aead",
40 "aes",
41 "cipher",
42 "ctr",
43 "ghash",
44 "subtle",
45]
46
47[[package]]
48name = "aes-kw"
49version = "0.2.1"
50source = "registry+https://github.com/rust-lang/crates.io-index"
51checksum = "69fa2b352dcefb5f7f3a5fb840e02665d311d878955380515e4fd50095dd3d8c"
52dependencies = [
53 "aes",
54]
55
56[[package]]
6name = "ahash"57name = "ahash"
7version = "0.8.12"58version = "0.8.12"
8source = "registry+https://github.com/rust-lang/crates.io-index"59source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -100,6 +151,7 @@ dependencies = [
100 "blake2",151 "blake2",
101 "cpufeatures 0.2.17",152 "cpufeatures 0.2.17",
102 "password-hash",153 "password-hash",
154 "zeroize",
103]155]
104156
105[[package]]157[[package]]
@@ -142,6 +194,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
142checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"194checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
143195
144[[package]]196[[package]]
197name = "base16ct"
198version = "0.2.0"
199source = "registry+https://github.com/rust-lang/crates.io-index"
200checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
201
202[[package]]
145name = "base64"203name = "base64"
146version = "0.22.1"204version = "0.22.1"
147source = "registry+https://github.com/rust-lang/crates.io-index"205source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -154,6 +212,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
154checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"212checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
155213
156[[package]]214[[package]]
215name = "bitfields"
216version = "1.0.3"
217source = "registry+https://github.com/rust-lang/crates.io-index"
218checksum = "ef6e59298da389bc0649c7463856b34c6e17fe542f88939426ede4436c6b1195"
219dependencies = [
220 "bitfields-impl",
221]
222
223[[package]]
224name = "bitfields-impl"
225version = "1.0.3"
226source = "registry+https://github.com/rust-lang/crates.io-index"
227checksum = "f2c044f98f86f15414668d6c8187c7e4fadab1ad2b31680f648703e0fe07c555"
228dependencies = [
229 "proc-macro2",
230 "quote",
231 "syn 2.0.119",
232 "thiserror 2.0.19",
233]
234
235[[package]]
157name = "bitflags"236name = "bitflags"
158version = "2.13.1"237version = "2.13.1"
159source = "registry+https://github.com/rust-lang/crates.io-index"238source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -163,6 +242,18 @@ dependencies = [
163]242]
164243
165[[package]]244[[package]]
245name = "bitvec"
246version = "1.1.1"
247source = "registry+https://github.com/rust-lang/crates.io-index"
248checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837"
249dependencies = [
250 "funty",
251 "radium",
252 "tap",
253 "wyz",
254]
255
256[[package]]
166name = "blake2"257name = "blake2"
167version = "0.10.6"258version = "0.10.6"
168source = "registry+https://github.com/rust-lang/crates.io-index"259source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -190,6 +281,25 @@ dependencies = [
190]281]
191282
192[[package]]283[[package]]
284name = "block-padding"
285version = "0.3.3"
286source = "registry+https://github.com/rust-lang/crates.io-index"
287checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
288dependencies = [
289 "generic-array",
290]
291
292[[package]]
293name = "blowfish"
294version = "0.9.1"
295source = "registry+https://github.com/rust-lang/crates.io-index"
296checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
297dependencies = [
298 "byteorder",
299 "cipher",
300]
301
302[[package]]
193name = "bstr"303name = "bstr"
194version = "1.13.0"304version = "1.13.0"
195source = "registry+https://github.com/rust-lang/crates.io-index"305source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -200,6 +310,15 @@ dependencies = [
200]310]
201311
202[[package]]312[[package]]
313name = "buffer-redux"
314version = "1.1.0"
315source = "registry+https://github.com/rust-lang/crates.io-index"
316checksum = "431a9cc8d7efa49bc326729264537f5e60affce816c66edf434350778c9f4f54"
317dependencies = [
318 "memchr",
319]
320
321[[package]]
203name = "bumpalo"322name = "bumpalo"
204version = "3.20.3"323version = "3.20.3"
205source = "registry+https://github.com/rust-lang/crates.io-index"324source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -224,6 +343,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
224checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"343checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
225344
226[[package]]345[[package]]
346name = "bzip2"
347version = "0.6.1"
348source = "registry+https://github.com/rust-lang/crates.io-index"
349checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c"
350dependencies = [
351 "libbz2-rs-sys",
352]
353
354[[package]]
355name = "camellia"
356version = "0.1.0"
357source = "registry+https://github.com/rust-lang/crates.io-index"
358checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30"
359dependencies = [
360 "byteorder",
361 "cipher",
362]
363
364[[package]]
365name = "cast5"
366version = "0.11.1"
367source = "registry+https://github.com/rust-lang/crates.io-index"
368checksum = "26b07d673db1ccf000e90f54b819db9e75a8348d6eb056e9b8ab53231b7a9911"
369dependencies = [
370 "cipher",
371]
372
373[[package]]
227name = "cc"374name = "cc"
228version = "1.4.0"375version = "1.4.0"
229source = "registry+https://github.com/rust-lang/crates.io-index"376source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -236,6 +383,15 @@ dependencies = [
236]383]
237384
238[[package]]385[[package]]
386name = "cfb-mode"
387version = "0.8.2"
388source = "registry+https://github.com/rust-lang/crates.io-index"
389checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330"
390dependencies = [
391 "cipher",
392]
393
394[[package]]
239name = "cfg-if"395name = "cfg-if"
240version = "1.0.4"396version = "1.0.4"
241source = "registry+https://github.com/rust-lang/crates.io-index"397source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -253,6 +409,16 @@ dependencies = [
253]409]
254410
255[[package]]411[[package]]
412name = "cipher"
413version = "0.4.4"
414source = "registry+https://github.com/rust-lang/crates.io-index"
415checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
416dependencies = [
417 "crypto-common 0.1.6",
418 "inout",
419]
420
421[[package]]
256name = "clap"422name = "clap"
257version = "4.6.4"423version = "4.6.4"
258source = "registry+https://github.com/rust-lang/crates.io-index"424source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -310,6 +476,17 @@ dependencies = [
310]476]
311477
312[[package]]478[[package]]
479name = "cmac"
480version = "0.7.2"
481source = "registry+https://github.com/rust-lang/crates.io-index"
482checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa"
483dependencies = [
484 "cipher",
485 "dbl",
486 "digest 0.10.7",
487]
488
489[[package]]
313name = "cmov"490name = "cmov"
314version = "0.5.4"491version = "0.5.4"
315source = "registry+https://github.com/rust-lang/crates.io-index"492source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -353,6 +530,21 @@ dependencies = [
353]530]
354531
355[[package]]532[[package]]
533name = "const-oid"
534version = "0.9.6"
535source = "registry+https://github.com/rust-lang/crates.io-index"
536checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
537
538[[package]]
539name = "convert_case"
540version = "0.10.0"
541source = "registry+https://github.com/rust-lang/crates.io-index"
542checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
543dependencies = [
544 "unicode-segmentation",
545]
546
547[[package]]
356name = "cpufeatures"548name = "cpufeatures"
357version = "0.2.17"549version = "0.2.17"
358source = "registry+https://github.com/rust-lang/crates.io-index"550source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -386,6 +578,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
386checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"578checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
387579
388[[package]]580[[package]]
581name = "crc24"
582version = "0.1.6"
583source = "registry+https://github.com/rust-lang/crates.io-index"
584checksum = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0"
585
586[[package]]
389name = "crossbeam-channel"587name = "crossbeam-channel"
390version = "0.5.16"588version = "0.5.16"
391source = "registry+https://github.com/rust-lang/crates.io-index"589source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -419,12 +617,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
419checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"617checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
420618
421[[package]]619[[package]]
620name = "crypto-bigint"
621version = "0.5.5"
622source = "registry+https://github.com/rust-lang/crates.io-index"
623checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
624dependencies = [
625 "generic-array",
626 "rand_core 0.6.4",
627 "subtle",
628 "zeroize",
629]
630
631[[package]]
422name = "crypto-common"632name = "crypto-common"
423version = "0.1.6"633version = "0.1.6"
424source = "registry+https://github.com/rust-lang/crates.io-index"634source = "registry+https://github.com/rust-lang/crates.io-index"
425checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"635checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
426dependencies = [636dependencies = [
427 "generic-array",637 "generic-array",
638 "rand_core 0.6.4",
428 "typenum",639 "typenum",
429]640]
430641
@@ -438,6 +649,15 @@ dependencies = [
438]649]
439650
440[[package]]651[[package]]
652name = "ctr"
653version = "0.9.2"
654source = "registry+https://github.com/rust-lang/crates.io-index"
655checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
656dependencies = [
657 "cipher",
658]
659
660[[package]]
441name = "ctutils"661name = "ctutils"
442version = "0.4.2"662version = "0.4.2"
443source = "registry+https://github.com/rust-lang/crates.io-index"663source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -447,12 +667,175 @@ dependencies = [
447]667]
448668
449[[package]]669[[package]]
670name = "curve25519-dalek"
671version = "4.1.3"
672source = "registry+https://github.com/rust-lang/crates.io-index"
673checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
674dependencies = [
675 "cfg-if",
676 "cpufeatures 0.2.17",
677 "curve25519-dalek-derive",
678 "digest 0.10.7",
679 "fiat-crypto",
680 "rustc_version",
681 "subtle",
682 "zeroize",
683]
684
685[[package]]
686name = "curve25519-dalek-derive"
687version = "0.1.1"
688source = "registry+https://github.com/rust-lang/crates.io-index"
689checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
690dependencies = [
691 "proc-macro2",
692 "quote",
693 "syn 2.0.119",
694]
695
696[[package]]
697name = "cx448"
698version = "0.1.1"
699source = "registry+https://github.com/rust-lang/crates.io-index"
700checksum = "b4c0cf476284b03eb6c10e78787b21c7abb7d7d43cb2f02532ba6b831ed892fa"
701dependencies = [
702 "crypto-bigint",
703 "elliptic-curve",
704 "pkcs8",
705 "rand_core 0.6.4",
706 "serdect 0.3.0",
707 "sha3",
708 "signature",
709 "subtle",
710 "zeroize",
711]
712
713[[package]]
714name = "darling"
715version = "0.20.11"
716source = "registry+https://github.com/rust-lang/crates.io-index"
717checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
718dependencies = [
719 "darling_core",
720 "darling_macro",
721]
722
723[[package]]
724name = "darling_core"
725version = "0.20.11"
726source = "registry+https://github.com/rust-lang/crates.io-index"
727checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
728dependencies = [
729 "fnv",
730 "ident_case",
731 "proc-macro2",
732 "quote",
733 "strsim",
734 "syn 2.0.119",
735]
736
737[[package]]
738name = "darling_macro"
739version = "0.20.11"
740source = "registry+https://github.com/rust-lang/crates.io-index"
741checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
742dependencies = [
743 "darling_core",
744 "quote",
745 "syn 2.0.119",
746]
747
748[[package]]
749name = "dbl"
750version = "0.3.2"
751source = "registry+https://github.com/rust-lang/crates.io-index"
752checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9"
753dependencies = [
754 "generic-array",
755]
756
757[[package]]
758name = "der"
759version = "0.7.10"
760source = "registry+https://github.com/rust-lang/crates.io-index"
761checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
762dependencies = [
763 "const-oid",
764 "pem-rfc7468",
765 "zeroize",
766]
767
768[[package]]
769name = "derive_builder"
770version = "0.20.2"
771source = "registry+https://github.com/rust-lang/crates.io-index"
772checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
773dependencies = [
774 "derive_builder_macro",
775]
776
777[[package]]
778name = "derive_builder_core"
779version = "0.20.2"
780source = "registry+https://github.com/rust-lang/crates.io-index"
781checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
782dependencies = [
783 "darling",
784 "proc-macro2",
785 "quote",
786 "syn 2.0.119",
787]
788
789[[package]]
790name = "derive_builder_macro"
791version = "0.20.2"
792source = "registry+https://github.com/rust-lang/crates.io-index"
793checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
794dependencies = [
795 "derive_builder_core",
796 "syn 2.0.119",
797]
798
799[[package]]
800name = "derive_more"
801version = "2.1.1"
802source = "registry+https://github.com/rust-lang/crates.io-index"
803checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
804dependencies = [
805 "derive_more-impl",
806]
807
808[[package]]
809name = "derive_more-impl"
810version = "2.1.1"
811source = "registry+https://github.com/rust-lang/crates.io-index"
812checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
813dependencies = [
814 "convert_case",
815 "proc-macro2",
816 "quote",
817 "rustc_version",
818 "syn 2.0.119",
819 "unicode-xid",
820]
821
822[[package]]
823name = "des"
824version = "0.8.1"
825source = "registry+https://github.com/rust-lang/crates.io-index"
826checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e"
827dependencies = [
828 "cipher",
829]
830
831[[package]]
450name = "digest"832name = "digest"
451version = "0.10.7"833version = "0.10.7"
452source = "registry+https://github.com/rust-lang/crates.io-index"834source = "registry+https://github.com/rust-lang/crates.io-index"
453checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"835checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
454dependencies = [836dependencies = [
455 "block-buffer 0.10.4",837 "block-buffer 0.10.4",
838 "const-oid",
456 "crypto-common 0.1.6",839 "crypto-common 0.1.6",
457 "subtle",840 "subtle",
458]841]
@@ -486,6 +869,74 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
486checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"869checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
487870
488[[package]]871[[package]]
872name = "dsa"
873version = "0.6.3"
874source = "registry+https://github.com/rust-lang/crates.io-index"
875checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689"
876dependencies = [
877 "digest 0.10.7",
878 "num-bigint-dig",
879 "num-traits",
880 "pkcs8",
881 "rfc6979",
882 "sha2 0.10.9",
883 "signature",
884 "zeroize",
885]
886
887[[package]]
888name = "eax"
889version = "0.5.0"
890source = "registry+https://github.com/rust-lang/crates.io-index"
891checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28"
892dependencies = [
893 "aead",
894 "cipher",
895 "cmac",
896 "ctr",
897 "subtle",
898]
899
900[[package]]
901name = "ecdsa"
902version = "0.16.9"
903source = "registry+https://github.com/rust-lang/crates.io-index"
904checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
905dependencies = [
906 "der",
907 "digest 0.10.7",
908 "elliptic-curve",
909 "rfc6979",
910 "signature",
911 "spki",
912]
913
914[[package]]
915name = "ed25519"
916version = "2.2.3"
917source = "registry+https://github.com/rust-lang/crates.io-index"
918checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
919dependencies = [
920 "pkcs8",
921 "signature",
922]
923
924[[package]]
925name = "ed25519-dalek"
926version = "2.2.0"
927source = "registry+https://github.com/rust-lang/crates.io-index"
928checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
929dependencies = [
930 "curve25519-dalek",
931 "ed25519",
932 "rand_core 0.6.4",
933 "serde",
934 "sha2 0.10.9",
935 "subtle",
936 "zeroize",
937]
938
939[[package]]
489name = "either"940name = "either"
490version = "1.17.0"941version = "1.17.0"
491source = "registry+https://github.com/rust-lang/crates.io-index"942source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -495,6 +946,31 @@ dependencies = [
495]946]
496947
497[[package]]948[[package]]
949name = "elliptic-curve"
950version = "0.13.8"
951source = "registry+https://github.com/rust-lang/crates.io-index"
952checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
953dependencies = [
954 "base16ct",
955 "base64ct",
956 "crypto-bigint",
957 "digest 0.10.7",
958 "ff",
959 "generic-array",
960 "group",
961 "hkdf 0.12.4",
962 "pem-rfc7468",
963 "pkcs8",
964 "rand_core 0.6.4",
965 "sec1",
966 "serde_json",
967 "serdect 0.2.0",
968 "subtle",
969 "tap",
970 "zeroize",
971]
972
973[[package]]
498name = "encode_unicode"974name = "encode_unicode"
499version = "1.0.0"975version = "1.0.0"
500source = "registry+https://github.com/rust-lang/crates.io-index"976source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -551,6 +1027,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
551checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"1027checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
5521028
553[[package]]1029[[package]]
1030name = "ff"
1031version = "0.13.1"
1032source = "registry+https://github.com/rust-lang/crates.io-index"
1033checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
1034dependencies = [
1035 "bitvec",
1036 "rand_core 0.6.4",
1037 "subtle",
1038]
1039
1040[[package]]
1041name = "fiat-crypto"
1042version = "0.2.9"
1043source = "registry+https://github.com/rust-lang/crates.io-index"
1044checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
1045
1046[[package]]
554name = "figment"1047name = "figment"
555version = "0.10.19"1048version = "0.10.19"
556source = "registry+https://github.com/rust-lang/crates.io-index"1049source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -573,6 +1066,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
573checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"1066checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
5741067
575[[package]]1068[[package]]
1069name = "flate2"
1070version = "1.1.9"
1071source = "registry+https://github.com/rust-lang/crates.io-index"
1072checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
1073dependencies = [
1074 "miniz_oxide",
1075 "zlib-rs",
1076]
1077
1078[[package]]
576name = "flume"1079name = "flume"
577version = "0.12.0"1080version = "0.12.0"
578source = "registry+https://github.com/rust-lang/crates.io-index"1081source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -584,6 +1087,12 @@ dependencies = [
584]1087]
5851088
586[[package]]1089[[package]]
1090name = "fnv"
1091version = "1.0.7"
1092source = "registry+https://github.com/rust-lang/crates.io-index"
1093checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
1094
1095[[package]]
587name = "foldhash"1096name = "foldhash"
588version = "0.2.0"1097version = "0.2.0"
589source = "registry+https://github.com/rust-lang/crates.io-index"1098source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -599,6 +1108,12 @@ dependencies = [
599]1108]
6001109
601[[package]]1110[[package]]
1111name = "funty"
1112version = "2.0.0"
1113source = "registry+https://github.com/rust-lang/crates.io-index"
1114checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
1115
1116[[package]]
602name = "futures-channel"1117name = "futures-channel"
603version = "0.3.33"1118version = "0.3.33"
604source = "registry+https://github.com/rust-lang/crates.io-index"1119source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -677,6 +1192,7 @@ checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
677dependencies = [1192dependencies = [
678 "typenum",1193 "typenum",
679 "version_check",1194 "version_check",
1195 "zeroize",
680]1196]
6811197
682[[package]]1198[[package]]
@@ -715,6 +1231,16 @@ dependencies = [
715]1231]
7161232
717[[package]]1233[[package]]
1234name = "ghash"
1235version = "0.5.1"
1236source = "registry+https://github.com/rust-lang/crates.io-index"
1237checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
1238dependencies = [
1239 "opaque-debug",
1240 "polyval",
1241]
1242
1243[[package]]
718name = "git"1244name = "git"
719version = "0.1.0"1245version = "0.1.0"
720dependencies = [1246dependencies = [
@@ -723,6 +1249,9 @@ dependencies = [
723 "libgit2-sys",1249 "libgit2-sys",
724 "model",1250 "model",
725 "moka",1251 "moka",
1252 "pgp",
1253 "rand_core 0.6.4",
1254 "ssh-key",
726 "tempfile",1255 "tempfile",
727 "thiserror 2.0.19",1256 "thiserror 2.0.19",
728]1257]
@@ -754,6 +1283,17 @@ dependencies = [
754]1283]
7551284
756[[package]]1285[[package]]
1286name = "group"
1287version = "0.13.0"
1288source = "registry+https://github.com/rust-lang/crates.io-index"
1289checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
1290dependencies = [
1291 "ff",
1292 "rand_core 0.6.4",
1293 "subtle",
1294]
1295
1296[[package]]
757name = "hashbrown"1297name = "hashbrown"
758version = "0.16.1"1298version = "0.16.1"
759source = "registry+https://github.com/rust-lang/crates.io-index"1299source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -801,6 +1341,15 @@ dependencies = [
8011341
802[[package]]1342[[package]]
803name = "hkdf"1343name = "hkdf"
1344version = "0.12.4"
1345source = "registry+https://github.com/rust-lang/crates.io-index"
1346checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
1347dependencies = [
1348 "hmac 0.12.1",
1349]
1350
1351[[package]]
1352name = "hkdf"
804version = "0.13.0"1353version = "0.13.0"
805source = "registry+https://github.com/rust-lang/crates.io-index"1354source = "registry+https://github.com/rust-lang/crates.io-index"
806checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018"1355checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018"
@@ -917,6 +1466,21 @@ dependencies = [
917]1466]
9181467
919[[package]]1468[[package]]
1469name = "idea"
1470version = "0.5.1"
1471source = "registry+https://github.com/rust-lang/crates.io-index"
1472checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477"
1473dependencies = [
1474 "cipher",
1475]
1476
1477[[package]]
1478name = "ident_case"
1479version = "1.0.1"
1480source = "registry+https://github.com/rust-lang/crates.io-index"
1481checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
1482
1483[[package]]
920name = "idna"1484name = "idna"
921version = "1.1.0"1485version = "1.1.0"
922source = "registry+https://github.com/rust-lang/crates.io-index"1486source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -971,6 +1535,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
971checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"1535checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
9721536
973[[package]]1537[[package]]
1538name = "inout"
1539version = "0.1.4"
1540source = "registry+https://github.com/rust-lang/crates.io-index"
1541checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
1542dependencies = [
1543 "generic-array",
1544]
1545
1546[[package]]
974name = "insta"1547name = "insta"
975version = "1.48.0"1548version = "1.48.0"
976source = "registry+https://github.com/rust-lang/crates.io-index"1549source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1016,10 +1589,42 @@ dependencies = [
1016]1589]
10171590
1018[[package]]1591[[package]]
1592name = "k256"
1593version = "0.13.4"
1594source = "registry+https://github.com/rust-lang/crates.io-index"
1595checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
1596dependencies = [
1597 "cfg-if",
1598 "ecdsa",
1599 "elliptic-curve",
1600 "once_cell",
1601 "sha2 0.10.9",
1602 "signature",
1603]
1604
1605[[package]]
1606name = "keccak"
1607version = "0.1.6"
1608source = "registry+https://github.com/rust-lang/crates.io-index"
1609checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
1610dependencies = [
1611 "cpufeatures 0.2.17",
1612]
1613
1614[[package]]
1019name = "lazy_static"1615name = "lazy_static"
1020version = "1.5.0"1616version = "1.5.0"
1021source = "registry+https://github.com/rust-lang/crates.io-index"1617source = "registry+https://github.com/rust-lang/crates.io-index"
1022checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"1618checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
1619dependencies = [
1620 "spin",
1621]
1622
1623[[package]]
1624name = "libbz2-rs-sys"
1625version = "0.2.5"
1626source = "registry+https://github.com/rust-lang/crates.io-index"
1627checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c"
10231628
1024[[package]]1629[[package]]
1025name = "libc"1630name = "libc"
@@ -1040,6 +1645,12 @@ dependencies = [
1040]1645]
10411646
1042[[package]]1647[[package]]
1648name = "libm"
1649version = "0.2.16"
1650source = "registry+https://github.com/rust-lang/crates.io-index"
1651checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
1652
1653[[package]]
1043name = "libsqlite3-sys"1654name = "libsqlite3-sys"
1044version = "0.37.0"1655version = "0.37.0"
1045source = "registry+https://github.com/rust-lang/crates.io-index"1656source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1095,6 +1706,16 @@ version = "0.1.0"
10951706
1096[[package]]1707[[package]]
1097name = "md-5"1708name = "md-5"
1709version = "0.10.6"
1710source = "registry+https://github.com/rust-lang/crates.io-index"
1711checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
1712dependencies = [
1713 "cfg-if",
1714 "digest 0.10.7",
1715]
1716
1717[[package]]
1718name = "md-5"
1098version = "0.11.0"1719version = "0.11.0"
1099source = "registry+https://github.com/rust-lang/crates.io-index"1720source = "registry+https://github.com/rust-lang/crates.io-index"
1100checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"1721checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
@@ -1110,6 +1731,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1110checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"1731checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
11111732
1112[[package]]1733[[package]]
1734name = "miniz_oxide"
1735version = "0.8.9"
1736source = "registry+https://github.com/rust-lang/crates.io-index"
1737checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
1738dependencies = [
1739 "adler2",
1740 "simd-adler32",
1741]
1742
1743[[package]]
1113name = "mio"1744name = "mio"
1114version = "1.2.2"1745version = "1.2.2"
1115source = "registry+https://github.com/rust-lang/crates.io-index"1746source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1145,12 +1776,91 @@ dependencies = [
1145]1776]
11461777
1147[[package]]1778[[package]]
1779name = "nom"
1780version = "8.0.0"
1781source = "registry+https://github.com/rust-lang/crates.io-index"
1782checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
1783dependencies = [
1784 "memchr",
1785]
1786
1787[[package]]
1788name = "num-bigint-dig"
1789version = "0.8.6"
1790source = "registry+https://github.com/rust-lang/crates.io-index"
1791checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
1792dependencies = [
1793 "lazy_static",
1794 "libm",
1795 "num-integer",
1796 "num-iter",
1797 "num-traits",
1798 "rand 0.8.7",
1799 "serde",
1800 "smallvec",
1801 "zeroize",
1802]
1803
1804[[package]]
1805name = "num-integer"
1806version = "0.1.46"
1807source = "registry+https://github.com/rust-lang/crates.io-index"
1808checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
1809dependencies = [
1810 "num-traits",
1811]
1812
1813[[package]]
1814name = "num-iter"
1815version = "0.1.46"
1816source = "registry+https://github.com/rust-lang/crates.io-index"
1817checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
1818dependencies = [
1819 "num-integer",
1820 "num-traits",
1821]
1822
1823[[package]]
1148name = "num-traits"1824name = "num-traits"
1149version = "0.2.19"1825version = "0.2.19"
1150source = "registry+https://github.com/rust-lang/crates.io-index"1826source = "registry+https://github.com/rust-lang/crates.io-index"
1151checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"1827checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
1152dependencies = [1828dependencies = [
1153 "autocfg",1829 "autocfg",
1830 "libm",
1831]
1832
1833[[package]]
1834name = "num_enum"
1835version = "0.7.6"
1836source = "registry+https://github.com/rust-lang/crates.io-index"
1837checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
1838dependencies = [
1839 "num_enum_derive",
1840 "rustversion",
1841]
1842
1843[[package]]
1844name = "num_enum_derive"
1845version = "0.7.6"
1846source = "registry+https://github.com/rust-lang/crates.io-index"
1847checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
1848dependencies = [
1849 "proc-macro2",
1850 "quote",
1851 "syn 2.0.119",
1852]
1853
1854[[package]]
1855name = "ocb3"
1856version = "0.1.0"
1857source = "registry+https://github.com/rust-lang/crates.io-index"
1858checksum = "c196e0276c471c843dd5777e7543a36a298a4be942a2a688d8111cd43390dedb"
1859dependencies = [
1860 "aead",
1861 "cipher",
1862 "ctr",
1863 "subtle",
1154]1864]
11551865
1156[[package]]1866[[package]]
@@ -1163,7 +1873,51 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
1163name = "once_cell_polyfill"1873name = "once_cell_polyfill"
1164version = "1.70.2"1874version = "1.70.2"
1165source = "registry+https://github.com/rust-lang/crates.io-index"1875source = "registry+https://github.com/rust-lang/crates.io-index"
1166checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"1876checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
1877
1878[[package]]
1879name = "opaque-debug"
1880version = "0.3.1"
1881source = "registry+https://github.com/rust-lang/crates.io-index"
1882checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
1883
1884[[package]]
1885name = "p256"
1886version = "0.13.2"
1887source = "registry+https://github.com/rust-lang/crates.io-index"
1888checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
1889dependencies = [
1890 "ecdsa",
1891 "elliptic-curve",
1892 "primeorder",
1893 "sha2 0.10.9",
1894]
1895
1896[[package]]
1897name = "p384"
1898version = "0.13.1"
1899source = "registry+https://github.com/rust-lang/crates.io-index"
1900checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
1901dependencies = [
1902 "ecdsa",
1903 "elliptic-curve",
1904 "primeorder",
1905 "sha2 0.10.9",
1906]
1907
1908[[package]]
1909name = "p521"
1910version = "0.13.3"
1911source = "registry+https://github.com/rust-lang/crates.io-index"
1912checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2"
1913dependencies = [
1914 "base16ct",
1915 "ecdsa",
1916 "elliptic-curve",
1917 "primeorder",
1918 "rand_core 0.6.4",
1919 "sha2 0.10.9",
1920]
11671921
1168[[package]]1922[[package]]
1169name = "parking"1923name = "parking"
@@ -1229,24 +1983,136 @@ dependencies = [
1229]1983]
12301984
1231[[package]]1985[[package]]
1986name = "pem-rfc7468"
1987version = "0.7.0"
1988source = "registry+https://github.com/rust-lang/crates.io-index"
1989checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
1990dependencies = [
1991 "base64ct",
1992]
1993
1994[[package]]
1232name = "percent-encoding"1995name = "percent-encoding"
1233version = "2.3.2"1996version = "2.3.2"
1234source = "registry+https://github.com/rust-lang/crates.io-index"1997source = "registry+https://github.com/rust-lang/crates.io-index"
1235checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"1998checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
12361999
1237[[package]]2000[[package]]
2001name = "pgp"
2002version = "0.20.0"
2003source = "registry+https://github.com/rust-lang/crates.io-index"
2004checksum = "1cfa4743b28656065ff4c0ba09e46b357a65e8c00fc2341e89084b82f87cbdf1"
2005dependencies = [
2006 "aead",
2007 "aes",
2008 "aes-gcm",
2009 "aes-kw",
2010 "argon2",
2011 "base64",
2012 "bitfields",
2013 "block-padding",
2014 "blowfish",
2015 "buffer-redux",
2016 "byteorder",
2017 "bytes",
2018 "bzip2",
2019 "camellia",
2020 "cast5",
2021 "cfb-mode",
2022 "cipher",
2023 "const-oid",
2024 "crc24",
2025 "curve25519-dalek",
2026 "cx448",
2027 "derive_builder",
2028 "derive_more",
2029 "des",
2030 "digest 0.10.7",
2031 "dsa",
2032 "eax",
2033 "ecdsa",
2034 "ed25519-dalek",
2035 "elliptic-curve",
2036 "flate2",
2037 "generic-array",
2038 "hex",
2039 "hkdf 0.12.4",
2040 "idea",
2041 "k256",
2042 "log",
2043 "md-5 0.10.6",
2044 "memchr",
2045 "nom",
2046 "num-bigint-dig",
2047 "num-traits",
2048 "num_enum",
2049 "ocb3",
2050 "p256",
2051 "p384",
2052 "p521",
2053 "rand 0.8.7",
2054 "replace_with",
2055 "ripemd",
2056 "rsa",
2057 "sha1 0.10.7",
2058 "sha1-checked",
2059 "sha2 0.10.9",
2060 "sha3",
2061 "signature",
2062 "smallvec",
2063 "snafu",
2064 "subtle",
2065 "twofish",
2066 "x25519-dalek",
2067 "zeroize",
2068]
2069
2070[[package]]
1238name = "pin-project-lite"2071name = "pin-project-lite"
1239version = "0.2.17"2072version = "0.2.17"
1240source = "registry+https://github.com/rust-lang/crates.io-index"2073source = "registry+https://github.com/rust-lang/crates.io-index"
1241checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"2074checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
12422075
1243[[package]]2076[[package]]
2077name = "pkcs1"
2078version = "0.7.5"
2079source = "registry+https://github.com/rust-lang/crates.io-index"
2080checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
2081dependencies = [
2082 "der",
2083 "pkcs8",
2084 "spki",
2085]
2086
2087[[package]]
2088name = "pkcs8"
2089version = "0.10.2"
2090source = "registry+https://github.com/rust-lang/crates.io-index"
2091checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
2092dependencies = [
2093 "der",
2094 "spki",
2095]
2096
2097[[package]]
1244name = "pkg-config"2098name = "pkg-config"
1245version = "0.3.33"2099version = "0.3.33"
1246source = "registry+https://github.com/rust-lang/crates.io-index"2100source = "registry+https://github.com/rust-lang/crates.io-index"
1247checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"2101checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
12482102
1249[[package]]2103[[package]]
2104name = "polyval"
2105version = "0.6.2"
2106source = "registry+https://github.com/rust-lang/crates.io-index"
2107checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
2108dependencies = [
2109 "cfg-if",
2110 "cpufeatures 0.2.17",
2111 "opaque-debug",
2112 "universal-hash",
2113]
2114
2115[[package]]
1250name = "portable-atomic"2116name = "portable-atomic"
1251version = "1.14.0"2117version = "1.14.0"
1252source = "registry+https://github.com/rust-lang/crates.io-index"2118source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1271,6 +2137,15 @@ dependencies = [
1271]2137]
12722138
1273[[package]]2139[[package]]
2140name = "primeorder"
2141version = "0.13.6"
2142source = "registry+https://github.com/rust-lang/crates.io-index"
2143checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
2144dependencies = [
2145 "elliptic-curve",
2146]
2147
2148[[package]]
1274name = "proc-macro2"2149name = "proc-macro2"
1275version = "1.0.107"2150version = "1.0.107"
1276source = "registry+https://github.com/rust-lang/crates.io-index"2151source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1314,12 +2189,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1314checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"2189checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
13152190
1316[[package]]2191[[package]]
2192name = "radium"
2193version = "0.7.0"
2194source = "registry+https://github.com/rust-lang/crates.io-index"
2195checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
2196
2197[[package]]
2198name = "rand"
2199version = "0.8.7"
2200source = "registry+https://github.com/rust-lang/crates.io-index"
2201checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
2202dependencies = [
2203 "libc",
2204 "rand_chacha 0.3.1",
2205 "rand_core 0.6.4",
2206]
2207
2208[[package]]
1317name = "rand"2209name = "rand"
1318version = "0.9.5"2210version = "0.9.5"
1319source = "registry+https://github.com/rust-lang/crates.io-index"2211source = "registry+https://github.com/rust-lang/crates.io-index"
1320checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"2212checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
1321dependencies = [2213dependencies = [
1322 "rand_chacha",2214 "rand_chacha 0.9.0",
1323 "rand_core 0.9.5",2215 "rand_core 0.9.5",
1324]2216]
13252217
@@ -1336,6 +2228,16 @@ dependencies = [
13362228
1337[[package]]2229[[package]]
1338name = "rand_chacha"2230name = "rand_chacha"
2231version = "0.3.1"
2232source = "registry+https://github.com/rust-lang/crates.io-index"
2233checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
2234dependencies = [
2235 "ppv-lite86",
2236 "rand_core 0.6.4",
2237]
2238
2239[[package]]
2240name = "rand_chacha"
1339version = "0.9.0"2241version = "0.9.0"
1340source = "registry+https://github.com/rust-lang/crates.io-index"2242source = "registry+https://github.com/rust-lang/crates.io-index"
1341checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"2243checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
@@ -1407,6 +2309,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1407checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"2309checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
14082310
1409[[package]]2311[[package]]
2312name = "replace_with"
2313version = "0.1.8"
2314source = "registry+https://github.com/rust-lang/crates.io-index"
2315checksum = "51743d3e274e2b18df81c4dc6caf8a5b8e15dbe799e0dca05c7617380094e884"
2316
2317[[package]]
2318name = "rfc6979"
2319version = "0.4.0"
2320source = "registry+https://github.com/rust-lang/crates.io-index"
2321checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
2322dependencies = [
2323 "hmac 0.12.1",
2324 "subtle",
2325]
2326
2327[[package]]
2328name = "ripemd"
2329version = "0.1.3"
2330source = "registry+https://github.com/rust-lang/crates.io-index"
2331checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f"
2332dependencies = [
2333 "digest 0.10.7",
2334]
2335
2336[[package]]
1410name = "rpassword"2337name = "rpassword"
1411version = "7.5.4"2338version = "7.5.4"
1412source = "registry+https://github.com/rust-lang/crates.io-index"2339source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1418,6 +2345,27 @@ dependencies = [
1418]2345]
14192346
1420[[package]]2347[[package]]
2348name = "rsa"
2349version = "0.9.10"
2350source = "registry+https://github.com/rust-lang/crates.io-index"
2351checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
2352dependencies = [
2353 "const-oid",
2354 "digest 0.10.7",
2355 "num-bigint-dig",
2356 "num-integer",
2357 "num-traits",
2358 "pkcs1",
2359 "pkcs8",
2360 "rand_core 0.6.4",
2361 "sha2 0.10.9",
2362 "signature",
2363 "spki",
2364 "subtle",
2365 "zeroize",
2366]
2367
2368[[package]]
1421name = "rtoolbox"2369name = "rtoolbox"
1422version = "0.0.5"2370version = "0.0.5"
1423source = "registry+https://github.com/rust-lang/crates.io-index"2371source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1428,6 +2376,15 @@ dependencies = [
1428]2376]
14292377
1430[[package]]2378[[package]]
2379name = "rustc_version"
2380version = "0.4.1"
2381source = "registry+https://github.com/rust-lang/crates.io-index"
2382checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
2383dependencies = [
2384 "semver",
2385]
2386
2387[[package]]
1431name = "rustix"2388name = "rustix"
1432version = "1.1.4"2389version = "1.1.4"
1433source = "registry+https://github.com/rust-lang/crates.io-index"2390source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1453,6 +2410,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1453checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"2410checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
14542411
1455[[package]]2412[[package]]
2413name = "sec1"
2414version = "0.7.3"
2415source = "registry+https://github.com/rust-lang/crates.io-index"
2416checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
2417dependencies = [
2418 "base16ct",
2419 "der",
2420 "generic-array",
2421 "pkcs8",
2422 "serdect 0.2.0",
2423 "subtle",
2424 "zeroize",
2425]
2426
2427[[package]]
2428name = "semver"
2429version = "1.0.28"
2430source = "registry+https://github.com/rust-lang/crates.io-index"
2431checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
2432
2433[[package]]
1456name = "serde"2434name = "serde"
1457version = "1.0.229"2435version = "1.0.229"
1458source = "registry+https://github.com/rust-lang/crates.io-index"2436source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1505,6 +2483,37 @@ dependencies = [
1505]2483]
15062484
1507[[package]]2485[[package]]
2486name = "serdect"
2487version = "0.2.0"
2488source = "registry+https://github.com/rust-lang/crates.io-index"
2489checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177"
2490dependencies = [
2491 "base16ct",
2492 "serde",
2493]
2494
2495[[package]]
2496name = "serdect"
2497version = "0.3.0"
2498source = "registry+https://github.com/rust-lang/crates.io-index"
2499checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53"
2500dependencies = [
2501 "base16ct",
2502 "serde",
2503]
2504
2505[[package]]
2506name = "sha1"
2507version = "0.10.7"
2508source = "registry+https://github.com/rust-lang/crates.io-index"
2509checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
2510dependencies = [
2511 "cfg-if",
2512 "cpufeatures 0.2.17",
2513 "digest 0.10.7",
2514]
2515
2516[[package]]
1508name = "sha1"2517name = "sha1"
1509version = "0.11.0"2518version = "0.11.0"
1510source = "registry+https://github.com/rust-lang/crates.io-index"2519source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1516,6 +2525,17 @@ dependencies = [
1516]2525]
15172526
1518[[package]]2527[[package]]
2528name = "sha1-checked"
2529version = "0.10.0"
2530source = "registry+https://github.com/rust-lang/crates.io-index"
2531checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423"
2532dependencies = [
2533 "digest 0.10.7",
2534 "sha1 0.10.7",
2535 "zeroize",
2536]
2537
2538[[package]]
1519name = "sha2"2539name = "sha2"
1520version = "0.10.9"2540version = "0.10.9"
1521source = "registry+https://github.com/rust-lang/crates.io-index"2541source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1538,12 +2558,38 @@ dependencies = [
1538]2558]
15392559
1540[[package]]2560[[package]]
2561name = "sha3"
2562version = "0.10.9"
2563source = "registry+https://github.com/rust-lang/crates.io-index"
2564checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874"
2565dependencies = [
2566 "digest 0.10.7",
2567 "keccak",
2568]
2569
2570[[package]]
1541name = "shlex"2571name = "shlex"
1542version = "2.0.1"2572version = "2.0.1"
1543source = "registry+https://github.com/rust-lang/crates.io-index"2573source = "registry+https://github.com/rust-lang/crates.io-index"
1544checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"2574checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
15452575
1546[[package]]2576[[package]]
2577name = "signature"
2578version = "2.2.0"
2579source = "registry+https://github.com/rust-lang/crates.io-index"
2580checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
2581dependencies = [
2582 "digest 0.10.7",
2583 "rand_core 0.6.4",
2584]
2585
2586[[package]]
2587name = "simd-adler32"
2588version = "0.3.10"
2589source = "registry+https://github.com/rust-lang/crates.io-index"
2590checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
2591
2592[[package]]
1547name = "similar"2593name = "similar"
1548version = "2.7.0"2594version = "2.7.0"
1549source = "registry+https://github.com/rust-lang/crates.io-index"2595source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1565,6 +2611,27 @@ dependencies = [
1565]2611]
15662612
1567[[package]]2613[[package]]
2614name = "snafu"
2615version = "0.9.2"
2616source = "registry+https://github.com/rust-lang/crates.io-index"
2617checksum = "e45cb604038abb7b926b679887b3226d8d0f23874b66623625a0454be425a4b7"
2618dependencies = [
2619 "snafu-derive",
2620]
2621
2622[[package]]
2623name = "snafu-derive"
2624version = "0.9.2"
2625source = "registry+https://github.com/rust-lang/crates.io-index"
2626checksum = "287f59010008f0d7cf5e3b03196d666c1acc46c8d3e9cf34c28a1a7157601e72"
2627dependencies = [
2628 "heck",
2629 "proc-macro2",
2630 "quote",
2631 "syn 2.0.119",
2632]
2633
2634[[package]]
1568name = "socket2"2635name = "socket2"
1569version = "0.6.5"2636version = "0.6.5"
1570source = "registry+https://github.com/rust-lang/crates.io-index"2637source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1584,6 +2651,16 @@ dependencies = [
1584]2651]
15852652
1586[[package]]2653[[package]]
2654name = "spki"
2655version = "0.7.3"
2656source = "registry+https://github.com/rust-lang/crates.io-index"
2657checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
2658dependencies = [
2659 "base64ct",
2660 "der",
2661]
2662
2663[[package]]
1587name = "sqlx"2664name = "sqlx"
1588version = "0.9.0"2665version = "0.9.0"
1589source = "registry+https://github.com/rust-lang/crates.io-index"2666source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1687,7 +2764,7 @@ dependencies = [
1687 "log",2764 "log",
1688 "percent-encoding",2765 "percent-encoding",
1689 "serde",2766 "serde",
1690 "sha1",2767 "sha1 0.11.0",
1691 "sha2 0.11.0",2768 "sha2 0.11.0",
1692 "sqlx-core",2769 "sqlx-core",
1693 "thiserror 2.0.19",2770 "thiserror 2.0.19",
@@ -1711,11 +2788,11 @@ dependencies = [
1711 "futures-core",2788 "futures-core",
1712 "futures-util",2789 "futures-util",
1713 "hex",2790 "hex",
1714 "hkdf",2791 "hkdf 0.13.0",
1715 "hmac 0.13.0",2792 "hmac 0.13.0",
1716 "itoa",2793 "itoa",
1717 "log",2794 "log",
1718 "md-5",2795 "md-5 0.11.0",
1719 "memchr",2796 "memchr",
1720 "rand 0.10.2",2797 "rand 0.10.2",
1721 "serde",2798 "serde",
@@ -1758,6 +2835,49 @@ name = "ssh"
1758version = "0.1.0"2835version = "0.1.0"
17592836
1760[[package]]2837[[package]]
2838name = "ssh-cipher"
2839version = "0.2.0"
2840source = "registry+https://github.com/rust-lang/crates.io-index"
2841checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f"
2842dependencies = [
2843 "cipher",
2844 "ssh-encoding",
2845]
2846
2847[[package]]
2848name = "ssh-encoding"
2849version = "0.2.0"
2850source = "registry+https://github.com/rust-lang/crates.io-index"
2851checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15"
2852dependencies = [
2853 "base64ct",
2854 "pem-rfc7468",
2855 "sha2 0.10.9",
2856]
2857
2858[[package]]
2859name = "ssh-key"
2860version = "0.6.7"
2861source = "registry+https://github.com/rust-lang/crates.io-index"
2862checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3"
2863dependencies = [
2864 "ed25519-dalek",
2865 "num-bigint-dig",
2866 "p256",
2867 "p384",
2868 "p521",
2869 "rand_core 0.6.4",
2870 "rsa",
2871 "sec1",
2872 "sha2 0.10.9",
2873 "signature",
2874 "ssh-cipher",
2875 "ssh-encoding",
2876 "subtle",
2877 "zeroize",
2878]
2879
2880[[package]]
1761name = "stable_deref_trait"2881name = "stable_deref_trait"
1762version = "1.2.1"2882version = "1.2.1"
1763source = "registry+https://github.com/rust-lang/crates.io-index"2883source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1838,6 +2958,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1838checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"2958checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
18392959
1840[[package]]2960[[package]]
2961name = "tap"
2962version = "1.0.1"
2963source = "registry+https://github.com/rust-lang/crates.io-index"
2964checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
2965
2966[[package]]
1841name = "tempfile"2967name = "tempfile"
1842version = "3.27.0"2968version = "3.27.0"
1843source = "registry+https://github.com/rust-lang/crates.io-index"2969source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2056,6 +3182,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2056checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782"3182checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782"
20573183
2058[[package]]3184[[package]]
3185name = "twofish"
3186version = "0.7.1"
3187source = "registry+https://github.com/rust-lang/crates.io-index"
3188checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013"
3189dependencies = [
3190 "cipher",
3191]
3192
3193[[package]]
2059name = "typenum"3194name = "typenum"
2060version = "1.20.1"3195version = "1.20.1"
2061source = "registry+https://github.com/rust-lang/crates.io-index"3196source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2108,6 +3243,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2108checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"3243checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
21093244
2110[[package]]3245[[package]]
3246name = "unicode-segmentation"
3247version = "1.13.3"
3248source = "registry+https://github.com/rust-lang/crates.io-index"
3249checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
3250
3251[[package]]
3252name = "unicode-xid"
3253version = "0.2.6"
3254source = "registry+https://github.com/rust-lang/crates.io-index"
3255checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
3256
3257[[package]]
3258name = "universal-hash"
3259version = "0.5.1"
3260source = "registry+https://github.com/rust-lang/crates.io-index"
3261checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
3262dependencies = [
3263 "crypto-common 0.1.6",
3264 "subtle",
3265]
3266
3267[[package]]
2111name = "url"3268name = "url"
2112version = "2.5.8"3269version = "2.5.8"
2113source = "registry+https://github.com/rust-lang/crates.io-index"3270source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2350,6 +3507,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2350checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"3507checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
23513508
2352[[package]]3509[[package]]
3510name = "wyz"
3511version = "0.5.1"
3512source = "registry+https://github.com/rust-lang/crates.io-index"
3513checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
3514dependencies = [
3515 "tap",
3516]
3517
3518[[package]]
3519name = "x25519-dalek"
3520version = "2.0.1"
3521source = "registry+https://github.com/rust-lang/crates.io-index"
3522checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
3523dependencies = [
3524 "curve25519-dalek",
3525 "rand_core 0.6.4",
3526 "serde",
3527 "zeroize",
3528]
3529
3530[[package]]
2353name = "yansi"3531name = "yansi"
2354version = "1.0.1"3532version = "1.0.1"
2355source = "registry+https://github.com/rust-lang/crates.io-index"3533source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2420,6 +3598,26 @@ dependencies = [
2420]3598]
24213599
2422[[package]]3600[[package]]
3601name = "zeroize"
3602version = "1.9.0"
3603source = "registry+https://github.com/rust-lang/crates.io-index"
3604checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
3605dependencies = [
3606 "zeroize_derive",
3607]
3608
3609[[package]]
3610name = "zeroize_derive"
3611version = "1.5.0"
3612source = "registry+https://github.com/rust-lang/crates.io-index"
3613checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
3614dependencies = [
3615 "proc-macro2",
3616 "quote",
3617 "syn 2.0.119",
3618]
3619
3620[[package]]
2423name = "zerotrie"3621name = "zerotrie"
2424version = "0.2.4"3622version = "0.2.4"
2425source = "registry+https://github.com/rust-lang/crates.io-index"3623source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2453,6 +3651,12 @@ dependencies = [
2453]3651]
24543652
2455[[package]]3653[[package]]
3654name = "zlib-rs"
3655version = "0.6.6"
3656source = "registry+https://github.com/rust-lang/crates.io-index"
3657checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5"
3658
3659[[package]]
2456name = "zmij"3660name = "zmij"
2457version = "1.0.23"3661version = "1.0.23"
2458source = "registry+https://github.com/rust-lang/crates.io-index"3662source = "registry+https://github.com/rust-lang/crates.io-index"
Cargo.toml +5 −0
@@ -96,6 +96,11 @@ moka = { version = "0.12", features = ["sync"] }
96inkjet = "0.11.1"96inkjet = "0.11.1"
97# Snapshot testing.97# Snapshot testing.
98insta = "1"98insta = "1"
99# Signature verification. Both are pure-Rust (ed25519-dalek/rsa/p256, no `ring`,
100# no OpenSSL) and MIT/Apache-2.0 — sequoia-openpgp is deliberately avoided as it
101# is LGPL, which our license gate forbids (see docs/decisions.md).
102ssh-key = { version = "0.6.7", features = ["ed25519", "rsa", "p256", "std"] }
103pgp = "0.20"
99104
100[workspace.lints.rust]105[workspace.lints.rust]
101unsafe_code = "forbid"106unsafe_code = "forbid"
crates/git/Cargo.toml +4 −0
@@ -14,6 +14,8 @@ model = { workspace = true }
14git2 = { workspace = true }14git2 = { workspace = true }
15globset = { workspace = true }15globset = { workspace = true }
16moka = { workspace = true }16moka = { workspace = true }
17ssh-key = { workspace = true }
18pgp = { workspace = true }
17thiserror = { workspace = true }19thiserror = { workspace = true }
18# Pin the libgit2 bindings to a build that requires libgit2 <= the version20# Pin the libgit2 bindings to a build that requires libgit2 <= the version
19# nixpkgs ships (1.9.4). git2 0.20's default libgit2-sys (0.18.7, bundling 1.9.6)21# nixpkgs ships (1.9.4). git2 0.20's default libgit2-sys (0.18.7, bundling 1.9.6)
@@ -24,6 +26,8 @@ libgit2-sys = "=0.18.3"
2426
25[dev-dependencies]27[dev-dependencies]
26tempfile = { workspace = true }28tempfile = { workspace = true }
29# Random key generation for the SSH signature fixtures (PrivateKey::random).
30rand_core = { workspace = true }
2731
28[lints]32[lints]
29workspace = true33workspace = true
crates/git/src/lib.rs +2 −0
@@ -29,6 +29,7 @@
2929
30mod attributes;30mod attributes;
31mod repo;31mod repo;
32mod signature;
32mod types;33mod types;
3334
34use std::fs;35use std::fs;
@@ -40,6 +41,7 @@ use git2::{Repository, RepositoryInitOptions};
4041
41pub use crate::attributes::{AttrValue, Attributes, AttributesCache, AttributesSet};42pub use crate::attributes::{AttrValue, Attributes, AttributesCache, AttributesSet};
42pub use crate::repo::Repo;43pub use crate::repo::Repo;
44pub use crate::signature::{SignatureCache, SignatureState, SigningKey};
43pub use crate::types::{45pub use crate::types::{
44 Blob, BranchInfo, CommitDetail, CommitSummary, DeltaStatus, DiffLine, DiffOpts, EntryKind,46 Blob, BranchInfo, CommitDetail, CommitSummary, DeltaStatus, DiffLine, DiffOpts, EntryKind,
45 FileDiff, FileDiffs, Hunk, LineOrigin, Oid, Page, Person, RefKind, Resolved, TagInfo,47 FileDiff, FileDiffs, Hunk, LineOrigin, Oid, Page, Person, RefKind, Resolved, TagInfo,
crates/git/src/signature.rs +492 −0
@@ -0,0 +1,492 @@
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//! Commit and tag signature verification (§3.6).
6//!
7//! git records a signature in the object's `gpgsig` header; libgit2's
8//! `extract_signature` hands back the armored signature and the exact payload it
9//! covers. We dispatch on the armor banner:
10//!
11//! * `-----BEGIN SSH SIGNATURE-----` → parsed with `ssh-key` and verified with
12//! namespace `"git"`. An SSH signature embeds its own public key, so we can
13//! always tell a cryptographically valid signature from a broken one and only
14//! then ask whether the key is registered.
15//! * `-----BEGIN PGP SIGNATURE-----` → parsed with `pgp` (rpgp) and verified as a
16//! detached signature over the payload. A PGP detached signature does **not**
17//! embed the key, so an unregistered signer cannot be cryptographically checked;
18//! we report it from the signature's issuer fingerprint.
19//!
20//! The four [`SignatureState`] outcomes are never collapsed: `Verified` is a valid
21//! signature by a registered key, `UnknownKey` a signature whose key we do not
22//! hold, `Invalid` a present-but-broken (or expired) signature, and `Unsigned` no
23//! signature at all.
24
25use std::sync::Arc;
26
27use model::KeyKind;
28use pgp::composed::{Deserializable, DetachedSignature, SignedPublicKey};
29use pgp::types::KeyDetails;
30use ssh_key::{HashAlg, PublicKey, SshSig};
31
32use crate::repo::Repo;
33use crate::types::Oid;
34
35/// A registered public key, as the store supplies it, used to attribute a verified
36/// signature back to its owner.
37#[derive(Debug, Clone)]
38pub struct SigningKey {
39 /// The owning user's id.
40 pub user_id: String,
41 /// The key's own id.
42 pub key_id: String,
43 /// SSH or GPG.
44 pub kind: KeyKind,
45 /// The stored fingerprint: `SHA256:…` for SSH, hex for GPG.
46 pub fingerprint: String,
47 /// The stored public key: an `authorized_keys` line (SSH) or an ASCII-armored
48 /// block (GPG).
49 pub public_key: String,
50}
51
52/// The outcome of verifying an object's signature.
53///
54/// The UI renders `Verified` as a green badge and every other carrying-a-signature
55/// case as an amber "Unverified" badge; the detail line distinguishes them. Ids are
56/// `String` ULIDs (the spec sketch used `Ulid`; the git layer has no ULID
57/// dependency — see docs/decisions.md).
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum SignatureState {
60 /// No signature is present.
61 Unsigned,
62 /// A valid signature by a key registered on this instance.
63 Verified {
64 /// The owning user's id.
65 user_id: String,
66 /// The registered key's id.
67 key_id: String,
68 /// SSH or GPG.
69 kind: KeyKind,
70 /// The verified key's fingerprint.
71 fingerprint: String,
72 },
73 /// A valid-looking signature whose key is not registered here.
74 UnknownKey {
75 /// SSH or GPG.
76 kind: KeyKind,
77 /// The signer's fingerprint, as far as it could be determined.
78 fingerprint: String,
79 },
80 /// A signature that is present but cryptographically invalid, malformed, or
81 /// expired.
82 Invalid {
83 /// The signature kind, if the armor identified one.
84 kind: Option<KeyKind>,
85 /// A human-readable reason for the log/detail line.
86 reason: String,
87 },
88}
89
90impl Repo {
91 /// Verify the signature on the object `oid` (a commit or annotated tag) against
92 /// the registered `keys`.
93 ///
94 /// Never fails: an absent or unreadable signature is reported as
95 /// [`SignatureState::Unsigned`], and any parse/verify problem as
96 /// [`SignatureState::Invalid`].
97 #[must_use]
98 pub fn signature_state(&self, oid: &Oid, keys: &[SigningKey]) -> SignatureState {
99 let Ok(git_oid) = git2::Oid::from_str(oid.as_str()) else {
100 return SignatureState::Unsigned;
101 };
102 // `extract_signature` returns NotFound when the object carries no signature.
103 let Ok((sig, payload)) = self.raw().extract_signature(&git_oid, None) else {
104 return SignatureState::Unsigned;
105 };
106 let armor = String::from_utf8_lossy(&sig);
107 if armor.contains("BEGIN SSH SIGNATURE") {
108 verify_ssh(&armor, &payload, keys)
109 } else if armor.contains("BEGIN PGP SIGNATURE") {
110 verify_gpg(&armor, &payload, keys)
111 } else {
112 SignatureState::Invalid {
113 kind: None,
114 reason: "unrecognized signature format".to_string(),
115 }
116 }
117 }
118}
119
120/// Verify an SSH signature over `payload`.
121///
122/// The signature embeds its signing key, so we verify against that key and then
123/// classify by whether its SHA256 fingerprint is registered.
124pub(crate) fn verify_ssh(armor: &str, payload: &[u8], keys: &[SigningKey]) -> SignatureState {
125 let sig = match SshSig::from_pem(armor.as_bytes()) {
126 Ok(sig) => sig,
127 Err(err) => {
128 return SignatureState::Invalid {
129 kind: Some(KeyKind::Ssh),
130 reason: format!("malformed SSH signature: {err}"),
131 };
132 }
133 };
134 // The SSHSIG format only carries sha256/sha512; the type guarantees it, so
135 // there is no other hash algorithm to reject.
136 debug_assert!(matches!(sig.hash_alg(), HashAlg::Sha256 | HashAlg::Sha512));
137
138 let embedded: PublicKey = sig.public_key().clone().into();
139 if let Err(err) = embedded.verify("git", payload, &sig) {
140 return SignatureState::Invalid {
141 kind: Some(KeyKind::Ssh),
142 reason: format!("SSH signature verification failed: {err}"),
143 };
144 }
145
146 let fingerprint = embedded.fingerprint(HashAlg::Sha256).to_string();
147 match keys
148 .iter()
149 .find(|k| k.kind == KeyKind::Ssh && ssh_fingerprint_matches(&k.fingerprint, &fingerprint))
150 {
151 Some(key) => SignatureState::Verified {
152 user_id: key.user_id.clone(),
153 key_id: key.key_id.clone(),
154 kind: KeyKind::Ssh,
155 fingerprint,
156 },
157 None => SignatureState::UnknownKey {
158 kind: KeyKind::Ssh,
159 fingerprint,
160 },
161 }
162}
163
164/// Verify a GPG detached signature over `payload`.
165///
166/// A detached PGP signature carries no key, so verification is only possible
167/// against a registered key. If none verifies, we distinguish a broken signature
168/// from a known key ([`SignatureState::Invalid`]) from an unregistered signer
169/// ([`SignatureState::UnknownKey`]) by the signature's issuer fingerprint.
170pub(crate) fn verify_gpg(armor: &str, payload: &[u8], keys: &[SigningKey]) -> SignatureState {
171 let detached = match DetachedSignature::from_string(armor) {
172 Ok((detached, _)) => detached,
173 Err(err) => {
174 return SignatureState::Invalid {
175 kind: Some(KeyKind::Gpg),
176 reason: format!("malformed PGP signature: {err}"),
177 };
178 }
179 };
180
181 let issuer = detached
182 .signature
183 .issuer_fingerprint()
184 .first()
185 .map(|fp| format!("{fp:X}"));
186
187 let mut known_issuer = false;
188 for key in keys.iter().filter(|k| k.kind == KeyKind::Gpg) {
189 let Ok((public_key, _)) = SignedPublicKey::from_string(&key.public_key) else {
190 continue;
191 };
192 let key_fpr = format!("{:X}", public_key.fingerprint());
193 if let Some(issuer) = &issuer
194 && gpg_fingerprint_matches(&key_fpr, issuer)
195 {
196 known_issuer = true;
197 }
198 let verified = detached.verify(&public_key, payload).is_ok()
199 || public_key
200 .public_subkeys
201 .iter()
202 .any(|sub| detached.verify(sub, payload).is_ok());
203 if verified {
204 return SignatureState::Verified {
205 user_id: key.user_id.clone(),
206 key_id: key.key_id.clone(),
207 kind: KeyKind::Gpg,
208 fingerprint: normalize_hex(&key.fingerprint),
209 };
210 }
211 }
212
213 let fingerprint = issuer.map(|fp| normalize_hex(&fp)).unwrap_or_default();
214 if known_issuer {
215 SignatureState::Invalid {
216 kind: Some(KeyKind::Gpg),
217 reason: "PGP signature does not verify against the registered key".to_string(),
218 }
219 } else {
220 SignatureState::UnknownKey {
221 kind: KeyKind::Gpg,
222 fingerprint,
223 }
224 }
225}
226
227/// Compare two SSH fingerprints. The `SHA256:<base64>` body is case-sensitive, so
228/// only surrounding whitespace is trimmed.
229fn ssh_fingerprint_matches(stored: &str, computed: &str) -> bool {
230 stored.trim() == computed.trim()
231}
232
233/// Compare two GPG fingerprints, ignoring case, spaces, and `0x`/colon separators.
234fn gpg_fingerprint_matches(a: &str, b: &str) -> bool {
235 normalize_hex(a) == normalize_hex(b)
236}
237
238/// Reduce a hex fingerprint to lowercase hex digits only, so representations that
239/// differ by case, spaces, or separators compare equal.
240fn normalize_hex(fp: &str) -> String {
241 fp.chars()
242 .filter(char::is_ascii_hexdigit)
243 .map(|c| c.to_ascii_lowercase())
244 .collect()
245}
246
247/// A bounded cache of verification results keyed by object oid.
248///
249/// Verification is expensive, so list views reuse results across renders. The
250/// result depends on the registered key set, so the web layer **must** clear the
251/// cache when keys change (it already bumps `cache_epoch` on such mutations).
252#[derive(Clone)]
253pub struct SignatureCache {
254 inner: moka::sync::Cache<String, Arc<SignatureState>>,
255}
256
257impl SignatureCache {
258 /// A cache holding up to `capacity` results.
259 #[must_use]
260 pub fn new(capacity: u64) -> Self {
261 Self {
262 inner: moka::sync::Cache::new(capacity),
263 }
264 }
265
266 /// Fetch or compute the signature state for `oid`.
267 #[must_use]
268 pub fn get(&self, repo: &Repo, oid: &Oid, keys: &[SigningKey]) -> Arc<SignatureState> {
269 if let Some(hit) = self.inner.get(oid.as_str()) {
270 return hit;
271 }
272 let state = Arc::new(repo.signature_state(oid, keys));
273 self.inner.insert(oid.to_string(), Arc::clone(&state));
274 state
275 }
276
277 /// Drop every cached result. Call after any key mutation.
278 pub fn clear(&self) {
279 self.inner.invalidate_all();
280 }
281}
282
283impl Default for SignatureCache {
284 fn default() -> Self {
285 Self::new(1024)
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
292
293 use rand_core::OsRng;
294 use ssh_key::{Algorithm, LineEnding, PrivateKey};
295
296 use super::*;
297
298 /// A GPG key, its detached signature over [`GPG_PAYLOAD`], and its fingerprint,
299 /// generated once with `gpg` and pinned here (§14: ship a static GPG fixture).
300 const GPG_PUB: &str = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n\
301mDMEamQrVhYJKwYBBAHaRw8BAQdAZmnSAPnE0i9ZFD376jPvfybRQnCVgdZznu/Z\n\
302xu9NMc+0I0ZhYnJpY2EgVGVzdCA8dGVzdEBmYWJyaWNhLmV4YW1wbGU+iJAEExYK\n\
303ADgWIQRCw0r0w7/IPhyGEPWZoNNqXdKXKAUCamQrVgIbAwULCQgHAgYVCgkICwIE\n\
304FgIDAQIeAQIXgAAKCRCZoNNqXdKXKHT3APkBBY7MB1be0vsOf0Krr5+mLzStHwjQ\n\
305ZhFF/DtG/1L+sgD+KcKERhLj5L5b/FHBxYGpw2ZY4aQoCOeGmk+zmflLbwS4OARq\n\
306ZCtWEgorBgEEAZdVAQUBAQdAeLjNUQ2tEAUDtr+Iu7qa/EOArrAd3QSWKHNWXfX7\n\
3074lIDAQgHiHgEGBYKACAWIQRCw0r0w7/IPhyGEPWZoNNqXdKXKAUCamQrVgIbDAAK\n\
308CRCZoNNqXdKXKJIEAP9Z2KdPQ7yVGAC6XjDoYKbbeAu58G4L4hqiq+JK6v18lwD/\n\
309T4OoXtnXPpwpy5jv0KTtE+cjQCl1W2qhQTFSqlXeawM=\n\
310=EOde\n\
311-----END PGP PUBLIC KEY BLOCK-----\n";
312
313 const GPG_SIG: &str = "-----BEGIN PGP SIGNATURE-----\n\n\
314iHUEABYKAB0WIQRCw0r0w7/IPhyGEPWZoNNqXdKXKAUCamQrVgAKCRCZoNNqXdKX\n\
315KCYOAQCaiD7VrRkDvxqwatypcp/0xfY9oc3OclJVa+rFVkUNMQD9HxnEfelG+hwU\n\
316MiNTlcoHWPclgdKH8OdrpfUmIIjFXAE=\n\
317=lUCx\n\
318-----END PGP SIGNATURE-----\n";
319
320 const GPG_PAYLOAD: &[u8] = b"fabrica signed payload\nsecond line\n";
321 const GPG_FPR: &str = "42c34af4c3bfc83e1c8610f599a0d36a5dd29728";
322
323 fn gpg_key() -> SigningKey {
324 SigningKey {
325 user_id: "user-1".into(),
326 key_id: "key-1".into(),
327 kind: KeyKind::Gpg,
328 fingerprint: GPG_FPR.into(),
329 public_key: GPG_PUB.into(),
330 }
331 }
332
333 /// Generate an ed25519 SSH key, its `authorized_keys` line, and its SHA256
334 /// fingerprint, entirely in-process.
335 fn ssh_key() -> (PrivateKey, SigningKey) {
336 let private = PrivateKey::random(&mut OsRng, Algorithm::Ed25519).unwrap();
337 let public = private.public_key();
338 let key = SigningKey {
339 user_id: "user-2".into(),
340 key_id: "key-2".into(),
341 kind: KeyKind::Ssh,
342 fingerprint: public.fingerprint(HashAlg::Sha256).to_string(),
343 public_key: public.to_openssh().unwrap(),
344 };
345 (private, key)
346 }
347
348 fn sign_ssh(private: &PrivateKey, payload: &[u8]) -> String {
349 private
350 .sign("git", HashAlg::Sha512, payload)
351 .unwrap()
352 .to_pem(LineEnding::LF)
353 .unwrap()
354 }
355
356 #[test]
357 fn ssh_verified_unknown_and_invalid() {
358 let (private, key) = ssh_key();
359 let payload = b"tree deadbeef\nauthor a\n\nmessage\n";
360 let armor = sign_ssh(&private, payload);
361
362 // Registered → Verified.
363 match verify_ssh(&armor, payload, std::slice::from_ref(&key)) {
364 SignatureState::Verified {
365 kind, fingerprint, ..
366 } => {
367 assert_eq!(kind, KeyKind::Ssh);
368 assert_eq!(fingerprint, key.fingerprint);
369 }
370 other => panic!("expected Verified, got {other:?}"),
371 }
372
373 // Valid signature, no registered key → UnknownKey.
374 match verify_ssh(&armor, payload, &[]) {
375 SignatureState::UnknownKey { kind, fingerprint } => {
376 assert_eq!(kind, KeyKind::Ssh);
377 assert_eq!(fingerprint, key.fingerprint);
378 }
379 other => panic!("expected UnknownKey, got {other:?}"),
380 }
381
382 // Wrong payload → Invalid, even with the key registered.
383 match verify_ssh(&armor, b"different payload", std::slice::from_ref(&key)) {
384 SignatureState::Invalid { kind, .. } => assert_eq!(kind, Some(KeyKind::Ssh)),
385 other => panic!("expected Invalid, got {other:?}"),
386 }
387 }
388
389 #[test]
390 fn gpg_verified_unknown_and_invalid() {
391 // Registered → Verified.
392 match verify_gpg(GPG_SIG, GPG_PAYLOAD, &[gpg_key()]) {
393 SignatureState::Verified {
394 kind, fingerprint, ..
395 } => {
396 assert_eq!(kind, KeyKind::Gpg);
397 assert_eq!(fingerprint, GPG_FPR);
398 }
399 other => panic!("expected Verified, got {other:?}"),
400 }
401
402 // No registered key → UnknownKey with the issuer fingerprint.
403 match verify_gpg(GPG_SIG, GPG_PAYLOAD, &[]) {
404 SignatureState::UnknownKey { kind, fingerprint } => {
405 assert_eq!(kind, KeyKind::Gpg);
406 assert_eq!(fingerprint, GPG_FPR);
407 }
408 other => panic!("expected UnknownKey, got {other:?}"),
409 }
410
411 // Registered key but tampered payload → Invalid.
412 match verify_gpg(GPG_SIG, b"tampered", &[gpg_key()]) {
413 SignatureState::Invalid { kind, .. } => assert_eq!(kind, Some(KeyKind::Gpg)),
414 other => panic!("expected Invalid, got {other:?}"),
415 }
416 }
417
418 #[test]
419 fn malformed_armor_is_invalid() {
420 match verify_ssh("-----BEGIN SSH SIGNATURE-----\nnonsense\n", b"x", &[]) {
421 SignatureState::Invalid { kind, .. } => assert_eq!(kind, Some(KeyKind::Ssh)),
422 other => panic!("expected Invalid, got {other:?}"),
423 }
424 }
425
426 #[test]
427 fn unsigned_commit_reports_unsigned() {
428 use std::path::Path;
429
430 use git2::{Repository, Signature, Time};
431
432 use crate::{create_bare, repo_path};
433
434 let dir = tempfile::tempdir().unwrap();
435 let id = "01hzxk9m2n8p7q6r5s4t3v2w1x";
436 create_bare(dir.path(), id, "main", Path::new("/usr/bin/fabrica")).unwrap();
437 let path = repo_path(dir.path(), id).unwrap();
438 let raw = Repository::open_bare(&path).unwrap();
439
440 let blob = raw.blob(b"hi\n").unwrap();
441 let mut tb = raw.treebuilder(None).unwrap();
442 tb.insert("f", blob, 0o100_644).unwrap();
443 let tree = raw.find_tree(tb.write().unwrap()).unwrap();
444 let sig = Signature::new("Ada", "ada@example.com", &Time::new(1, 0)).unwrap();
445 let oid = raw
446 .commit(Some("refs/heads/main"), &sig, &sig, "unsigned", &tree, &[])
447 .unwrap();
448
449 let repo = Repo::open_path(&path).unwrap();
450 assert_eq!(
451 repo.signature_state(&Oid::new(oid.to_string()), &[]),
452 SignatureState::Unsigned
453 );
454 }
455
456 #[test]
457 fn ssh_signed_commit_end_to_end() {
458 use std::path::Path;
459
460 use git2::{Repository, Signature, Time};
461
462 use crate::{create_bare, repo_path};
463
464 let dir = tempfile::tempdir().unwrap();
465 let id = "01hzxk9m2n8p7q6r5s4t3v2w1y";
466 create_bare(dir.path(), id, "main", Path::new("/usr/bin/fabrica")).unwrap();
467 let path = repo_path(dir.path(), id).unwrap();
468 let raw = Repository::open_bare(&path).unwrap();
469
470 let blob = raw.blob(b"hi\n").unwrap();
471 let mut tb = raw.treebuilder(None).unwrap();
472 tb.insert("f", blob, 0o100_644).unwrap();
473 let tree = raw.find_tree(tb.write().unwrap()).unwrap();
474 let who = Signature::new("Ada", "ada@example.com", &Time::new(1, 0)).unwrap();
475 let content = raw
476 .commit_create_buffer(&who, &who, "signed commit", &tree, &[])
477 .unwrap();
478 let content = std::str::from_utf8(&content).unwrap();
479
480 let (private, key) = ssh_key();
481 let armor = sign_ssh(&private, content.as_bytes());
482 let oid = raw.commit_signed(content, &armor, None).unwrap();
483
484 let repo = Repo::open_path(&path).unwrap();
485 match repo.signature_state(&Oid::new(oid.to_string()), std::slice::from_ref(&key)) {
486 SignatureState::Verified { fingerprint, .. } => {
487 assert_eq!(fingerprint, key.fingerprint);
488 }
489 other => panic!("expected Verified, got {other:?}"),
490 }
491 }
492}
crates/model/src/entity.rs +38 −0
@@ -16,6 +16,44 @@
16/// the schema.16/// the schema.
17pub type Timestamp = i64;17pub type Timestamp = i64;
1818
19/// The kind of a registered public key: an SSH key (used for both push auth and
20/// signature verification) or a GPG key (signature verification only). Stored as
21/// the `keys.kind` text `'ssh'` / `'gpg'`.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum KeyKind {
24 /// An `OpenSSH` public key.
25 Ssh,
26 /// An `OpenPGP` (GPG) public key.
27 Gpg,
28}
29
30impl KeyKind {
31 /// The lowercase database token (`"ssh"` / `"gpg"`).
32 #[must_use]
33 pub fn as_str(self) -> &'static str {
34 match self {
35 Self::Ssh => "ssh",
36 Self::Gpg => "gpg",
37 }
38 }
39
40 /// Parse from the database token, case-insensitively.
41 #[must_use]
42 pub fn from_token(s: &str) -> Option<Self> {
43 match s.to_ascii_lowercase().as_str() {
44 "ssh" => Some(Self::Ssh),
45 "gpg" => Some(Self::Gpg),
46 _ => None,
47 }
48 }
49}
50
51impl std::fmt::Display for KeyKind {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 f.write_str(self.as_str())
54 }
55}
56
19/// A registered account.57/// A registered account.
20#[derive(Debug, Clone, PartialEq, Eq)]58#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct User {59pub struct User {
crates/model/src/lib.rs +1 −1
@@ -11,5 +11,5 @@
11pub mod entity;11pub mod entity;
12pub mod name;12pub mod name;
1313
14pub use entity::{Repo, Timestamp, User};14pub use entity::{KeyKind, Repo, Timestamp, User};
15pub use name::{MAX_NAME_LEN, NameError, RESERVED_NAMES, validate_name};15pub use name::{MAX_NAME_LEN, NameError, RESERVED_NAMES, validate_name};
docs/decisions.md +35 −0
@@ -328,3 +328,38 @@ is bounded by the `spawn_blocking` pool at the call site rather than by silently
328corrupting a page. Wiring a hard per-file deadline (a cancellation flag polled by328corrupting a page. Wiring a hard per-file deadline (a cancellation flag polled by
329the grammar, or a watchdog thread) is deferred to the polish phase, where the329the grammar, or a watchdog thread) is deferred to the polish phase, where the
330highlight cache and measured hot paths land together. Tracked as a known gap.330highlight cache and measured hot paths land together. Tracked as a known gap.
331
332## 2026-07-24 — GPG verification via rpgp, not sequoia-openpgp
333
334**Decision:** OpenPGP signature verification uses the `pgp` crate (rpgp) 0.20, not
335`sequoia-openpgp` as the spec names.
336
337**Alternatives:** `sequoia-openpgp` (the spec's choice); shell out to `gpg`.
338
339**Rationale:** `sequoia-openpgp` is **LGPL-2.0-or-later**, which is not in
340`deny.toml`'s allow-list — pulling it in fails `cargo-deny`, the same class of
341constraint that rules out `ring` and drove the hand-rolled JWT. `gpgme` shells out
342to GnuPG (GPL). rpgp is `MIT OR Apache-2.0`, pure-Rust (ed25519-dalek/rsa/p256, no
343`ring`, no OpenSSL/C), and covers the verification surface we need. SSH signatures
344use `ssh-key` (also Apache-2.0/MIT, pure-Rust), as the spec specifies. This is a
345**SHOULD** deviation logged here.
346
347## 2026-07-24 — SignatureState ids are String; key expiry/revocation best-effort
348
349**Decision:** `SignatureState` carries `String` ids rather than the spec sketch's
350`Ulid`, and does not yet reject on GPG **key expiry or revocation**.
351
352**Alternatives:** Depend on `ulid` in the git crate for typed ids; implement full
353key-validity/revocation checking.
354
355**Rationale:** The git crate has no `ulid` dependency and speaks ids as opaque
356strings everywhere else (oids are hex `String`s); threading a `Ulid` type through
357solely for these fields would add a dependency for no gain, since ids originate in
358and return to the store as strings. On expiry/revocation: rpgp 0.20 exposes no
359one-call "valid at time T" check for detached signatures, and revocation requires
360manually scanning revocation signatures. SSH signature verification and GPG
361signature **cryptographic** verification are complete and cover all four
362`SignatureState` variants (tested); rejecting on GPG key expiry/revocation is a
363documented best-effort gap to close alongside the signature cache wiring in the web
364phase. Expired/revoked keys therefore currently verify as `Verified` if the
365signature is otherwise valid — tracked as a known limitation.