From 41e56367bdaeff7c27a82d6db302b1e53beb6169 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sun, 9 Aug 2026 20:59:49 +0000 Subject: [PATCH] Establish pass compatibility fixture harness (#1) --- Cargo.lock | 1217 ++++++++++++++++- crates/storage/Cargo.toml | 12 + .../refresh_compatibility_fixtures.rs | 571 ++++++++ .../storage/tests/compatibility_fixtures.rs | 236 ++++ .../tests/fixtures/compatibility/README.md | 52 + .../fixtures/compatibility/behavior.toml | 1205 ++++++++++++++++ .../expected/basic/email/personal.txt | 3 + .../compatibility/expected/basic/otp/hotp.txt | 1 + .../compatibility/expected/basic/otp/totp.txt | 2 + .../expected/basic/shared/multiple.txt | 2 + .../expected/basic/team/service.txt | 2 + .../expected/basic/unicode/咖啡.txt | 3 + .../nested/outer/inner/nested-entry.txt | 1 + .../expected/nested/outer/root-entry.txt | 1 + .../fixtures/compatibility/generated.toml | 105 ++ .../compatibility/keys/alice-public.asc | 13 + .../compatibility/keys/alice-public.pgp | Bin 0 -> 415 bytes .../compatibility/keys/alice-secret.asc | 17 + .../compatibility/keys/alice-secret.pgp | Bin 0 -> 581 bytes .../compatibility/keys/bob-public.asc | 13 + .../compatibility/keys/bob-public.pgp | Bin 0 -> 411 bytes .../compatibility/keys/bob-secret.asc | 17 + .../compatibility/keys/bob-secret.pgp | Bin 0 -> 577 bytes .../tests/fixtures/compatibility/layouts.toml | 51 + .../compatibility/repositories/basic.git/HEAD | 1 + .../repositories/basic.git/config | 6 + .../11/00a0be07a853900e5dbbeaef49aa7a329f90c1 | Bin 0 -> 52 bytes .../47/d4bb26ec7dc2046e3d72a7868a7578c6c97db1 | Bin 0 -> 57 bytes .../68/2ed002ea90ed47cd94e546c738851c08e3954f | Bin 0 -> 57 bytes .../a7/d88415df6027082096f6fd035387ac76726920 | Bin 0 -> 148 bytes .../bb/ace78ed41918acad8de131f58e1fccbf5b06db | Bin 0 -> 86 bytes .../c1/bec8815d40fab6e23162443ef55ce1c46bddf6 | Bin 0 -> 196 bytes .../f2/6189c986fc85cd7edb067ba87dee7a13847878 | Bin 0 -> 241 bytes .../repositories/basic.git/refs/heads/main | 1 + .../compatibility/repositories/inner.git/HEAD | 1 + .../repositories/inner.git/config | 6 + .../12/90ab99298b22240d96f5c4987f48aaa69ec565 | Bin 0 -> 57 bytes .../30/8371ba7fd91255536f4e6afee2e23a02af6915 | 4 + .../74/3396337e1c5f4f8a2915adf9909fd15e2dbce6 | Bin 0 -> 190 bytes .../ca/39c3b70baad33826262028f772bcd05635b4a8 | Bin 0 -> 90 bytes .../repositories/inner.git/refs/heads/main | 1 + .../compatibility/repositories/outer.git/HEAD | 1 + .../repositories/outer.git/config | 6 + .../28/c9a2a0dc705193cac947c046f99b84058f0bdc | Bin 0 -> 88 bytes .../2c/32c59867d2e4b4ab07ca8e9225e8eaa887a58e | Bin 0 -> 161 bytes .../68/2ed002ea90ed47cd94e546c738851c08e3954f | Bin 0 -> 57 bytes .../af/ae3d12617388c20584e7afb3f87e0975b0fd00 | Bin 0 -> 190 bytes .../repositories/outer.git/refs/heads/main | 1 + .../compatibility/stores/basic/.gpg-id | 1 + .../compatibility/stores/basic/.gpg-id.sig | Bin 0 -> 119 bytes .../stores/basic/email/personal.gpg | Bin 0 -> 221 bytes .../compatibility/stores/basic/otp/hotp.gpg | 2 + .../compatibility/stores/basic/otp/totp.gpg | Bin 0 -> 289 bytes .../compatibility/stores/basic/shared/.gpg-id | 2 + .../stores/basic/shared/multiple.gpg | 3 + .../compatibility/stores/basic/team/.gpg-id | 1 + .../stores/basic/team/.gpg-id.sig | Bin 0 -> 119 bytes .../stores/basic/team/service.gpg | 1 + .../stores/basic/unicode/咖啡.gpg | 2 + .../compatibility/stores/nested/outer/.gpg-id | 1 + .../stores/nested/outer/inner/.gpg-id | 1 + .../nested/outer/inner/nested-entry.gpg | 2 + .../stores/nested/outer/root-entry.gpg | Bin 0 -> 170 bytes .../fixtures/compatibility/upstream.toml | 21 + crates/storage/tests/support/compatibility.rs | 406 ++++++ crates/storage/tests/support/mod.rs | 1 + 66 files changed, 3978 insertions(+), 18 deletions(-) create mode 100644 crates/storage/examples/refresh_compatibility_fixtures.rs create mode 100644 crates/storage/tests/compatibility_fixtures.rs create mode 100644 crates/storage/tests/fixtures/compatibility/README.md create mode 100644 crates/storage/tests/fixtures/compatibility/behavior.toml create mode 100644 crates/storage/tests/fixtures/compatibility/expected/basic/email/personal.txt create mode 100644 crates/storage/tests/fixtures/compatibility/expected/basic/otp/hotp.txt create mode 100644 crates/storage/tests/fixtures/compatibility/expected/basic/otp/totp.txt create mode 100644 crates/storage/tests/fixtures/compatibility/expected/basic/shared/multiple.txt create mode 100644 crates/storage/tests/fixtures/compatibility/expected/basic/team/service.txt create mode 100644 crates/storage/tests/fixtures/compatibility/expected/basic/unicode/咖啡.txt create mode 100644 crates/storage/tests/fixtures/compatibility/expected/nested/outer/inner/nested-entry.txt create mode 100644 crates/storage/tests/fixtures/compatibility/expected/nested/outer/root-entry.txt create mode 100644 crates/storage/tests/fixtures/compatibility/generated.toml create mode 100644 crates/storage/tests/fixtures/compatibility/keys/alice-public.asc create mode 100644 crates/storage/tests/fixtures/compatibility/keys/alice-public.pgp create mode 100644 crates/storage/tests/fixtures/compatibility/keys/alice-secret.asc create mode 100644 crates/storage/tests/fixtures/compatibility/keys/alice-secret.pgp create mode 100644 crates/storage/tests/fixtures/compatibility/keys/bob-public.asc create mode 100644 crates/storage/tests/fixtures/compatibility/keys/bob-public.pgp create mode 100644 crates/storage/tests/fixtures/compatibility/keys/bob-secret.asc create mode 100644 crates/storage/tests/fixtures/compatibility/keys/bob-secret.pgp create mode 100644 crates/storage/tests/fixtures/compatibility/layouts.toml create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/basic.git/HEAD create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/basic.git/config create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/11/00a0be07a853900e5dbbeaef49aa7a329f90c1 create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/47/d4bb26ec7dc2046e3d72a7868a7578c6c97db1 create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/68/2ed002ea90ed47cd94e546c738851c08e3954f create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/a7/d88415df6027082096f6fd035387ac76726920 create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/bb/ace78ed41918acad8de131f58e1fccbf5b06db create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/c1/bec8815d40fab6e23162443ef55ce1c46bddf6 create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/f2/6189c986fc85cd7edb067ba87dee7a13847878 create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/basic.git/refs/heads/main create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/inner.git/HEAD create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/inner.git/config create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/inner.git/objects/12/90ab99298b22240d96f5c4987f48aaa69ec565 create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/inner.git/objects/30/8371ba7fd91255536f4e6afee2e23a02af6915 create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/inner.git/objects/74/3396337e1c5f4f8a2915adf9909fd15e2dbce6 create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/inner.git/objects/ca/39c3b70baad33826262028f772bcd05635b4a8 create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/inner.git/refs/heads/main create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/outer.git/HEAD create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/outer.git/config create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/outer.git/objects/28/c9a2a0dc705193cac947c046f99b84058f0bdc create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/outer.git/objects/2c/32c59867d2e4b4ab07ca8e9225e8eaa887a58e create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/outer.git/objects/68/2ed002ea90ed47cd94e546c738851c08e3954f create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/outer.git/objects/af/ae3d12617388c20584e7afb3f87e0975b0fd00 create mode 100644 crates/storage/tests/fixtures/compatibility/repositories/outer.git/refs/heads/main create mode 100644 crates/storage/tests/fixtures/compatibility/stores/basic/.gpg-id create mode 100644 crates/storage/tests/fixtures/compatibility/stores/basic/.gpg-id.sig create mode 100644 crates/storage/tests/fixtures/compatibility/stores/basic/email/personal.gpg create mode 100644 crates/storage/tests/fixtures/compatibility/stores/basic/otp/hotp.gpg create mode 100644 crates/storage/tests/fixtures/compatibility/stores/basic/otp/totp.gpg create mode 100644 crates/storage/tests/fixtures/compatibility/stores/basic/shared/.gpg-id create mode 100644 crates/storage/tests/fixtures/compatibility/stores/basic/shared/multiple.gpg create mode 100644 crates/storage/tests/fixtures/compatibility/stores/basic/team/.gpg-id create mode 100644 crates/storage/tests/fixtures/compatibility/stores/basic/team/.gpg-id.sig create mode 100644 crates/storage/tests/fixtures/compatibility/stores/basic/team/service.gpg create mode 100644 crates/storage/tests/fixtures/compatibility/stores/basic/unicode/咖啡.gpg create mode 100644 crates/storage/tests/fixtures/compatibility/stores/nested/outer/.gpg-id create mode 100644 crates/storage/tests/fixtures/compatibility/stores/nested/outer/inner/.gpg-id create mode 100644 crates/storage/tests/fixtures/compatibility/stores/nested/outer/inner/nested-entry.gpg create mode 100644 crates/storage/tests/fixtures/compatibility/stores/nested/outer/root-entry.gpg create mode 100644 crates/storage/tests/fixtures/compatibility/upstream.toml create mode 100644 crates/storage/tests/support/compatibility.rs create mode 100644 crates/storage/tests/support/mod.rs diff --git a/Cargo.lock b/Cargo.lock index ab6a8ff..1afceee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,57 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "bytes", + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aes-kw" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69fa2b352dcefb5f7f3a5fb840e02665d311d878955380515e4fd50095dd3d8c" +dependencies = [ + "aes", +] + [[package]] name = "ahash" version = "0.8.12" @@ -154,6 +205,19 @@ dependencies = [ "num-traits", ] +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", + "zeroize", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -231,7 +295,7 @@ dependencies = [ "serde", "serde_derive", "unicode-ident", - "winnow", + "winnow 1.0.4", ] [[package]] @@ -386,12 +450,24 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "basic-toml" version = "0.1.10" @@ -431,6 +507,27 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitfields" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6e59298da389bc0649c7463856b34c6e17fe542f88939426ede4436c6b1195" +dependencies = [ + "bitfields-impl", +] + +[[package]] +name = "bitfields-impl" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c044f98f86f15414668d6c8187c7e4fadab1ad2b31680f648703e0fe07c555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "thiserror 2.0.19", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -443,6 +540,27 @@ version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block" version = "0.1.6" @@ -458,6 +576,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "block2" version = "0.5.1" @@ -489,6 +616,25 @@ dependencies = [ "piper", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "buffer-redux" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "431a9cc8d7efa49bc326729264537f5e60affce816c66edf434350778c9f4f54" +dependencies = [ + "memchr", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -521,6 +667,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.12.1" @@ -578,6 +730,16 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "camellia" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "camino" version = "1.2.5" @@ -611,6 +773,15 @@ dependencies = [ "thiserror 2.0.19", ] +[[package]] +name = "cast5" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b07d673db1ccf000e90f54b819db9e75a8348d6eb056e9b8ab53231b7a9911" +dependencies = [ + "cipher", +] + [[package]] name = "castaway" version = "0.2.4" @@ -632,6 +803,15 @@ dependencies = [ "shlex", ] +[[package]] +name = "cfb-mode" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330" +dependencies = [ + "cipher", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -644,6 +824,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.6.4" @@ -723,6 +913,17 @@ dependencies = [ "x11rb", ] +[[package]] +name = "cmac" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa" +dependencies = [ + "cipher", + "dbl", + "digest", +] + [[package]] name = "codespan-reporting" version = "0.12.0" @@ -773,6 +974,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "convert_case" version = "0.10.0" @@ -885,6 +1092,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc24" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "critical-section" version = "1.2.0" @@ -943,6 +1165,18 @@ dependencies = [ "wgpu", ] +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -950,6 +1184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -972,20 +1207,97 @@ dependencies = [ "dtor", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "cursor-icon" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "cx448" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c0cf476284b03eb6c10e78787b21c7abb7d7d43cb2f02532ba6b831ed892fa" +dependencies = [ + "crypto-bigint", + "elliptic-curve", + "pkcs8", + "rand_core", + "serdect 0.3.0", + "sha3", + "signature", + "subtle", + "zeroize", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + [[package]] name = "darling" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", ] [[package]] @@ -1001,29 +1313,91 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.119", +] + [[package]] name = "darling_macro" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "darling_core", + "darling_core 0.23.0", "quote", "syn 2.0.119", ] +[[package]] +name = "dbl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" +dependencies = [ + "generic-array", +] + [[package]] name = "deltae" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.119", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -1044,6 +1418,16 @@ dependencies = [ "quote", "rustc_version", "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", ] [[package]] @@ -1053,7 +1437,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -1102,18 +1488,111 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "dsa" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689" +dependencies = [ + "digest", + "num-bigint-dig", + "num-traits", + "pkcs8", + "rfc6979", + "sha2", + "signature", + "zeroize", +] + [[package]] name = "dtor" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edf234dd1594d6dd434a8fb8cada51ddbbc593e40e4a01556a0b31c62da2775b" +[[package]] +name = "eax" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28" +dependencies = [ + "aead", + "cipher", + "cmac", + "ctr", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "base64ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "serde_json", + "serdect 0.2.0", + "subtle", + "tap", + "zeroize", +] + [[package]] name = "endi" version = "1.1.1" @@ -1224,6 +1703,23 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "filedescriptor" version = "0.8.3" @@ -1253,6 +1749,17 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1348,6 +1855,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.33" @@ -1457,6 +1970,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1469,6 +1983,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -1492,6 +2017,16 @@ dependencies = [ "r-efi 6.0.0", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gl_generator" version = "0.14.0" @@ -1598,6 +2133,17 @@ dependencies = [ "bitflags 2.13.1", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "guillotiere" version = "0.6.2" @@ -1688,6 +2234,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "iced" version = "0.14.0" @@ -1872,6 +2436,15 @@ dependencies = [ "winit", ] +[[package]] +name = "idea" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477" +dependencies = [ + "cipher", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1899,13 +2472,22 @@ dependencies = [ "rustversion", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "instability" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ - "darling", + "darling 0.23.0", "indoc", "proc-macro2", "quote", @@ -1915,6 +2497,18 @@ dependencies = [ [[package]] name = "ironstorage" version = "0.1.0" +dependencies = [ + "flate2", + "hex", + "pgp", + "rand_chacha", + "serde", + "sha1", + "sha2", + "smallvec", + "tempfile", + "toml 0.9.12+spec-1.1.0", +] [[package]] name = "ironstorage-apple" @@ -2049,6 +2643,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + [[package]] name = "kasuari" version = "0.4.12" @@ -2060,6 +2668,15 @@ dependencies = [ "thiserror 2.0.19", ] +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + [[package]] name = "khronos-egl" version = "6.0.0" @@ -2098,6 +2715,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -2224,6 +2844,16 @@ dependencies = [ "libc", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.8.3" @@ -2275,6 +2905,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.2.2" @@ -2391,6 +3031,32 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "serde", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.2.2" @@ -2408,6 +3074,25 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2808,6 +3493,18 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "ocb3" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c196e0276c471c843dd5777e7543a36a298a4be942a2a688d8111cd43390dedb" +dependencies = [ + "aead", + "cipher", + "ctr", + "subtle", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -2820,6 +3517,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "orbclient" version = "0.3.55" @@ -2867,6 +3570,44 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core", + "sha2", +] + [[package]] name = "palette" version = "0.7.6" @@ -2920,12 +3661,32 @@ dependencies = [ "windows-link", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2974,6 +3735,75 @@ dependencies = [ "pest", ] +[[package]] +name = "pgp" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfa4743b28656065ff4c0ba09e46b357a65e8c00fc2341e89084b82f87cbdf1" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "aes-kw", + "argon2", + "base64", + "bitfields", + "block-padding", + "blowfish", + "buffer-redux", + "byteorder", + "bytes", + "camellia", + "cast5", + "cfb-mode", + "cipher", + "const-oid", + "crc24", + "curve25519-dalek", + "cx448", + "derive_builder", + "derive_more", + "des", + "digest", + "dsa", + "eax", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "flate2", + "generic-array", + "hex", + "hkdf", + "idea", + "k256", + "log", + "md-5", + "memchr", + "nom 8.0.0", + "num-bigint-dig", + "num-traits", + "num_enum", + "ocb3", + "p256", + "p384", + "p521", + "rand", + "replace_with", + "ripemd", + "rsa", + "sha1", + "sha1-checked", + "sha2", + "sha3", + "signature", + "smallvec", + "snafu", + "subtle", + "twofish", + "x25519-dalek", + "zeroize", +] + [[package]] name = "phf" version = "0.11.3" @@ -3069,6 +3899,27 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.33" @@ -3095,6 +3946,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.14.0" @@ -3116,12 +3979,30 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "presser" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -3176,12 +4057,30 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", "rand_core", ] @@ -3190,6 +4089,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] [[package]] name = "range-alloc" @@ -3394,12 +4296,57 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "replace_with" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51743d3e274e2b18df81c4dc6caf8a5b8e15dbe799e0dca05c7617380094e884" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + [[package]] name = "roxmltree" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -3513,6 +4460,21 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "serdect 0.2.0", + "subtle", + "zeroize", +] + [[package]] name = "self_cell" version = "1.3.0" @@ -3592,6 +4554,48 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "serdect" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1-checked" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" +dependencies = [ + "digest", + "sha1", + "zeroize", +] + [[package]] name = "sha2" version = "0.10.9" @@ -3603,6 +4607,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "shlex" version = "2.0.1" @@ -3640,6 +4654,22 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + [[package]] name = "simd_cesu8" version = "1.2.0" @@ -3781,6 +4811,27 @@ dependencies = [ "serde", ] +[[package]] +name = "snafu" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45cb604038abb7b926b679887b3226d8d0f23874b66623625a0454be425a4b7" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287f59010008f0d7cf5e3b03196d666c1acc46c8d3e9cf34c28a1a7157601e72" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "softbuffer" version = "0.4.8" @@ -3812,6 +4863,12 @@ dependencies = [ "x11rb", ] +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + [[package]] name = "spirv" version = "0.3.0+sdk-1.3.268.0" @@ -3821,6 +4878,16 @@ dependencies = [ "bitflags 2.13.1", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -3860,6 +4927,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "svg_fmt" version = "0.4.5" @@ -3919,6 +4992,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.27.0" @@ -3961,7 +5040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" dependencies = [ "fnv", - "nom", + "nom 7.1.3", "phf", "phf_codegen", ] @@ -4140,6 +5219,21 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + [[package]] name = "toml" version = "1.1.4+spec-1.1.0" @@ -4149,10 +5243,19 @@ dependencies = [ "indexmap", "serde_core", "serde_spanned", - "toml_datetime", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", ] [[package]] @@ -4171,9 +5274,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.4", ] [[package]] @@ -4182,7 +5285,7 @@ version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ - "winnow", + "winnow 1.0.4", ] [[package]] @@ -4232,6 +5335,15 @@ dependencies = [ "core_maths", ] +[[package]] +name = "twofish" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013" +dependencies = [ + "cipher", +] + [[package]] name = "typenum" version = "1.20.1" @@ -4302,6 +5414,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "uniffi" version = "0.32.0" @@ -4335,7 +5453,7 @@ dependencies = [ "serde", "tempfile", "textwrap", - "toml", + "toml 1.1.4+spec-1.1.0", "uniffi_internal_macros", "uniffi_meta", "uniffi_pipeline", @@ -4380,7 +5498,7 @@ dependencies = [ "quote", "serde", "syn 2.0.119", - "toml", + "toml 1.1.4+spec-1.1.0", "uniffi_meta", ] @@ -4421,6 +5539,16 @@ dependencies = [ "weedle2", ] +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "utf8parse" version = "0.2.2" @@ -4710,7 +5838,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] @@ -5298,6 +6426,12 @@ dependencies = [ "xkbcommon-dl", ] +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + [[package]] name = "winnow" version = "1.0.4" @@ -5313,6 +6447,15 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x11-dl" version = "2.21.0" @@ -5345,6 +6488,18 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + [[package]] name = "xcursor" version = "0.3.10" @@ -5411,7 +6566,7 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow", + "winnow 1.0.4", "zbus_macros", "zbus_names", "zvariant", @@ -5439,7 +6594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" dependencies = [ "serde", - "winnow", + "winnow 1.0.4", "zvariant", ] @@ -5469,6 +6624,32 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zlib-rs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" + [[package]] name = "zmij" version = "1.0.23" @@ -5484,7 +6665,7 @@ dependencies = [ "endi", "enumflags2", "serde", - "winnow", + "winnow 1.0.4", "zvariant_derive", "zvariant_utils", ] @@ -5512,5 +6693,5 @@ dependencies = [ "quote", "serde", "syn 2.0.119", - "winnow", + "winnow 1.0.4", ] diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index 9368fc4..a3ea47e 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -5,3 +5,15 @@ version.workspace = true edition.workspace = true rust-version.workspace = true publish = false + +[dev-dependencies] +flate2 = "1.1" +hex = "0.4" +pgp = { version = "0.20", default-features = false } +rand_chacha = "0.3" +serde = { version = "1", features = ["derive"] } +sha1 = "0.10" +sha2 = "0.10" +smallvec = "1.15" +tempfile = "3" +toml = "0.9" diff --git a/crates/storage/examples/refresh_compatibility_fixtures.rs b/crates/storage/examples/refresh_compatibility_fixtures.rs new file mode 100644 index 0000000..09320ae --- /dev/null +++ b/crates/storage/examples/refresh_compatibility_fixtures.rs @@ -0,0 +1,571 @@ +#![forbid(unsafe_code)] + +use std::{ + collections::BTreeMap, + error::Error, + fs, + io::Write, + path::{Path, PathBuf}, +}; + +use flate2::{Compression, write::ZlibEncoder}; +use pgp::{ + composed::{ + ArmorOptions, Deserializable, DetachedSignature, EncryptionCaps, KeyType, MessageBuilder, + SecretKeyParamsBuilder, SignedSecretKey, SubkeyParamsBuilder, SubpacketConfig, + }, + crypto::{ecc_curve::ECCCurve, hash::HashAlgorithm, sym::SymmetricKeyAlgorithm}, + packet::{Subpacket, SubpacketData}, + ser::Serialize as _, + types::{KeyDetails as _, Password, Timestamp}, +}; +use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng}; +use serde::Serialize; +use sha1::{Digest as _, Sha1}; +use sha2::Sha256; +use smallvec::smallvec; + +const FIXTURE_EPOCH: u32 = 1_704_067_200; +const ALICE_PASSPHRASE: &str = "fixture-alice-passphrase"; +const BOB_PASSPHRASE: &str = "fixture-bob-passphrase"; + +#[derive(Serialize)] +struct GeneratedManifest { + format: u8, + generated_by: &'static str, + fixture_seed: &'static str, + keys: Vec, + entries: Vec, + repositories: Vec, +} + +#[derive(Serialize)] +struct KeyRecord { + name: &'static str, + user_id: &'static str, + primary_fingerprint: String, + encryption_subkey_fingerprint: String, + passphrase: &'static str, + public_armor: String, + public_binary: String, + secret_armor: String, + secret_binary: String, +} + +#[derive(Serialize)] +struct EntryRecord { + store: &'static str, + path: &'static str, + plaintext: &'static str, + recipients: Vec, + ciphertext_sha256: String, +} + +#[derive(Serialize)] +struct RepositoryRecord { + name: &'static str, + template: String, + head: String, + commits: Vec, +} + +struct SyntheticKey { + name: &'static str, + user_id: &'static str, + passphrase: &'static str, + secret: SignedSecretKey, +} + +impl SyntheticKey { + fn primary_fingerprint(&self) -> String { + format!("{:X}", self.secret.primary_key.fingerprint()) + } + + fn encryption_fingerprint(&self) -> String { + format!("{:X}", self.secret.secret_subkeys[0].fingerprint()) + } +} + +fn main() -> Result<(), Box> { + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/compatibility"); + prepare_output(&root)?; + + let mut rng = ChaCha20Rng::from_seed([0x49; 32]); + let alice = load_or_generate_key( + &root, + &mut rng, + "alice", + "Alice Fixture ", + ALICE_PASSPHRASE, + )?; + let bob = load_or_generate_key( + &root, + &mut rng, + "bob", + "Bob Fixture ", + BOB_PASSPHRASE, + )?; + + let keys = vec![write_key(&root, &alice)?, write_key(&root, &bob)?]; + let mut entries = Vec::new(); + + write_recipient_file(&root, "basic/.gpg-id", &[&alice])?; + write_signature(&root, "basic/.gpg-id.sig", &alice, single_recipient(&alice))?; + write_recipient_file(&root, "basic/team/.gpg-id", &[&bob])?; + write_signature( + &root, + "basic/team/.gpg-id.sig", + &bob, + single_recipient(&bob), + )?; + write_recipient_file(&root, "basic/shared/.gpg-id", &[&alice, &bob])?; + + entries.push(write_entry( + &root, + &mut rng, + "basic", + "email/personal.gpg", + "email/personal.txt", + b"correct horse fixture\nlogin: alice@example.test\nurl: https://example.test\n", + &[&alice], + )?); + entries.push(write_entry( + &root, + &mut rng, + "basic", + "unicode/咖啡.gpg", + "unicode/咖啡.txt", + "pässwörd-猫\nlogin: 用户@example.test\nnotes: café\n".as_bytes(), + &[&alice], + )?); + entries.push(write_entry( + &root, + &mut rng, + "basic", + "otp/totp.gpg", + "otp/totp.txt", + b"fixture-password\notpauth://totp/IronStorage:alice%40example.test?secret=JBSWY3DPEHPK3PXP&issuer=IronStorage&algorithm=SHA1&digits=6&period=30\n", + &[&alice], + )?); + entries.push(write_entry( + &root, + &mut rng, + "basic", + "otp/hotp.gpg", + "otp/hotp.txt", + b"otpauth://hotp/IronStorage:counter?secret=JBSWY3DPEHPK3PXP&issuer=IronStorage&counter=0&digits=8\n", + &[&alice], + )?); + entries.push(write_entry( + &root, + &mut rng, + "basic", + "team/service.gpg", + "team/service.txt", + b"team-fixture-password\nowner: bob\n", + &[&bob], + )?); + entries.push(write_entry( + &root, + &mut rng, + "basic", + "shared/multiple.gpg", + "shared/multiple.txt", + b"shared-fixture-password\nrecipients: alice,bob\n", + &[&alice, &bob], + )?); + + write_recipient_file(&root, "nested/outer/.gpg-id", &[&alice])?; + write_recipient_file(&root, "nested/outer/inner/.gpg-id", &[&bob])?; + entries.push(write_entry( + &root, + &mut rng, + "nested", + "outer/root-entry.gpg", + "outer/root-entry.txt", + b"outer repository entry\n", + &[&alice], + )?); + entries.push(write_entry( + &root, + &mut rng, + "nested", + "outer/inner/nested-entry.gpg", + "outer/inner/nested-entry.txt", + b"inner repository entry\n", + &[&bob], + )?); + + let repositories = vec![ + write_basic_repository(&root)?, + write_nested_repository(&root, "outer", "nested/outer", "root-entry.gpg")?, + write_nested_repository(&root, "inner", "nested/outer/inner", "nested-entry.gpg")?, + ]; + + let manifest = GeneratedManifest { + format: 1, + generated_by: "cargo run -p ironstorage --example refresh_compatibility_fixtures", + fixture_seed: "49 repeated 32 times (hex); checked-in keys are reused on refresh", + keys, + entries, + repositories, + }; + write( + root.join("generated.toml"), + toml::to_string_pretty(&manifest)?.as_bytes(), + )?; + + println!("refreshed {}", root.display()); + Ok(()) +} + +fn prepare_output(root: &Path) -> Result<(), Box> { + fs::create_dir_all(root)?; + fs::create_dir_all(root.join("keys"))?; + for generated in ["stores", "expected", "repositories"] { + let path = root.join(generated); + if path.exists() { + fs::remove_dir_all(&path)?; + } + fs::create_dir_all(path)?; + } + Ok(()) +} + +fn load_or_generate_key( + root: &Path, + rng: &mut ChaCha20Rng, + name: &'static str, + user_id: &'static str, + passphrase: &'static str, +) -> Result> { + let path = root.join(format!("keys/{name}-secret.asc")); + if path.is_file() { + let (secret, _) = SignedSecretKey::from_armor_single(fs::File::open(path)?)?; + secret.verify_bindings()?; + return Ok(SyntheticKey { + name, + user_id, + passphrase, + secret, + }); + } + generate_key(rng, name, user_id, passphrase) +} + +fn generate_key( + rng: &mut ChaCha20Rng, + name: &'static str, + user_id: &'static str, + passphrase: &'static str, +) -> Result> { + let created_at = Timestamp::from_secs(FIXTURE_EPOCH); + let subkey = SubkeyParamsBuilder::default() + .key_type(KeyType::ECDH(ECCCurve::Curve25519Legacy)) + .can_encrypt(EncryptionCaps::All) + .created_at(created_at) + .passphrase(Some(passphrase.to_owned())) + .build()?; + let secret = SecretKeyParamsBuilder::default() + .key_type(KeyType::Ed25519Legacy) + .can_certify(true) + .can_sign(true) + .created_at(created_at) + .primary_user_id(user_id.to_owned()) + .passphrase(Some(passphrase.to_owned())) + .preferred_symmetric_algorithms(smallvec![ + SymmetricKeyAlgorithm::AES256, + SymmetricKeyAlgorithm::AES128, + ]) + .preferred_hash_algorithms(smallvec![HashAlgorithm::Sha256]) + .preferred_compression_algorithms(smallvec![]) + .subkey(subkey) + .build()? + .generate(rng)?; + secret.verify_bindings()?; + Ok(SyntheticKey { + name, + user_id, + passphrase, + secret, + }) +} + +fn write_key(root: &Path, key: &SyntheticKey) -> Result> { + let public = key.secret.to_public_key(); + let public_armor_path = format!("keys/{}-public.asc", key.name); + let public_binary_path = format!("keys/{}-public.pgp", key.name); + let secret_armor_path = format!("keys/{}-secret.asc", key.name); + let secret_binary_path = format!("keys/{}-secret.pgp", key.name); + + write( + root.join(&public_armor_path), + public + .to_armored_string(ArmorOptions::default())? + .as_bytes(), + )?; + let mut public_binary = Vec::new(); + public.to_writer(&mut public_binary)?; + write(root.join(&public_binary_path), &public_binary)?; + write( + root.join(&secret_armor_path), + key.secret + .to_armored_string(ArmorOptions::default())? + .as_bytes(), + )?; + let mut secret_binary = Vec::new(); + key.secret.to_writer(&mut secret_binary)?; + write(root.join(&secret_binary_path), &secret_binary)?; + + Ok(KeyRecord { + name: key.name, + user_id: key.user_id, + primary_fingerprint: key.primary_fingerprint(), + encryption_subkey_fingerprint: key.encryption_fingerprint(), + passphrase: key.passphrase, + public_armor: public_armor_path, + public_binary: public_binary_path, + secret_armor: secret_armor_path, + secret_binary: secret_binary_path, + }) +} + +fn recipient_bytes(keys: &[&SyntheticKey]) -> Vec { + let mut bytes = keys + .iter() + .map(|key| key.primary_fingerprint()) + .collect::>() + .join("\n") + .into_bytes(); + bytes.push(b'\n'); + bytes +} + +fn single_recipient(key: &SyntheticKey) -> Vec { + recipient_bytes(&[key]) +} + +fn write_recipient_file( + root: &Path, + relative: &str, + keys: &[&SyntheticKey], +) -> Result<(), Box> { + write(root.join("stores").join(relative), &recipient_bytes(keys)) +} + +fn write_signature( + root: &Path, + relative: &str, + key: &SyntheticKey, + data: Vec, +) -> Result<(), Box> { + let hashed = vec![ + Subpacket::regular(SubpacketData::IssuerFingerprint( + key.secret.primary_key.fingerprint(), + ))?, + Subpacket::regular(SubpacketData::SignatureCreationTime(Timestamp::from_secs( + FIXTURE_EPOCH, + )))?, + ]; + let unhashed = vec![Subpacket::regular(SubpacketData::IssuerKeyId( + key.secret.primary_key.legacy_key_id(), + ))?]; + let signature = DetachedSignature::sign_binary_data_with_subpackets( + ChaCha20Rng::from_seed([0x53; 32]), + &key.secret.primary_key, + &Password::from(key.passphrase), + HashAlgorithm::Sha256, + data.as_slice(), + SubpacketConfig::UserDefined { hashed, unhashed }, + )?; + let mut bytes = Vec::new(); + signature.to_writer(&mut bytes)?; + write(root.join("stores").join(relative), &bytes) +} + +fn write_entry( + root: &Path, + rng: &mut ChaCha20Rng, + store: &'static str, + path: &'static str, + plaintext_path: &'static str, + plaintext: &[u8], + recipients: &[&SyntheticKey], +) -> Result> { + let mut message = MessageBuilder::from_bytes("", plaintext.to_vec()) + .seipd_v1(&mut *rng, SymmetricKeyAlgorithm::AES256); + for recipient in recipients { + let public = recipient.secret.to_public_key(); + message.encrypt_to_key(&mut *rng, &public.public_subkeys[0])?; + } + let ciphertext = message.to_vec(&mut *rng)?; + write(root.join("stores").join(store).join(path), &ciphertext)?; + write( + root.join("expected").join(store).join(plaintext_path), + plaintext, + )?; + + Ok(EntryRecord { + store, + path, + plaintext: plaintext_path, + recipients: recipients + .iter() + .map(|key| key.primary_fingerprint()) + .collect(), + ciphertext_sha256: hex::encode(Sha256::digest(&ciphertext)), + }) +} + +fn write_basic_repository(root: &Path) -> Result> { + let repository = root.join("repositories/basic.git"); + prepare_git_repository(&repository)?; + let recipient = fs::read(root.join("stores/basic/.gpg-id"))?; + let encrypted = fs::read(root.join("stores/basic/email/personal.gpg"))?; + + let recipient_blob = write_git_object(&repository, "blob", &recipient)?; + let initial_tree = write_git_tree( + &repository, + &[("100644", ".gpg-id", recipient_blob.clone())], + )?; + let initial = write_git_commit( + &repository, + &initial_tree, + None, + "Set GPG id to Alice Fixture.", + )?; + + let encrypted_blob = write_git_object(&repository, "blob", &encrypted)?; + let email_tree = write_git_tree(&repository, &[("100644", "personal.gpg", encrypted_blob)])?; + let final_tree = write_git_tree( + &repository, + &[ + ("100644", ".gpg-id", recipient_blob), + ("40000", "email", email_tree), + ], + )?; + let head = write_git_commit( + &repository, + &final_tree, + Some(&initial), + "Add given password for email/personal to store.", + )?; + write_ref(&repository, &head)?; + + Ok(RepositoryRecord { + name: "basic", + template: "repositories/basic.git".to_owned(), + head: head.clone(), + commits: vec![initial, head], + }) +} + +fn write_nested_repository( + root: &Path, + name: &'static str, + store: &str, + entry: &str, +) -> Result> { + let template = format!("repositories/{name}.git"); + let repository = root.join(&template); + prepare_git_repository(&repository)?; + let recipient = fs::read(root.join("stores").join(store).join(".gpg-id"))?; + let encrypted = fs::read(root.join("stores").join(store).join(entry))?; + let recipient_blob = write_git_object(&repository, "blob", &recipient)?; + let entry_blob = write_git_object(&repository, "blob", &encrypted)?; + let tree = write_git_tree( + &repository, + &[ + ("100644", ".gpg-id", recipient_blob), + ("100644", entry, entry_blob), + ], + )?; + let head = write_git_commit( + &repository, + &tree, + None, + &format!("Add current contents of {name} password store."), + )?; + write_ref(&repository, &head)?; + Ok(RepositoryRecord { + name, + template, + head: head.clone(), + commits: vec![head], + }) +} + +fn prepare_git_repository(repository: &Path) -> Result<(), Box> { + fs::create_dir_all(repository.join("objects"))?; + fs::create_dir_all(repository.join("refs/heads"))?; + write(repository.join("HEAD"), b"ref: refs/heads/main\n")?; + write( + repository.join("config"), + b"[core]\n\trepositoryformatversion = 0\n\tfilemode = true\n\tbare = true\n[pass]\n\tsigncommits = false\n", + )?; + Ok(()) +} + +fn write_git_tree( + repository: &Path, + entries: &[(&str, &str, String)], +) -> Result> { + let mut sorted = BTreeMap::new(); + for (mode, name, id) in entries { + sorted.insert(name.as_bytes(), (*mode, id)); + } + let mut body = Vec::new(); + for (name, (mode, id)) in sorted { + body.extend_from_slice(mode.as_bytes()); + body.push(b' '); + body.extend_from_slice(name); + body.push(0); + body.extend_from_slice(&hex::decode(id)?); + } + write_git_object(repository, "tree", &body) +} + +fn write_git_commit( + repository: &Path, + tree: &str, + parent: Option<&str>, + message: &str, +) -> Result> { + let mut body = format!("tree {tree}\n"); + if let Some(parent) = parent { + body.push_str(&format!("parent {parent}\n")); + } + body.push_str("author IronStorage Fixture 1704067200 +0000\n"); + body.push_str( + "committer IronStorage Fixture 1704067200 +0000\n\n", + ); + body.push_str(message); + body.push('\n'); + write_git_object(repository, "commit", body.as_bytes()) +} + +fn write_git_object(repository: &Path, kind: &str, body: &[u8]) -> Result> { + let mut canonical = format!("{kind} {}\0", body.len()).into_bytes(); + canonical.extend_from_slice(body); + let id = hex::encode(Sha1::digest(&canonical)); + let path = repository.join("objects").join(&id[..2]).join(&id[2..]); + let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(&canonical)?; + write(path, &encoder.finish()?)?; + Ok(id) +} + +fn write_ref(repository: &Path, head: &str) -> Result<(), Box> { + write( + repository.join("refs/heads/main"), + format!("{head}\n").as_bytes(), + ) +} + +fn write(path: PathBuf, contents: &[u8]) -> Result<(), Box> { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + fs::write(path, contents)?; + Ok(()) +} diff --git a/crates/storage/tests/compatibility_fixtures.rs b/crates/storage/tests/compatibility_fixtures.rs new file mode 100644 index 0000000..867841e --- /dev/null +++ b/crates/storage/tests/compatibility_fixtures.rs @@ -0,0 +1,236 @@ +#![forbid(unsafe_code)] + +mod support; + +use std::collections::{BTreeMap, BTreeSet}; + +use support::compatibility::{ + FixtureSet, TestResult, decrypt_entry_with_passphrase, validate_entry_record, + validate_key_record, validate_recipient_signature, validate_repository, +}; + +#[test] +fn pins_the_selected_upstream_revisions() -> TestResult { + let fixture = FixtureSet::load()?; + assert_eq!(fixture.upstream.format, 1); + assert_eq!(fixture.upstream.project.len(), 2); + + let projects = fixture + .upstream + .project + .iter() + .map(|project| (project.name.as_str(), project)) + .collect::>(); + let password_store = projects["password-store"]; + assert_eq!(password_store.version, "1.7.4"); + assert_eq!(password_store.reported_version, "1.7.4"); + assert_eq!( + password_store.commit, + "1078f2514d579178d5df7042c6a790e9c9b731ad" + ); + let pass_otp = projects["pass-otp"]; + assert_eq!(pass_otp.version, "1.2.0"); + assert_eq!(pass_otp.reported_version, "1.1.1"); + assert_eq!(pass_otp.commit, "1e9d10ca75ae1a8672a7f192809713463657778e"); + for project in projects.values() { + assert_eq!(project.commit.len(), 40); + assert!(project.commit.bytes().all(|byte| byte.is_ascii_hexdigit())); + assert!(project.source.starts_with("https://")); + assert!(!project.behavior_source.is_empty()); + assert!(project.license.starts_with("GPL-")); + } + Ok(()) +} + +#[test] +fn behavior_catalog_covers_the_milestone_contract() -> TestResult { + let fixture = FixtureSet::load()?; + assert_eq!(fixture.behavior.format, 1); + assert!(fixture.behavior.cases.len() >= 65); + + let mut ids = BTreeSet::new(); + let mut commands = BTreeSet::new(); + let mut flags = BTreeSet::new(); + let mut areas = BTreeSet::new(); + let mut has_success = false; + let mut has_failure = false; + + for case in &fixture.behavior.cases { + assert!( + ids.insert(case.id.as_str()), + "duplicate case ID {}", + case.id + ); + assert!( + matches!(case.status, 0 | 1), + "invalid status in {}", + case.id + ); + has_success |= case.status == 0; + has_failure |= case.status != 0; + areas.insert(case.area.as_str()); + if let Some(command) = case.argv.first() { + commands.insert(command.as_str()); + } + for argument in &case.argv { + if argument.starts_with('-') { + let flag = argument.split('=').next().unwrap_or(argument); + flags.insert(flag); + } + } + if case.status != 0 { + assert!(case.recipients.is_empty(), "failed case has recipients"); + assert!(case.commit.is_empty(), "failed case has a commit"); + } + assert!(!case.outcome.is_empty()); + assert!(!case.stdin.is_empty()); + } + + assert!(has_success && has_failure); + for area in [ + "init", "show", "list", "find", "grep", "insert", "edit", "generate", "remove", "move", + "copy", "git", "otp", "meta", + ] { + assert!(areas.contains(area), "missing behavior area {area}"); + } + for command in [ + "init", + "show", + "ls", + "list", + "find", + "search", + "grep", + "insert", + "add", + "edit", + "generate", + "rm", + "remove", + "delete", + "mv", + "rename", + "cp", + "copy", + "git", + "otp", + "help", + "version", + "--help", + "--version", + ] { + assert!( + commands.contains(command), + "missing command or alias {command}" + ); + } + for flag in [ + "-p", + "--path", + "-c", + "--clip", + "-q2", + "--qrcode", + "-e", + "--echo", + "-m", + "--multiline", + "-f", + "--force", + "-n", + "--no-symbols", + "-i", + "--in-place", + "-r", + "--recursive", + "-s", + "--secret", + "-a", + "--account", + "--issuer", + ] { + assert!(flags.contains(flag), "missing option spelling {flag}"); + } + Ok(()) +} + +#[test] +fn generated_openpgp_fixtures_are_self_consistent() -> TestResult { + let fixture = FixtureSet::load()?; + assert_eq!(fixture.generated.format, 1); + assert_eq!(fixture.generated.keys.len(), 2); + assert!(fixture.generated.entries.len() >= 8); + for key in &fixture.generated.keys { + validate_key_record(&fixture, key)?; + } + for entry in &fixture.generated.entries { + validate_entry_record(&fixture, entry)?; + } + let bob_entry = fixture + .generated + .entries + .iter() + .find(|entry| entry.store == "basic" && entry.path == "team/service.gpg") + .expect("Bob-only entry"); + let bob = fixture.key("bob")?; + assert!( + decrypt_entry_with_passphrase(&fixture, bob_entry, bob, "fixture-alice-passphrase",) + .is_err(), + "the encrypted secret-key passphrase must be required" + ); + validate_recipient_signature(&fixture, "basic", "alice")?; + validate_recipient_signature(&fixture, "basic/team", "bob")?; + Ok(()) +} + +#[test] +fn generated_git_repositories_have_valid_objects_and_refs() -> TestResult { + let fixture = FixtureSet::load()?; + assert_eq!(fixture.generated.repositories.len(), 3); + for repository in &fixture.generated.repositories { + validate_repository(&fixture, repository)?; + } + Ok(()) +} + +#[test] +fn fixtures_materialize_without_touching_a_user_store() -> TestResult { + let fixture = FixtureSet::load()?; + let store = fixture.materialize_store("basic")?; + assert!(store.path().join(".gpg-id").is_file()); + assert!(store.path().join("email/personal.gpg").is_file()); + assert!(store.path().join("team/.gpg-id").is_file()); + + let repository = fixture.materialize_repository("basic")?; + assert!(repository.path().join("HEAD").is_file()); + assert!(repository.path().join("objects").is_dir()); + assert!(repository.path().join("refs/heads/main").is_file()); + Ok(()) +} + +#[test] +fn hostile_and_interruption_recipes_are_present() -> TestResult { + let fixture = FixtureSet::load()?; + let nodes = fixture + .layouts + .get("synthetic_node") + .and_then(toml::Value::as_array) + .expect("synthetic_node array"); + assert!(nodes.len() >= 4); + let outcomes = nodes + .iter() + .filter_map(|node| node.get("expected")) + .filter_map(toml::Value::as_str) + .collect::>(); + assert!(outcomes.contains("reject-symlink-escape")); + assert!(outcomes.contains("reject-unsupported-file-type")); + assert!(outcomes.contains("require-trailing-slash-disambiguation")); + + let interruptions = fixture + .layouts + .get("interruption") + .and_then(toml::Value::as_array) + .expect("interruption array"); + assert_eq!(interruptions.len(), 3); + Ok(()) +} diff --git a/crates/storage/tests/fixtures/compatibility/README.md b/crates/storage/tests/fixtures/compatibility/README.md new file mode 100644 index 0000000..9b0b024 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/README.md @@ -0,0 +1,52 @@ +# Compatibility fixtures + +These fixtures pin password-store 1.7.4 at commit +`1078f2514d579178d5df7042c6a790e9c9b731ad` and pass-otp 1.2.0 at commit +`1e9d10ca75ae1a8672a7f192809713463657778e`. The behavioral catalog was +written from the projects' published command documentation and pinned source; +no upstream source or test code is copied here. + +The v1.2.0 pass-otp tag reports `1.1.1` from its `version` command. The +fixture records that observable upstream behavior explicitly rather than +silently correcting it. + +All identities, passphrases, plaintexts, OTP secrets, Git identities, and +remote names are synthetic and public. They must never be reused for real +secrets. The generator reuses the checked-in identities and produces: + +- ASCII-armored and binary public keys; +- passphrase-protected ASCII-armored and binary secret keys; +- binary OpenPGP messages in an ordinary pass directory tree; +- root, nested, signed, and multi-recipient `.gpg-id` policies; +- TOTP and HOTP entries; +- valid loose-object Git repository templates, including an automatic-commit + history and nested-repository boundaries. + +Rust tests parse keys, verify signatures, decrypt every entry, compare exact +plaintext bytes, validate ciphertext digests, validate Git objects and refs, +and materialize isolated stores and repositories. They never invoke `pass`, +`gpg`, `git`, a shell, or an OTP/QR helper. + +## Refreshing fixtures + +From the repository root: + +```sh +cargo run -p ironstorage --example refresh_compatibility_fixtures +cargo test -p ironstorage --test compatibility_fixtures +``` + +Normal refreshes are byte-for-byte reproducible at the locked dependency +versions because checked-in identities are reused. Deleting the key fixtures +explicitly rotates the synthetic identities; OpenPGP self-signature timestamps +then make that rotation intentionally non-reproducible. Review all binary +changes and the generated fingerprints, digests, and Git object IDs before +committing them. + +An optional developer interoperability audit may import the generated keys +into a disposable GnuPG home, decrypt the `.gpg` entries, and point an +upstream `pass` 1.7.4 checkout at a materialized store. That audit is never a +test prerequisite and must use a temporary keyring and the synthetic fixture +passphrases. Refreshing observable behavior against upstream `pass` or +`pass-otp` is similarly development-only; application/runtime code may not +execute either tool. diff --git a/crates/storage/tests/fixtures/compatibility/behavior.toml b/crates/storage/tests/fixtures/compatibility/behavior.toml new file mode 100644 index 0000000..3b0daa0 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/behavior.toml @@ -0,0 +1,1205 @@ +format = 1 + +# This is an original, data-only description of observable behavior. It is not +# copied upstream source or test code. Dynamic values are fixed through the +# clock and random-stream fields so Rust implementations can execute the same +# cases without invoking pass, gpg, git, oathtool, or qrencode. + +[[case]] +id = "init-root" +area = "init" +argv = ["init", "ALICE"] +stdin = "none" +tty = false +status = 0 +outcome = "write:.gpg-id" +recipients = ["alice"] +commit = "Set GPG id to ALICE." + +[[case]] +id = "init-path-short-multiple" +area = "init" +argv = ["init", "-p", "team", "ALICE", "BOB"] +stdin = "none" +tty = false +status = 0 +outcome = "write:team/.gpg-id-and-selectively-reencrypt" +recipients = ["alice", "bob"] +commit = "Set GPG id to ALICE, BOB (team)." + +[[case]] +id = "init-path-long" +area = "init" +argv = ["init", "--path", "team", "BOB"] +stdin = "none" +tty = false +status = 0 +outcome = "write:team/.gpg-id-and-selectively-reencrypt" +recipients = ["bob"] +commit = "Set GPG id to BOB (team)." + +[[case]] +id = "init-remove" +area = "init" +argv = ["init", "--path", "team", ""] +stdin = "none" +tty = false +status = 0 +outcome = "remove:team/.gpg-id-and-selectively-reencrypt" +recipients = ["alice"] +commit = "Deinitialize team recipient policy." + +[[case]] +id = "init-traversal" +area = "init" +argv = ["init", "-p", "../escape", "ALICE"] +stdin = "none" +tty = false +status = 1 +outcome = "reject:sneaky-path" +recipients = [] +commit = "" + +[[case]] +id = "default-list" +area = "show" +argv = [] +stdin = "none" +tty = false +status = 0 +outcome = "tree:root" +recipients = [] +commit = "" + +[[case]] +id = "show-entry" +area = "show" +argv = ["show", "email/personal"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:entire-plaintext" +recipients = [] +commit = "" + +[[case]] +id = "show-implicit" +area = "show" +argv = ["email/personal"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:entire-plaintext" +recipients = [] +commit = "" + +[[case]] +id = "show-clip-short-default-line" +area = "show" +argv = ["show", "-c", "email/personal"] +stdin = "none" +tty = false +status = 0 +outcome = "clipboard:line-1-no-secret-stdout" +recipients = [] +commit = "" + +[[case]] +id = "show-clip-long-line" +area = "show" +argv = ["show", "--clip=2", "email/personal"] +stdin = "none" +tty = false +status = 0 +outcome = "clipboard:line-2-no-secret-stdout" +recipients = [] +commit = "" + +[[case]] +id = "show-qrcode-short-line" +area = "show" +argv = ["show", "-q2", "email/personal"] +stdin = "none" +tty = false +status = 0 +outcome = "qrcode:line-2-no-secret-stdout" +recipients = [] +commit = "" + +[[case]] +id = "show-qrcode-long-default-line" +area = "show" +argv = ["show", "--qrcode", "email/personal"] +stdin = "none" +tty = false +status = 0 +outcome = "qrcode:line-1-no-secret-stdout" +recipients = [] +commit = "" + +[[case]] +id = "show-mutually-exclusive" +area = "show" +argv = ["show", "--clip", "--qrcode", "email/personal"] +stdin = "none" +tty = false +status = 1 +outcome = "usage-error:no-change" +recipients = [] +commit = "" + +[[case]] +id = "show-invalid-line" +area = "show" +argv = ["show", "--clip=99", "email/personal"] +stdin = "none" +tty = false +status = 1 +outcome = "not-found:line-no-change" +recipients = [] +commit = "" + +[[case]] +id = "ls-alias" +area = "list" +argv = ["ls", "team"] +stdin = "none" +tty = false +status = 0 +outcome = "tree:team" +recipients = [] +commit = "" + +[[case]] +id = "list-alias" +area = "list" +argv = ["list", "team"] +stdin = "none" +tty = false +status = 0 +outcome = "tree:team" +recipients = [] +commit = "" + +[[case]] +id = "find" +area = "find" +argv = ["find", "service", "咖啡"] +stdin = "none" +tty = false +status = 0 +outcome = "tree:case-insensitive-matches" +recipients = [] +commit = "" + +[[case]] +id = "search-alias" +area = "find" +argv = ["search", "PERSONAL"] +stdin = "none" +tty = false +status = 0 +outcome = "tree:case-insensitive-matches" +recipients = [] +commit = "" + +[[case]] +id = "grep-basic" +area = "grep" +argv = ["grep", "alice@example\\.test"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:matching-entry-and-line" +recipients = [] +commit = "" + +[[case]] +id = "grep-ignore-case" +area = "grep" +argv = ["grep", "-i", "FIXTURE"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:matching-entries-and-lines" +recipients = [] +commit = "" + +[[case]] +id = "grep-invert-match" +area = "grep" +argv = ["grep", "-v", "fixture"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:nonmatching-lines" +recipients = [] +commit = "" + +[[case]] +id = "grep-unsupported-option" +area = "grep" +argv = ["grep", "--binary-files=text", "fixture"] +stdin = "none" +tty = false +status = 1 +outcome = "unsupported-option:no-change" +recipients = [] +commit = "" + +[[case]] +id = "insert-hidden-confirmed" +area = "insert" +argv = ["insert", "new/entry"] +stdin = "secret-confirmed:fixture-secret" +tty = true +status = 0 +outcome = "write:new/entry.gpg" +recipients = ["alice"] +commit = "Add given password for new/entry to store." + +[[case]] +id = "insert-echo-short" +area = "insert" +argv = ["insert", "-e", "new/entry"] +stdin = "line:fixture-secret" +tty = true +status = 0 +outcome = "write:new/entry.gpg" +recipients = ["alice"] +commit = "Add given password for new/entry to store." + +[[case]] +id = "insert-echo-long-force" +area = "insert" +argv = ["insert", "--echo", "--force", "email/personal"] +stdin = "line:replacement" +tty = true +status = 0 +outcome = "overwrite:email/personal.gpg" +recipients = ["alice"] +commit = "Add given password for email/personal to store." + +[[case]] +id = "insert-multiline-short" +area = "insert" +argv = ["insert", "-m", "new/multiline"] +stdin = "bytes:first\\nsecond\\n" +tty = false +status = 0 +outcome = "write:new/multiline.gpg-exact-bytes" +recipients = ["alice"] +commit = "Add given password for new/multiline to store." + +[[case]] +id = "insert-multiline-long" +area = "insert" +argv = ["insert", "--multiline", "new/multiline"] +stdin = "bytes:first\\nsecond\\n" +tty = false +status = 0 +outcome = "write:new/multiline.gpg-exact-bytes" +recipients = ["alice"] +commit = "Add given password for new/multiline to store." + +[[case]] +id = "insert-confirmation-mismatch" +area = "insert" +argv = ["insert", "new/entry"] +stdin = "secret-mismatch:first,second" +tty = true +status = 1 +outcome = "reject:mismatch-no-change" +recipients = [] +commit = "" + +[[case]] +id = "insert-overwrite-decline" +area = "insert" +argv = ["insert", "email/personal"] +stdin = "decision:no" +tty = true +status = 1 +outcome = "cancel:no-change" +recipients = [] +commit = "" + +[[case]] +id = "add-alias" +area = "insert" +argv = ["add", "-f", "new/entry"] +stdin = "line:fixture-secret" +tty = false +status = 0 +outcome = "write:new/entry.gpg" +recipients = ["alice"] +commit = "Add given password for new/entry to store." + +[[case]] +id = "edit-existing" +area = "edit" +argv = ["edit", "email/personal"] +stdin = "editor-replacement:expected/basic/email/personal-edited.txt" +tty = true +status = 0 +outcome = "atomic-overwrite:email/personal.gpg" +recipients = ["alice"] +commit = "Edit password for email/personal using fixture-editor." + +[[case]] +id = "edit-unchanged" +area = "edit" +argv = ["edit", "email/personal"] +stdin = "editor-unchanged" +tty = true +status = 1 +outcome = "unchanged:no-change" +recipients = [] +commit = "" + +[[case]] +id = "edit-editor-failure" +area = "edit" +argv = ["edit", "email/personal"] +stdin = "editor-exit:42" +tty = true +status = 1 +outcome = "editor-failed:no-change-and-cleanup" +recipients = [] +commit = "" + +[[case]] +id = "generate-default" +area = "generate" +argv = ["generate", "generated/default"] +stdin = "none" +tty = false +status = 0 +outcome = "write:generated/default.gpg-and-stdout-password" +recipients = ["alice"] +commit = "Add generated password for generated/default." +random_stream = "00..ff-repeating" + +[[case]] +id = "generate-length" +area = "generate" +argv = ["generate", "generated/length", "32"] +stdin = "none" +tty = false +status = 0 +outcome = "write:generated/length.gpg-length-32" +recipients = ["alice"] +commit = "Add generated password for generated/length." +random_stream = "00..ff-repeating" + +[[case]] +id = "generate-no-symbols-short" +area = "generate" +argv = ["generate", "-n", "generated/alnum", "16"] +stdin = "none" +tty = false +status = 0 +outcome = "write:generated/alnum.gpg-alphanumeric" +recipients = ["alice"] +commit = "Add generated password for generated/alnum." +random_stream = "00..ff-repeating" + +[[case]] +id = "generate-no-symbols-long-clip" +area = "generate" +argv = ["generate", "--no-symbols", "--clip", "generated/clip", "16"] +stdin = "none" +tty = false +status = 0 +outcome = "write-and-clipboard:no-secret-stdout" +recipients = ["alice"] +commit = "Add generated password for generated/clip." +random_stream = "00..ff-repeating" + +[[case]] +id = "generate-qrcode-short" +area = "generate" +argv = ["generate", "-q", "generated/qr", "16"] +stdin = "none" +tty = false +status = 0 +outcome = "write-and-qrcode:no-secret-stdout" +recipients = ["alice"] +commit = "Add generated password for generated/qr." +random_stream = "00..ff-repeating" + +[[case]] +id = "generate-in-place-short" +area = "generate" +argv = ["generate", "-i", "email/personal", "20"] +stdin = "none" +tty = false +status = 0 +outcome = "replace-first-line-preserve-tail-exactly" +recipients = ["alice"] +commit = "Replace generated password for email/personal." +random_stream = "00..ff-repeating" + +[[case]] +id = "generate-in-place-long" +area = "generate" +argv = ["generate", "--in-place", "email/personal", "20"] +stdin = "none" +tty = false +status = 0 +outcome = "replace-first-line-preserve-tail-exactly" +recipients = ["alice"] +commit = "Replace generated password for email/personal." +random_stream = "00..ff-repeating" + +[[case]] +id = "generate-force-long" +area = "generate" +argv = ["generate", "--force", "email/personal", "20"] +stdin = "none" +tty = false +status = 0 +outcome = "overwrite:email/personal.gpg" +recipients = ["alice"] +commit = "Add generated password for email/personal." +random_stream = "00..ff-repeating" + +[[case]] +id = "generate-zero" +area = "generate" +argv = ["generate", "new/entry", "0"] +stdin = "none" +tty = false +status = 1 +outcome = "reject:zero-length-no-change" +recipients = [] +commit = "" + +[[case]] +id = "generate-decline-overwrite" +area = "generate" +argv = ["generate", "email/personal"] +stdin = "decision:no" +tty = true +status = 1 +outcome = "cancel:no-change" +recipients = [] +commit = "" + +[[case]] +id = "rm-entry" +area = "remove" +argv = ["rm", "email/personal"] +stdin = "decision:yes" +tty = true +status = 0 +outcome = "remove:email/personal.gpg-and-empty-directories" +recipients = [] +commit = "Remove email/personal from store." + +[[case]] +id = "remove-alias-force" +area = "remove" +argv = ["remove", "--force", "email/personal"] +stdin = "none" +tty = false +status = 0 +outcome = "remove:email/personal.gpg-and-empty-directories" +recipients = [] +commit = "Remove email/personal from store." + +[[case]] +id = "delete-alias" +area = "remove" +argv = ["delete", "-f", "email/personal"] +stdin = "none" +tty = false +status = 0 +outcome = "remove:email/personal.gpg-and-empty-directories" +recipients = [] +commit = "Remove email/personal from store." + +[[case]] +id = "rm-directory-requires-recursive" +area = "remove" +argv = ["rm", "team/"] +stdin = "decision:yes" +tty = true +status = 1 +outcome = "reject:recursive-required-no-change" +recipients = [] +commit = "" + +[[case]] +id = "rm-directory-recursive-short" +area = "remove" +argv = ["rm", "-r", "team/"] +stdin = "decision:yes" +tty = true +status = 0 +outcome = "remove-tree:team" +recipients = [] +commit = "Remove team/ from store." + +[[case]] +id = "rm-directory-recursive-long" +area = "remove" +argv = ["rm", "--recursive", "--force", "team/"] +stdin = "none" +tty = false +status = 0 +outcome = "remove-tree:team" +recipients = [] +commit = "Remove team/ from store." + +[[case]] +id = "rm-decline" +area = "remove" +argv = ["rm", "email/personal"] +stdin = "decision:no" +tty = true +status = 1 +outcome = "cancel:no-change" +recipients = [] +commit = "" + +[[case]] +id = "mv-entry" +area = "move" +argv = ["mv", "email/personal", "archive/personal"] +stdin = "none" +tty = false +status = 0 +outcome = "move-atomic-and-reencrypt-if-needed" +recipients = ["alice"] +commit = "Rename email/personal to archive/personal." + +[[case]] +id = "rename-alias-force" +area = "move" +argv = ["rename", "--force", "email/personal", "team/personal"] +stdin = "none" +tty = false +status = 0 +outcome = "move-atomic-and-reencrypt" +recipients = ["bob"] +commit = "Rename email/personal to team/personal." + +[[case]] +id = "mv-collision-decline" +area = "move" +argv = ["mv", "email/personal", "team/service"] +stdin = "decision:no" +tty = true +status = 1 +outcome = "cancel:no-partial-move" +recipients = [] +commit = "" + +[[case]] +id = "cp-entry" +area = "copy" +argv = ["cp", "email/personal", "archive/personal"] +stdin = "none" +tty = false +status = 0 +outcome = "copy-source-preserved" +recipients = ["alice"] +commit = "Copy email/personal to archive/personal." + +[[case]] +id = "copy-alias-force" +area = "copy" +argv = ["copy", "-f", "email/personal", "team/personal"] +stdin = "none" +tty = false +status = 0 +outcome = "copy-source-preserved-and-reencrypt" +recipients = ["bob"] +commit = "Copy email/personal to team/personal." + +[[case]] +id = "git-init" +area = "git" +argv = ["git", "init"] +stdin = "none" +tty = false +status = 0 +outcome = "repository:init-with-gitattributes" +recipients = [] +commit = "Add current contents of password store." + +[[case]] +id = "git-status" +area = "git" +argv = ["git", "status"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:working-tree-status" +recipients = [] +commit = "" + +[[case]] +id = "git-log" +area = "git" +argv = ["git", "log"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:ordered-commit-log" +recipients = [] +commit = "" + +[[case]] +id = "git-diff" +area = "git" +argv = ["git", "diff"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:working-tree-diff" +recipients = [] +commit = "" + +[[case]] +id = "git-add" +area = "git" +argv = ["git", "add", ".gpg-id"] +stdin = "none" +tty = false +status = 0 +outcome = "index:stage-supported-path" +recipients = [] +commit = "" + +[[case]] +id = "git-commit" +area = "git" +argv = ["git", "commit", "-m", "fixture explicit commit"] +stdin = "none" +tty = false +status = 0 +outcome = "repository:new-commit" +recipients = [] +commit = "fixture explicit commit" + +[[case]] +id = "git-remote-list" +area = "git" +argv = ["git", "remote"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:configured-remotes" +recipients = [] +commit = "" + +[[case]] +id = "git-remote-add-https" +area = "git" +argv = ["git", "remote", "add", "origin", "https://example.test/store.git"] +stdin = "none" +tty = false +status = 0 +outcome = "configure:https-remote" +recipients = [] +commit = "" + +[[case]] +id = "git-config-get" +area = "git" +argv = ["git", "config", "--get", "remote.origin.url"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:https-remote" +recipients = [] +commit = "" + +[[case]] +id = "git-config-set" +area = "git" +argv = ["git", "config", "remote.origin.url", "https://example.test/other.git"] +stdin = "none" +tty = false +status = 0 +outcome = "configure:https-remote" +recipients = [] +commit = "" + +[[case]] +id = "git-fetch" +area = "git" +argv = ["git", "fetch", "origin"] +stdin = "credential-ref:fixture-server/fixture-app" +tty = false +status = 0 +outcome = "embedded-https-fetch" +recipients = [] +commit = "" + +[[case]] +id = "git-pull" +area = "git" +argv = ["git", "pull", "origin", "main"] +stdin = "credential-ref:fixture-server/fixture-app" +tty = false +status = 0 +outcome = "embedded-https-fetch-and-merge" +recipients = [] +commit = "" + +[[case]] +id = "git-push" +area = "git" +argv = ["git", "push", "origin", "main"] +stdin = "credential-ref:fixture-server/fixture-app" +tty = false +status = 0 +outcome = "embedded-https-push" +recipients = [] +commit = "" + +[[case]] +id = "git-forbidden-ssh-scheme" +area = "git" +argv = ["git", "remote", "add", "origin", "ssh://example.test/store.git"] +stdin = "none" +tty = false +status = 1 +outcome = "reject-before-transport:no-change" +recipients = [] +commit = "" + +[[case]] +id = "git-forbidden-scp-syntax" +area = "git" +argv = ["git", "remote", "add", "origin", "git@example.test:store.git"] +stdin = "none" +tty = false +status = 1 +outcome = "reject-before-transport:no-change" +recipients = [] +commit = "" + +[[case]] +id = "git-forbidden-git-scheme" +area = "git" +argv = ["git", "remote", "add", "origin", "git://example.test/store.git"] +stdin = "none" +tty = false +status = 1 +outcome = "reject-before-transport:no-change" +recipients = [] +commit = "" + +[[case]] +id = "git-forbidden-file-scheme" +area = "git" +argv = ["git", "remote", "add", "origin", "file:///tmp/store.git"] +stdin = "none" +tty = false +status = 1 +outcome = "reject-before-transport:no-change" +recipients = [] +commit = "" + +[[case]] +id = "git-forbidden-local-path" +area = "git" +argv = ["git", "remote", "add", "origin", "../store.git"] +stdin = "none" +tty = false +status = 1 +outcome = "reject-before-transport:no-change" +recipients = [] +commit = "" + +[[case]] +id = "git-forbidden-helper-scheme" +area = "git" +argv = ["git", "remote", "add", "origin", "ext::helper command"] +stdin = "none" +tty = false +status = 1 +outcome = "reject-before-transport:no-change" +recipients = [] +commit = "" + +[[case]] +id = "git-unsupported-passthrough" +area = "git" +argv = ["git", "rebase", "main"] +stdin = "none" +tty = false +status = 1 +outcome = "unsupported-workflow:no-change" +recipients = [] +commit = "" + +[[case]] +id = "otp-code-default" +area = "otp" +argv = ["otp", "otp/totp"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:totp-code" +recipients = [] +commit = "" +clock = "1970-01-01T00:00:59Z" + +[[case]] +id = "otp-code-explicit-clip" +area = "otp" +argv = ["otp", "code", "--clip", "otp/totp"] +stdin = "none" +tty = false +status = 0 +outcome = "clipboard:totp-code-no-secret-stdout" +recipients = [] +commit = "" +clock = "1970-01-01T00:00:59Z" + +[[case]] +id = "otp-show-alias-short-clip" +area = "otp" +argv = ["otp", "show", "-c", "otp/totp"] +stdin = "none" +tty = false +status = 0 +outcome = "clipboard:totp-code-no-secret-stdout" +recipients = [] +commit = "" +clock = "1970-01-01T00:00:59Z" + +[[case]] +id = "otp-hotp-increment" +area = "otp" +argv = ["otp", "code", "otp/hotp"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:hotp-code-and-atomic-counter-increment" +recipients = ["alice"] +commit = "Increment HOTP counter for otp/hotp." + +[[case]] +id = "otp-insert-uri" +area = "otp" +argv = ["otp", "insert", "otp/new"] +stdin = "secret-confirmed:otpauth-uri" +tty = true +status = 0 +outcome = "write:otp/new.gpg" +recipients = ["alice"] +commit = "Add OTP secret for otp/new to store." + +[[case]] +id = "otp-add-alias-force-echo" +area = "otp" +argv = ["otp", "add", "--force", "--echo", "otp/new"] +stdin = "line:otpauth-uri" +tty = true +status = 0 +outcome = "overwrite:otp/new.gpg" +recipients = ["alice"] +commit = "Add OTP secret for otp/new to store." + +[[case]] +id = "otp-add-alias-short-options" +area = "otp" +argv = ["otp", "add", "-f", "-e", "otp/new"] +stdin = "line:otpauth-uri" +tty = true +status = 0 +outcome = "overwrite:otp/new.gpg" +recipients = ["alice"] +commit = "Add OTP secret for otp/new to store." + +[[case]] +id = "otp-insert-derived-path-secret-short-options" +area = "otp" +argv = ["otp", "insert", "-s", "-i", "Issuer", "-a", "account"] +stdin = "secret-confirmed:JBSWY3DPEHPK3PXP;decision:yes" +tty = true +status = 0 +outcome = "write:Issuer/account.gpg-derived-uri" +recipients = ["alice"] +commit = "Add OTP secret for Issuer/account to store." + +[[case]] +id = "otp-insert-derived-path-decline" +area = "otp" +argv = ["otp", "insert", "--secret", "--issuer", "Issuer"] +stdin = "line:JBSWY3DPEHPK3PXP;decision:no" +tty = true +status = 1 +outcome = "cancel:no-change" +recipients = [] +commit = "" + +[[case]] +id = "otp-insert-secret-long-options" +area = "otp" +argv = ["otp", "insert", "--secret", "--issuer", "Issuer", "--account", "account", "otp/new"] +stdin = "line:JBSWY3DPEHPK3PXP" +tty = false +status = 0 +outcome = "write:otp/new.gpg-derived-uri" +recipients = ["alice"] +commit = "Add OTP secret for otp/new to store." + +[[case]] +id = "otp-append" +area = "otp" +argv = ["otp", "append", "email/personal"] +stdin = "line:otpauth-uri" +tty = false +status = 0 +outcome = "append-uri-preserve-existing-bytes" +recipients = ["alice"] +commit = "Append OTP secret for email/personal." + +[[case]] +id = "otp-append-replace-decline" +area = "otp" +argv = ["otp", "append", "otp/totp"] +stdin = "decision:no" +tty = true +status = 1 +outcome = "cancel:no-change" +recipients = [] +commit = "" + +[[case]] +id = "otp-append-replace-force" +area = "otp" +argv = ["otp", "append", "-f", "otp/totp"] +stdin = "line:otpauth-uri" +tty = false +status = 0 +outcome = "replace-first-uri-preserve-other-lines" +recipients = ["alice"] +commit = "Replace OTP secret for otp/totp." + +[[case]] +id = "otp-append-secret-long-options" +area = "otp" +argv = ["otp", "append", "--force", "--echo", "--secret", "--issuer", "Issuer", "--account", "account", "otp/totp"] +stdin = "line:JBSWY3DPEHPK3PXP" +tty = true +status = 0 +outcome = "replace-first-uri-preserve-other-lines" +recipients = ["alice"] +commit = "Replace OTP secret for otp/totp." + +[[case]] +id = "otp-uri" +area = "otp" +argv = ["otp", "uri", "otp/totp"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:uri" +recipients = [] +commit = "" + +[[case]] +id = "otp-uri-clip" +area = "otp" +argv = ["otp", "uri", "--clip", "otp/totp"] +stdin = "none" +tty = false +status = 0 +outcome = "clipboard:uri-no-secret-stdout" +recipients = [] +commit = "" + +[[case]] +id = "otp-uri-qrcode-short" +area = "otp" +argv = ["otp", "uri", "-q", "otp/totp"] +stdin = "none" +tty = false +status = 0 +outcome = "qrcode:uri" +recipients = [] +commit = "" + +[[case]] +id = "otp-uri-qrcode-long" +area = "otp" +argv = ["otp", "uri", "--qrcode", "otp/totp"] +stdin = "none" +tty = false +status = 0 +outcome = "qrcode:uri" +recipients = [] +commit = "" + +[[case]] +id = "otp-uri-mutually-exclusive" +area = "otp" +argv = ["otp", "uri", "--clip", "--qrcode", "otp/totp"] +stdin = "none" +tty = false +status = 1 +outcome = "usage-error:no-change" +recipients = [] +commit = "" + +[[case]] +id = "otp-validate-valid" +area = "otp" +argv = ["otp", "validate", "otpauth://totp/account?secret=JBSWY3DPEHPK3PXP"] +stdin = "none" +tty = false +status = 0 +outcome = "valid:no-output" +recipients = [] +commit = "" + +[[case]] +id = "otp-validate-missing-secret" +area = "otp" +argv = ["otp", "validate", "otpauth://totp/account?issuer=Fixture"] +stdin = "none" +tty = false +status = 1 +outcome = "invalid:no-change" +recipients = [] +commit = "" + +[[case]] +id = "otp-validate-missing-hotp-counter" +area = "otp" +argv = ["otp", "validate", "otpauth://hotp/account?secret=JBSWY3DPEHPK3PXP"] +stdin = "none" +tty = false +status = 1 +outcome = "invalid:no-change" +recipients = [] +commit = "" + +[[case]] +id = "otp-validate-malformed" +area = "otp" +argv = ["otp", "validate", "https://example.test/not-otp"] +stdin = "none" +tty = false +status = 1 +outcome = "invalid:no-change" +recipients = [] +commit = "" + +[[case]] +id = "otp-help" +area = "otp" +argv = ["otp", "help"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:otp-help" +recipients = [] +commit = "" + +[[case]] +id = "otp-help-long" +area = "otp" +argv = ["otp", "--help"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:otp-help" +recipients = [] +commit = "" + +[[case]] +id = "otp-help-short" +area = "otp" +argv = ["otp", "-h"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:otp-help" +recipients = [] +commit = "" + +[[case]] +id = "otp-version" +area = "otp" +argv = ["otp", "version"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:1.1.1" +recipients = [] +commit = "" + +[[case]] +id = "otp-version-long" +area = "otp" +argv = ["otp", "--version"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:1.1.1" +recipients = [] +commit = "" + +[[case]] +id = "help" +area = "meta" +argv = ["help"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:help" +recipients = [] +commit = "" + +[[case]] +id = "help-long" +area = "meta" +argv = ["--help"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:help" +recipients = [] +commit = "" + +[[case]] +id = "version" +area = "meta" +argv = ["version"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:version" +recipients = [] +commit = "" + +[[case]] +id = "version-long" +area = "meta" +argv = ["--version"] +stdin = "none" +tty = false +status = 0 +outcome = "stdout:version" +recipients = [] +commit = "" + +[[case]] +id = "unknown-command-as-show" +area = "show" +argv = ["does-not-exist"] +stdin = "none" +tty = false +status = 1 +outcome = "not-found:no-change" +recipients = [] +commit = "" diff --git a/crates/storage/tests/fixtures/compatibility/expected/basic/email/personal.txt b/crates/storage/tests/fixtures/compatibility/expected/basic/email/personal.txt new file mode 100644 index 0000000..a5d1e4f --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/expected/basic/email/personal.txt @@ -0,0 +1,3 @@ +correct horse fixture +login: alice@example.test +url: https://example.test diff --git a/crates/storage/tests/fixtures/compatibility/expected/basic/otp/hotp.txt b/crates/storage/tests/fixtures/compatibility/expected/basic/otp/hotp.txt new file mode 100644 index 0000000..9f78d26 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/expected/basic/otp/hotp.txt @@ -0,0 +1 @@ +otpauth://hotp/IronStorage:counter?secret=JBSWY3DPEHPK3PXP&issuer=IronStorage&counter=0&digits=8 diff --git a/crates/storage/tests/fixtures/compatibility/expected/basic/otp/totp.txt b/crates/storage/tests/fixtures/compatibility/expected/basic/otp/totp.txt new file mode 100644 index 0000000..626f616 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/expected/basic/otp/totp.txt @@ -0,0 +1,2 @@ +fixture-password +otpauth://totp/IronStorage:alice%40example.test?secret=JBSWY3DPEHPK3PXP&issuer=IronStorage&algorithm=SHA1&digits=6&period=30 diff --git a/crates/storage/tests/fixtures/compatibility/expected/basic/shared/multiple.txt b/crates/storage/tests/fixtures/compatibility/expected/basic/shared/multiple.txt new file mode 100644 index 0000000..d71a403 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/expected/basic/shared/multiple.txt @@ -0,0 +1,2 @@ +shared-fixture-password +recipients: alice,bob diff --git a/crates/storage/tests/fixtures/compatibility/expected/basic/team/service.txt b/crates/storage/tests/fixtures/compatibility/expected/basic/team/service.txt new file mode 100644 index 0000000..8a37571 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/expected/basic/team/service.txt @@ -0,0 +1,2 @@ +team-fixture-password +owner: bob diff --git a/crates/storage/tests/fixtures/compatibility/expected/basic/unicode/咖啡.txt b/crates/storage/tests/fixtures/compatibility/expected/basic/unicode/咖啡.txt new file mode 100644 index 0000000..7d14aa2 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/expected/basic/unicode/咖啡.txt @@ -0,0 +1,3 @@ +pässwörd-猫 +login: 用户@example.test +notes: café diff --git a/crates/storage/tests/fixtures/compatibility/expected/nested/outer/inner/nested-entry.txt b/crates/storage/tests/fixtures/compatibility/expected/nested/outer/inner/nested-entry.txt new file mode 100644 index 0000000..3a9928a --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/expected/nested/outer/inner/nested-entry.txt @@ -0,0 +1 @@ +inner repository entry diff --git a/crates/storage/tests/fixtures/compatibility/expected/nested/outer/root-entry.txt b/crates/storage/tests/fixtures/compatibility/expected/nested/outer/root-entry.txt new file mode 100644 index 0000000..9516ea1 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/expected/nested/outer/root-entry.txt @@ -0,0 +1 @@ +outer repository entry diff --git a/crates/storage/tests/fixtures/compatibility/generated.toml b/crates/storage/tests/fixtures/compatibility/generated.toml new file mode 100644 index 0000000..6048c60 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/generated.toml @@ -0,0 +1,105 @@ +format = 1 +generated_by = "cargo run -p ironstorage --example refresh_compatibility_fixtures" +fixture_seed = "49 repeated 32 times (hex); checked-in keys are reused on refresh" + +[[keys]] +name = "alice" +user_id = "Alice Fixture " +primary_fingerprint = "7E5C5241B25F6FFAAD717EBFA132DCB2DC23AE30" +encryption_subkey_fingerprint = "10208D0B84AD13D2157795D5EB55885EC93C0A26" +passphrase = "fixture-alice-passphrase" +public_armor = "keys/alice-public.asc" +public_binary = "keys/alice-public.pgp" +secret_armor = "keys/alice-secret.asc" +secret_binary = "keys/alice-secret.pgp" + +[[keys]] +name = "bob" +user_id = "Bob Fixture " +primary_fingerprint = "B37027B56FC406BD3F6A622B2AC03492B992D06F" +encryption_subkey_fingerprint = "46E34F32333752A01E4E019B4AC0B1A0B4D6278D" +passphrase = "fixture-bob-passphrase" +public_armor = "keys/bob-public.asc" +public_binary = "keys/bob-public.pgp" +secret_armor = "keys/bob-secret.asc" +secret_binary = "keys/bob-secret.pgp" + +[[entries]] +store = "basic" +path = "email/personal.gpg" +plaintext = "email/personal.txt" +recipients = ["7E5C5241B25F6FFAAD717EBFA132DCB2DC23AE30"] +ciphertext_sha256 = "8259de0c1ae6b5d4fc3ebd410452f96a266b0d55d111cfaf9c609baf3deda1b2" + +[[entries]] +store = "basic" +path = "unicode/咖啡.gpg" +plaintext = "unicode/咖啡.txt" +recipients = ["7E5C5241B25F6FFAAD717EBFA132DCB2DC23AE30"] +ciphertext_sha256 = "c5c829b46e517109bcfea7731c846ca6d09eff8076fe4444aaa1c2512fc06d44" + +[[entries]] +store = "basic" +path = "otp/totp.gpg" +plaintext = "otp/totp.txt" +recipients = ["7E5C5241B25F6FFAAD717EBFA132DCB2DC23AE30"] +ciphertext_sha256 = "260a0d7371964ca9d115e2d818cf1a8a63e2a4ff9a172ca421c569aa649cf014" + +[[entries]] +store = "basic" +path = "otp/hotp.gpg" +plaintext = "otp/hotp.txt" +recipients = ["7E5C5241B25F6FFAAD717EBFA132DCB2DC23AE30"] +ciphertext_sha256 = "337bb6568bc3805b8c269463654b081da88d3755da62603b263b6865babf9bd2" + +[[entries]] +store = "basic" +path = "team/service.gpg" +plaintext = "team/service.txt" +recipients = ["B37027B56FC406BD3F6A622B2AC03492B992D06F"] +ciphertext_sha256 = "b325904fbdbe335bc9cd2319ace3e598da194e6450bf3f47371507b1f47306ae" + +[[entries]] +store = "basic" +path = "shared/multiple.gpg" +plaintext = "shared/multiple.txt" +recipients = [ + "7E5C5241B25F6FFAAD717EBFA132DCB2DC23AE30", + "B37027B56FC406BD3F6A622B2AC03492B992D06F", +] +ciphertext_sha256 = "7a8ac913dfb930d6ed8d98d40b12be9f522685c5be45d6b2b30b1bc407b5996b" + +[[entries]] +store = "nested" +path = "outer/root-entry.gpg" +plaintext = "outer/root-entry.txt" +recipients = ["7E5C5241B25F6FFAAD717EBFA132DCB2DC23AE30"] +ciphertext_sha256 = "58c1470b623ec7076ddf52e8c3a8372e508ab4b84b7ad146e7878d85e4b872de" + +[[entries]] +store = "nested" +path = "outer/inner/nested-entry.gpg" +plaintext = "outer/inner/nested-entry.txt" +recipients = ["B37027B56FC406BD3F6A622B2AC03492B992D06F"] +ciphertext_sha256 = "b8757d983ad7d78205f3f7b74cfb22b949aebae25bc70febcffe0c5d6ec4882b" + +[[repositories]] +name = "basic" +template = "repositories/basic.git" +head = "c1bec8815d40fab6e23162443ef55ce1c46bddf6" +commits = [ + "a7d88415df6027082096f6fd035387ac76726920", + "c1bec8815d40fab6e23162443ef55ce1c46bddf6", +] + +[[repositories]] +name = "outer" +template = "repositories/outer.git" +head = "2c32c59867d2e4b4ab07ca8e9225e8eaa887a58e" +commits = ["2c32c59867d2e4b4ab07ca8e9225e8eaa887a58e"] + +[[repositories]] +name = "inner" +template = "repositories/inner.git" +head = "308371ba7fd91255536f4e6afee2e23a02af6915" +commits = ["308371ba7fd91255536f4e6afee2e23a02af6915"] diff --git a/crates/storage/tests/fixtures/compatibility/keys/alice-public.asc b/crates/storage/tests/fixtures/compatibility/keys/alice-public.asc new file mode 100644 index 0000000..8f7ac0d --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/keys/alice-public.asc @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xjMEZZIAgBYJKwYBBAHaRw8BAQdAZihauC+eAYkipuDUFUMqq+UVCvWVZCK8gvTi +rkx1grXNKUFsaWNlIEZpeHR1cmUgPGFsaWNlQGlyb25zdG9yYWdlLmludmFsaWQ+ +wokEExYIADEFAmp457IWIQR+XFJBsl9v+q1xfr+hMtyy3COuMAIbAwIeAQMLCQcC +FQgBFgEnAhkBAAoJEKEy3LLcI64wEAABAKqDzgiRwCBFC+kcbKGNOWO9O/nKVDHR +Q2d4r8HOUXItAQCTGZHFaBkaZNyDjAOLTBBWMBxa0i5WatkAx5ZnKJT8Cs44BGWS +AIASCisGAQQBl1UBBQEBB0BIPO/SwIrdEJLCGmXEG1/szwa9u7WK0rPQArtvrYMx +PAMBCAfCeAQYFggAIAUCanjnswIbDBYhBH5cUkGyX2/6rXF+v6Ey3LLcI64wAAoJ +EKEy3LLcI64wyKIA/i7fuf/N31swIwzJXLBkzU4BvWJfv6NuHuvQej4qmnhrAQDi +ZJ+POdCbAxeTE7wNT5llULI8oowyUXfcF7C7UzDCAw== +=Mbap +-----END PGP PUBLIC KEY BLOCK----- diff --git a/crates/storage/tests/fixtures/compatibility/keys/alice-public.pgp b/crates/storage/tests/fixtures/compatibility/keys/alice-public.pgp new file mode 100644 index 0000000000000000000000000000000000000000..74fadb06a47d3c8d7ed95de9ed3071b8d14b2ba4 GIT binary patch literal 415 zcmX?R%#u2Zp+SsOn~jl$@s>M3BO|**nnu(P{dtU?O3NNx5p~vD{Zy3e>(msbJxyO8 zt@9~u+Im*gF()%QRlzN@qNKDaRlz0^#C6Cl%Fin<$uCMwPu0uJD+3Cq*d6L*5fBWZSU@_U6(drVA`F(w%O2znURD2Pz8$w&=U$^Pi$tA=E30)WH+B!#PCn= z{?7kr??)Rb^PG&?kaE_Kac@%m{>6E6uP;>DY0aw0W@LDjGQZ#Q!fa;o$-;Yh{b!~I UY_eI@V-#3^M|{KXV1q-<01-Z&yZ`_I literal 0 HcmV?d00001 diff --git a/crates/storage/tests/fixtures/compatibility/keys/alice-secret.asc b/crates/storage/tests/fixtures/compatibility/keys/alice-secret.asc new file mode 100644 index 0000000..2752edb --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/keys/alice-secret.asc @@ -0,0 +1,17 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +xYYEZZIAgBYJKwYBBAHaRw8BAQdAZihauC+eAYkipuDUFUMqq+UVCvWVZCK8gvTi +rkx1grX+CQMIY/R1q1/mEXzgJkJdUlNlns+1/EtfSNxLhlzT8XaaLL/pf7ZPI7e0 +mvzQuBEvkEGTVAxt2u80sGMY7mbZvcG6L++dUpP1FqvPdM8fjCv8PM0pQWxpY2Ug +Rml4dHVyZSA8YWxpY2VAaXJvbnN0b3JhZ2UuaW52YWxpZD7CiQQTFggAMQUCanjn +shYhBH5cUkGyX2/6rXF+v6Ey3LLcI64wAhsDAh4BAwsJBwIVCAEWAScCGQEACgkQ +oTLcstwjrjAQAAEAqoPOCJHAIEUL6RxsoY05Y707+cpUMdFDZ3ivwc5Rci0BAJMZ +kcVoGRpk3IOMA4tMEFYwHFrSLlZq2QDHlmcolPwKx4sEZZIAgBIKKwYBBAGXVQEF +AQEHQEg879LAit0QksIaZcQbX+zPBr27tYrSs9ACu2+tgzE8AwEIB/4JAwhRDjBN +dWgYi+AGAFqqDR6n0O+kc1WyeUYI+G8+R9EDz6Ar0xGGaZZ8GUU17BWGDY/UbxUU +z5LHAQ2KB05GYf/9VjHTaKZCSiKFJlCElEASwngEGBYIACAFAmp457MCGwwWIQR+ +XFJBsl9v+q1xfr+hMtyy3COuMAAKCRChMtyy3COuMMiiAP4u37n/zd9bMCMMyVyw +ZM1OAb1iX7+jbh7r0Ho+Kpp4awEA4mSfjznQmwMXkxO8DU+ZZVCyPKKMMlF33Bew +u1MwwgM= +=ij6E +-----END PGP PRIVATE KEY BLOCK----- diff --git a/crates/storage/tests/fixtures/compatibility/keys/alice-secret.pgp b/crates/storage/tests/fixtures/compatibility/keys/alice-secret.pgp new file mode 100644 index 0000000000000000000000000000000000000000..12a79dad3f6b41d38576cc5828030a7b6a1793ec GIT binary patch literal 581 zcmX@w#*#XTp+SsOn~jl$@s>M3BO|**nnu(P{dtU?O3NNx5p~vD{Zy3e>(msbJxyO8 zt@9~u+WL=^nIrj2>FW4rf;A7+oMMB5Q|F!E`o}xoQ8W-9Kw@(>%GZ_WQliaH}@XgrT>0z(B!XTtIwC5m+#U3V{=y1F()%QRlzN@ zqNKDaRlz0^#C6Cl%Fin<$uCMwPu0uJD+3Cq*d6L*5fWI-4&R0NGkP+bo`t1YpSTBbM%9jaiF0EUYK zI9xU}N%PveTMXkni{N literal 0 HcmV?d00001 diff --git a/crates/storage/tests/fixtures/compatibility/keys/bob-public.asc b/crates/storage/tests/fixtures/compatibility/keys/bob-public.asc new file mode 100644 index 0000000..b356d0a --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/keys/bob-public.asc @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xjMEZZIAgBYJKwYBBAHaRw8BAQdANd08TxPj0ZuIl1cMvEf0FMCxubDwlzXDLX6w +9M+959XNJUJvYiBGaXh0dXJlIDxib2JAaXJvbnN0b3JhZ2UuaW52YWxpZD7CiQQT +FggAMQUCanjntBYhBLNwJ7VvxAa9P2piKyrANJK5ktBvAhsDAh4BAwsJBwIVCAEW +AScCGQEACgkQKsA0krmS0G9BCwD/daZy2BdmNQX8TldPBtoLTrugeaFmtrcVDQZ3 +AzWEMcMA/16q87Zkaw2ozanLSBcRiq4iZnBTAmU/7bUSlA2myZoHzjgEZZIAgBIK +KwYBBAGXVQEFAQEHQByJpci1aMxEcUVlw9mByZUPyaQ7kI1emdJOuN1hcLEKAwEI +B8J4BBgWCAAgBQJqeOe1AhsMFiEEs3AntW/EBr0/amIrKsA0krmS0G8ACgkQKsA0 +krmS0G+2lwEAvD9R7o52H37GsfeMS15UJBeIHBaYHjpsIyfoOWq0vsEA/j0oL4IG +SIakL3z1C1KHU+4iz3rzz2vNnhgGQd7+DS8H +=vS0q +-----END PGP PUBLIC KEY BLOCK----- diff --git a/crates/storage/tests/fixtures/compatibility/keys/bob-public.pgp b/crates/storage/tests/fixtures/compatibility/keys/bob-public.pgp new file mode 100644 index 0000000000000000000000000000000000000000..8f44a5e967b5b587053a39ca1136cec6ce7aed82 GIT binary patch literal 411 zcmX?R%#u2Zp+SsOn~jl$@s>M3BO|+m>0KLt;l~$ecT5lG+2j62ut~~Ka>y*o&nqs;FG@^L)yvE)OU%hku{+esA}q$i zV93gpRq=d_m?F#O0`;x=N7(k-XC-NC9Wa@+bJB%;CTV6SIYwq~PIe|y4n{FXbtXwh z1};tkn7ku5!~fD{MK{FLOj-ZQEQye%c0 zcg5M2r#-|4yVfbC6$CS-+P~c@G=+EB$yw~@EWq9r;sSefdMG0+$e%KuOHXXgIO9_2 zntJ$VgZT#Nj9T%b^-XSA4L(VEkS^b4&)|Pz-8UER7=r^%>v@OxE`N|#C9{f(} UeAVak*=OfTusPoQ$E(i{0D-BS%>V!Z literal 0 HcmV?d00001 diff --git a/crates/storage/tests/fixtures/compatibility/keys/bob-secret.asc b/crates/storage/tests/fixtures/compatibility/keys/bob-secret.asc new file mode 100644 index 0000000..f1eed94 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/keys/bob-secret.asc @@ -0,0 +1,17 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +xYYEZZIAgBYJKwYBBAHaRw8BAQdANd08TxPj0ZuIl1cMvEf0FMCxubDwlzXDLX6w +9M+959X+CQMIx1A5+eCcr6bgYLtP6nkTdu/qlT/dwB7B38PIlZekNRoN2yd48DcO +WmOqWrkE+Fxovj8zITOJUGP16kHJjW0727wsY1LI4BrX2Gsa3m0jV80lQm9iIEZp +eHR1cmUgPGJvYkBpcm9uc3RvcmFnZS5pbnZhbGlkPsKJBBMWCAAxBQJqeOe0FiEE +s3AntW/EBr0/amIrKsA0krmS0G8CGwMCHgEDCwkHAhUIARYBJwIZAQAKCRAqwDSS +uZLQb0ELAP91pnLYF2Y1BfxOV08G2gtOu6B5oWa2txUNBncDNYQxwwD/XqrztmRr +DajNqctIFxGKriJmcFMCZT/ttRKUDabJmgfHiwRlkgCAEgorBgEEAZdVAQUBAQdA +HImlyLVozERxRWXD2YHJlQ/JpDuQjV6Z0k643WFwsQoDAQgH/gkDCJdPSdzYfIpD +4M3cgwcGp2Ok/jR9F18ObkKhc/M6UfllIkZKLNl9zjUnDagCIzy+ZiLWTxAAKl02 +bGkW56gOcrp+PIARjn85GEBRXXYogE7CeAQYFggAIAUCanjntQIbDBYhBLNwJ7Vv +xAa9P2piKyrANJK5ktBvAAoJECrANJK5ktBvtpcBALw/Ue6Odh9+xrH3jEteVCQX +iBwWmB46bCMn6DlqtL7BAP49KC+CBkiGpC989QtSh1PuIs96889rzZ4YBkHe/g0v +Bw== +=w2Yf +-----END PGP PRIVATE KEY BLOCK----- diff --git a/crates/storage/tests/fixtures/compatibility/keys/bob-secret.pgp b/crates/storage/tests/fixtures/compatibility/keys/bob-secret.pgp new file mode 100644 index 0000000000000000000000000000000000000000..2b710a02e976c251d7cc36baf27096e8436f2d79 GIT binary patch literal 577 zcmX@w#*#XTp+SsOn~jl$@s>M3BO|+m>0KLt;l~$ecT5lG+2j62hvY1QoOg-D?XU> zMJ2C_+R5@GCS#wyv7&KjK=RjDjwgF_t#9wqNe(*kK%Hd~Ko$`|u+%hXl zN{dnzY?AVm95RdY^NLIIixSgQ^)mCy5_2+B><)FZ2#awr7_u^DRXpD!rpU6nKz(ce z5w^YdSxMSj2TUgIoOB_dNt&5Sj**$0lbuPFgHeo8ok^0Bfs0cBChy42@V|6f(GBr5 zQ`SFz;r?v5xczo7s9cz~ZM!HhTRF37i{W90|8c86Z%fJMU2%5hX%BJ1u60Uj1;I?I z_HVZeP2pX3au)mXZg9v6ae+f_dMG0+DClH5m!8;~amJ<4HTCe##*J`N zlHZQIi3JD_PLBTiO-}BCmnl9%DXYVw#vn@|v^3SAJJf1JlX<_katH7VBN^V{{ zH*3$Cs`IX3QnuNbrgY6;fI%zPEGJX!`3k9WiT`_Ff%bx&`U2!*Ud~}$k4mM^lHLe_p?);x*fM@mEm|i K)gJ&Y4G;e{6c+&i literal 0 HcmV?d00001 diff --git a/crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/47/d4bb26ec7dc2046e3d72a7868a7578c6c97db1 b/crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/47/d4bb26ec7dc2046e3d72a7868a7578c6c97db1 new file mode 100644 index 0000000000000000000000000000000000000000..d38e8bbc52ba33aedde3ff8341cf8a43c329f484 GIT binary patch literal 57 zcmV-90LK4#oGU3xO;s>4U@$Z=Ff%bxC`c_T&d*EC(MvB#XZV!Zd9v+K>)E>7Y}G4j P-&F~>R8#-}oMIEZhmja& literal 0 HcmV?d00001 diff --git a/crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/68/2ed002ea90ed47cd94e546c738851c08e3954f b/crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/68/2ed002ea90ed47cd94e546c738851c08e3954f new file mode 100644 index 0000000000000000000000000000000000000000..cd473c59bd73031fc0a32949ba8fd66847cfec62 GIT binary patch literal 57 zcmb=J<9+I+e~^J;ib1()shO#fVTqAZsc~^}aY>1xxk-6xQL%}MSy5q`QBk3pQCW$p M*#jm9?Wx?A08)1ol>h($ literal 0 HcmV?d00001 diff --git a/crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/a7/d88415df6027082096f6fd035387ac76726920 b/crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/a7/d88415df6027082096f6fd035387ac76726920 new file mode 100644 index 0000000000000000000000000000000000000000..19bcffa6379291c88fac96e4bc1af110082bef42 GIT binary patch literal 148 zcmV;F0BiqvoUM*Q3c@fD06pgwdl$+kZH)mDy;!^neZe-}>O$K<+=|Z^EcgO%0|Udv zG)25S)sJ4R3Yx(tY1_g%-tLluZTl`aecqcybXBW1YeG`E^E-5D zh80ed)BIdzA@hqy>5pJNR=D#H5w?hkCVm%`{}MATD>SzTqz6rK9wq)-h4}ys?o8{c Ct3ZVS literal 0 HcmV?d00001 diff --git a/crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/bb/ace78ed41918acad8de131f58e1fccbf5b06db b/crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/bb/ace78ed41918acad8de131f58e1fccbf5b06db new file mode 100644 index 0000000000000000000000000000000000000000..d9716cc2f6ba40e4075f70f3981f3747744d1e70 GIT binary patch literal 86 zcmb=J!|2Vh<$p<0YN~>nIfJ2rftiVkf?j$-x^89)Lx$c3rdJc*x}Tl$)a|%Ms|?5E qss1JgK%kJCo0yry;C^Md+MC)#EP1v?%iFq2D~_G4-T2x<_9OshJtE}* literal 0 HcmV?d00001 diff --git a/crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/c1/bec8815d40fab6e23162443ef55ce1c46bddf6 b/crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/c1/bec8815d40fab6e23162443ef55ce1c46bddf6 new file mode 100644 index 0000000000000000000000000000000000000000..134d2aae463b38e0e1afaadb4ae4f0f440b340ba GIT binary patch literal 196 zcmV;#06YJ9oUM+*O2jY_Kt1Ox<}R$0v`tc2L@$D8zhIh~tbsNu)9mW!i|#LYd+^}# zQrouD!Oi1CR}okiN)n>*XlENK5l0p~*gQqCIi)2}3wmD6PAatn1&(pF6K9VuV01Y9 z>@(vq4KYv(J~%%+G<5BsP2pWz{pd~UBJfte`&9+b`3^5dTRrabRO*e^!p~p>j_89! yghxa~lm3>w{7cNdGQ*|Zq{2?e@zs(JHOb8~nE9WPSj|H)7*?qGCP( literal 0 HcmV?d00001 diff --git a/crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/f2/6189c986fc85cd7edb067ba87dee7a13847878 b/crates/storage/tests/fixtures/compatibility/repositories/basic.git/objects/f2/6189c986fc85cd7edb067ba87dee7a13847878 new file mode 100644 index 0000000000000000000000000000000000000000..19509af75bcec21979febbc8aa90546331c75f8a GIT binary patch literal 241 zcmVhshxy}wV^tsF20`Fb0DS(=Cska1oF`vwD~r8~_hb@q@WLS6C%r*Qg7 z#TwWZNCI*PQi|p3!}}T1djVz1WhbO5vipXOoFN7X0Bl}+xuBIK*Ma_Xe!XK=01`lH zY_TW@=AO}F2}Gbag-!8r+i20{ZP)1OXKv}|zS DS/qʘ0DQu +U?8BLYr g"23nXim.OiSFDloA/ę@Eu\ +1<4*'m>.ET \ No newline at end of file diff --git a/crates/storage/tests/fixtures/compatibility/repositories/inner.git/objects/74/3396337e1c5f4f8a2915adf9909fd15e2dbce6 b/crates/storage/tests/fixtures/compatibility/repositories/inner.git/objects/74/3396337e1c5f4f8a2915adf9909fd15e2dbce6 new file mode 100644 index 0000000000000000000000000000000000000000..a572e75c368f37380bbd924dc44e8c7bddb95d1a GIT binary patch literal 190 zcmV;v073tFoB^`{O#fnRZ(<-ZH!uLfUIR+Nv7ofpCyf#T2S8av3e-i__1eqo4^Z@& zY`k}1eD}w-Yfy7jYpEQyl}IqqU+1*4YXk>jqGu!2`hxYpy*y96C~8?7+7QqPr>Df| z9OTjLEwB&7k!mNi9`Q`lNCD+KZ=(!+x7HySJvw7Lg9rravg1Y3clT1PyO(3tn_|sb s^mfoLRj5MbeOd>|LOY%=8lwa0oDN)8ee-tAXD~D{Ff%bx&`U2!*Ud~}$k4mM^lHLe_p?);x*fM@mEm|i u)gP*)C_leMH#M)Ms1l@@Vf{K=p~T{jL#!>&*Kht&$631JF9QGxydfa)ASq-3 literal 0 HcmV?d00001 diff --git a/crates/storage/tests/fixtures/compatibility/repositories/outer.git/objects/2c/32c59867d2e4b4ab07ca8e9225e8eaa887a58e b/crates/storage/tests/fixtures/compatibility/repositories/outer.git/objects/2c/32c59867d2e4b4ab07ca8e9225e8eaa887a58e new file mode 100644 index 0000000000000000000000000000000000000000..6de11b79c09b3b85068dc13fce123503eb6143db GIT binary patch literal 161 zcmV;S0ABxioUM*c3c@fDMqT$5vlq%Vrau7@-3YEdK{`n_U^6nEs>hewE4X{`!TTQZ zI3@*S?M5X=FdaE>JO*m9uH241IolH2W^k_WEY{tCeW1e6dS`))aK0*wzePBwkIoWd zKdf*}!g*T9GUbq5MhgmSkNy)<6mUODFmQdBxVKTtTF2ZJOIzj!_!aG PnQyUtLsZ=#5GB literal 0 HcmV?d00001 diff --git a/crates/storage/tests/fixtures/compatibility/repositories/outer.git/objects/68/2ed002ea90ed47cd94e546c738851c08e3954f b/crates/storage/tests/fixtures/compatibility/repositories/outer.git/objects/68/2ed002ea90ed47cd94e546c738851c08e3954f new file mode 100644 index 0000000000000000000000000000000000000000..cd473c59bd73031fc0a32949ba8fd66847cfec62 GIT binary patch literal 57 zcmb=J<9+I+e~^J;ib1()shO#fVTqAZsc~^}aY>1xxk-6xQL%}MSy5q`QBk3pQCW$p M*#jm9?Wx?A08)1ol>h($ literal 0 HcmV?d00001 diff --git a/crates/storage/tests/fixtures/compatibility/repositories/outer.git/objects/af/ae3d12617388c20584e7afb3f87e0975b0fd00 b/crates/storage/tests/fixtures/compatibility/repositories/outer.git/objects/af/ae3d12617388c20584e7afb3f87e0975b0fd00 new file mode 100644 index 0000000000000000000000000000000000000000..d7564699581a7e2d762c9bb75fcbda8af4ac95d5 GIT binary patch literal 190 zcmV;v073tFoB^`{O#fnRZ(<-ZH!uLfUIXh@h+fG&3MLW(2SBoM^id9-Lr-7qkaZ-q z@h}Xw2h{6iqr|JAMn<*81Y9uBq(G6&Q(Cd_Fc@s-@W0tLwFZ%uMUVLnaU&GOEJ47Y z^IyPfe;eOBMmBAJBWX3#NCB4PR}I1YA$V{f=@Hk6u8iq)pJ^%m! literal 0 HcmV?d00001 diff --git a/crates/storage/tests/fixtures/compatibility/repositories/outer.git/refs/heads/main b/crates/storage/tests/fixtures/compatibility/repositories/outer.git/refs/heads/main new file mode 100644 index 0000000..24940da --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/repositories/outer.git/refs/heads/main @@ -0,0 +1 @@ +2c32c59867d2e4b4ab07ca8e9225e8eaa887a58e diff --git a/crates/storage/tests/fixtures/compatibility/stores/basic/.gpg-id b/crates/storage/tests/fixtures/compatibility/stores/basic/.gpg-id new file mode 100644 index 0000000..682ed00 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/stores/basic/.gpg-id @@ -0,0 +1 @@ +7E5C5241B25F6FFAAD717EBFA132DCB2DC23AE30 diff --git a/crates/storage/tests/fixtures/compatibility/stores/basic/.gpg-id.sig b/crates/storage/tests/fixtures/compatibility/stores/basic/.gpg-id.sig new file mode 100644 index 0000000000000000000000000000000000000000..318bd4c6ed1446dc5ca3bb614b66243af2e5a25c GIT binary patch literal 119 zcmX?9%EBPV!5}N9$Wj**hshxy}wV^ ztsF20`Fb0DS(=Cska1oF`vwD~r8~_hb@q@WLS6C%r*Qg7#TwWZNCI*PQi|p3!}}T1 zdjVz1WhbO5vipXOoFN7X0Bl}+xuBIK*Ma_Xe!XK=01`lHY_TW@=AO}F2}Gbag-!8r X+i20{ZP)1OXKv}|zҒGM+n/xu=u;CVnA۬v!7a]XV@x] {UIXh@h+fG&3MLW(2S6N&rO35t%tUcTWy9Hl$(0Yuq&tv}UYXKPxZPoJ zu?jE@AQnlxQa(u^t~UqI_4Gn8fmEr~aiEU1$iS1&dhbVgRcU4Hpguymsf`LvmEU{P zzX3Aj0r{e7y3x68ok#*^Ado1a`4!&JjxGy8Ng>uV+yV9ph{SdA=l2#vd40CYVN;bp z^ceMyocf$?Zi5)fcY=!Ug+U{AQOvEN`0l30>7A6~l_6j&+LZ6pLFTr<&J`v@^njLK zCjqQm(c!j9bL$VNvO0M41=HdqFs7lT(X0~Y7sH3#R@DANjdu-T=g3UZ-=|9W0OwaM nxO=V4+6%L92&Csd%U`~Nt=@nw5>tttRb^N2_;`nrE~R)w8Q_#m literal 0 HcmV?d00001 diff --git a/crates/storage/tests/fixtures/compatibility/stores/basic/shared/.gpg-id b/crates/storage/tests/fixtures/compatibility/stores/basic/shared/.gpg-id new file mode 100644 index 0000000..01b3ec3 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/stores/basic/shared/.gpg-id @@ -0,0 +1,2 @@ +7E5C5241B25F6FFAAD717EBFA132DCB2DC23AE30 +B37027B56FC406BD3F6A622B2AC03492B992D06F diff --git a/crates/storage/tests/fixtures/compatibility/stores/basic/shared/multiple.gpg b/crates/storage/tests/fixtures/compatibility/stores/basic/shared/multiple.gpg new file mode 100644 index 0000000..6b2f40a --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/stores/basic/shared/multiple.gpg @@ -0,0 +1,3 @@ +^U^< +&@Db +UIJୠx[O0l]2+'jfEd+ܲɜEbx4Fj'M^J'@>Ә&Ίcd\_x0#۴6AX u'c(hȎx`Ńhrm_𨗲w:^>z#7:$UJEN6VOl$սZ5)^/./Vn_[&%݂E{& \ No newline at end of file diff --git a/crates/storage/tests/fixtures/compatibility/stores/basic/team/.gpg-id b/crates/storage/tests/fixtures/compatibility/stores/basic/team/.gpg-id new file mode 100644 index 0000000..1290ab9 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/stores/basic/team/.gpg-id @@ -0,0 +1 @@ +B37027B56FC406BD3F6A622B2AC03492B992D06F diff --git a/crates/storage/tests/fixtures/compatibility/stores/basic/team/.gpg-id.sig b/crates/storage/tests/fixtures/compatibility/stores/basic/team/.gpg-id.sig new file mode 100644 index 0000000000000000000000000000000000000000..7dd01829e47c1bac8f70c644bae619253e674451 GIT binary patch literal 119 zcmX?9%EBPV!5}N9$g;UWeQW*^w!QXQN!nTmOeXD|bRnOWDRmM<0|OVQ08Hf2Dn^Db zmClnu0-a73?hkv|kZn$1C^>vHK Vmt%Ji_Izh+*|T-6V$m!eJ^=7yG>QNK literal 0 HcmV?d00001 diff --git a/crates/storage/tests/fixtures/compatibility/stores/basic/team/service.gpg b/crates/storage/tests/fixtures/compatibility/stores/basic/team/service.gpg new file mode 100644 index 0000000..8b1d01b --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/stores/basic/team/service.gpg @@ -0,0 +1 @@ +^J'@REmFz}wMy?T9 L0Y馺"Gw/Jfy2VtϫX;hR!܎bd'E^[̓_|cwk 0j=x8(jqV7qWK.+'9)W o U9 \ No newline at end of file diff --git a/crates/storage/tests/fixtures/compatibility/stores/basic/unicode/咖啡.gpg b/crates/storage/tests/fixtures/compatibility/stores/basic/unicode/咖啡.gpg new file mode 100644 index 0000000..1130164 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/stores/basic/unicode/咖啡.gpg @@ -0,0 +1,2 @@ +^U^< +&@cf}Á[v`%@vw0Sgшbl HhGG.ĊKhʄӞP ~x2I4<%1Bmk?Yoڻ\2Tb%F,g鈃hmC?{gߏiz_gҫ \ No newline at end of file diff --git a/crates/storage/tests/fixtures/compatibility/stores/nested/outer/.gpg-id b/crates/storage/tests/fixtures/compatibility/stores/nested/outer/.gpg-id new file mode 100644 index 0000000..682ed00 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/stores/nested/outer/.gpg-id @@ -0,0 +1 @@ +7E5C5241B25F6FFAAD717EBFA132DCB2DC23AE30 diff --git a/crates/storage/tests/fixtures/compatibility/stores/nested/outer/inner/.gpg-id b/crates/storage/tests/fixtures/compatibility/stores/nested/outer/inner/.gpg-id new file mode 100644 index 0000000..1290ab9 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/stores/nested/outer/inner/.gpg-id @@ -0,0 +1 @@ +B37027B56FC406BD3F6A622B2AC03492B992D06F diff --git a/crates/storage/tests/fixtures/compatibility/stores/nested/outer/inner/nested-entry.gpg b/crates/storage/tests/fixtures/compatibility/stores/nested/outer/inner/nested-entry.gpg new file mode 100644 index 0000000..7433963 --- /dev/null +++ b/crates/storage/tests/fixtures/compatibility/stores/nested/outer/inner/nested-entry.gpg @@ -0,0 +1,2 @@ +^J'@YC +EPlw`|ǵkPsSkH0_紲kag# = Result>; + +#[derive(Debug, Deserialize)] +pub struct UpstreamManifest { + pub format: u8, + pub project: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct UpstreamProject { + pub name: String, + pub version: String, + pub reported_version: String, + pub tag: String, + pub commit: String, + pub source: String, + pub behavior_source: String, + pub license: String, +} + +#[derive(Debug, Deserialize)] +pub struct BehaviorManifest { + pub format: u8, + #[serde(rename = "case")] + pub cases: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct BehaviorCase { + pub id: String, + pub area: String, + pub argv: Vec, + pub stdin: String, + pub tty: bool, + pub status: i32, + pub outcome: String, + pub recipients: Vec, + pub commit: String, + pub random_stream: Option, + pub clock: Option, +} + +#[derive(Debug, Deserialize)] +pub struct GeneratedManifest { + pub format: u8, + pub generated_by: String, + pub fixture_seed: String, + pub keys: Vec, + pub entries: Vec, + pub repositories: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct KeyRecord { + pub name: String, + pub user_id: String, + pub primary_fingerprint: String, + pub encryption_subkey_fingerprint: String, + pub passphrase: String, + pub public_armor: String, + pub public_binary: String, + pub secret_armor: String, + pub secret_binary: String, +} + +#[derive(Debug, Deserialize)] +pub struct EntryRecord { + pub store: String, + pub path: String, + pub plaintext: String, + pub recipients: Vec, + pub ciphertext_sha256: String, +} + +#[derive(Debug, Deserialize)] +pub struct RepositoryRecord { + pub name: String, + pub template: String, + pub head: String, + pub commits: Vec, +} + +pub struct FixtureSet { + root: PathBuf, + pub upstream: UpstreamManifest, + pub behavior: BehaviorManifest, + pub generated: GeneratedManifest, + pub layouts: toml::Value, +} + +impl FixtureSet { + pub fn load() -> TestResult { + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/compatibility"); + let upstream = parse_toml(root.join("upstream.toml"))?; + let behavior = parse_toml(root.join("behavior.toml"))?; + let generated = parse_toml(root.join("generated.toml"))?; + let layouts = parse_toml(root.join("layouts.toml"))?; + Ok(Self { + root, + upstream, + behavior, + generated, + layouts, + }) + } + + pub fn path(&self, relative: impl AsRef) -> PathBuf { + self.root.join(relative) + } + + pub fn read(&self, relative: impl AsRef) -> TestResult> { + Ok(fs::read(self.path(relative))?) + } + + pub fn key(&self, name: &str) -> TestResult<&KeyRecord> { + self.generated + .keys + .iter() + .find(|key| key.name == name) + .ok_or_else(|| test_error(format!("missing fixture key {name}"))) + } + + pub fn key_by_fingerprint(&self, fingerprint: &str) -> TestResult<&KeyRecord> { + self.generated + .keys + .iter() + .find(|key| key.primary_fingerprint == fingerprint) + .ok_or_else(|| test_error(format!("missing fixture key {fingerprint}"))) + } + + pub fn parse_secret_key(&self, key: &KeyRecord) -> TestResult { + let bytes = self.read(&key.secret_armor)?; + let (secret, _) = SignedSecretKey::from_armor_single(Cursor::new(bytes))?; + Ok(secret) + } + + pub fn parse_public_key(&self, key: &KeyRecord) -> TestResult { + let bytes = self.read(&key.public_armor)?; + let (public, _) = SignedPublicKey::from_armor_single(Cursor::new(bytes))?; + Ok(public) + } + + pub fn materialize_store(&self, name: &str) -> TestResult { + let temporary = tempfile::tempdir()?; + copy_tree(&self.path(Path::new("stores").join(name)), temporary.path())?; + Ok(temporary) + } + + pub fn materialize_repository(&self, name: &str) -> TestResult { + let repository = self + .generated + .repositories + .iter() + .find(|repository| repository.name == name) + .ok_or_else(|| test_error(format!("missing repository fixture {name}")))?; + let temporary = tempfile::tempdir()?; + copy_tree(&self.path(&repository.template), temporary.path())?; + Ok(temporary) + } +} + +pub fn decrypt_entry( + fixture: &FixtureSet, + entry: &EntryRecord, + key: &KeyRecord, +) -> TestResult> { + decrypt_entry_with_passphrase(fixture, entry, key, &key.passphrase) +} + +pub fn decrypt_entry_with_passphrase( + fixture: &FixtureSet, + entry: &EntryRecord, + key: &KeyRecord, + passphrase: &str, +) -> TestResult> { + let secret = fixture.parse_secret_key(key)?; + let ciphertext = fixture.read(Path::new("stores").join(&entry.store).join(&entry.path))?; + let message = Message::from_bytes(Cursor::new(ciphertext))?; + let mut decrypted = message.decrypt(&Password::from(passphrase), &secret)?; + Ok(decrypted.as_data_vec()?) +} + +pub fn validate_key_record(fixture: &FixtureSet, key: &KeyRecord) -> TestResult { + let secret = fixture.parse_secret_key(key)?; + let public = fixture.parse_public_key(key)?; + secret.verify_bindings()?; + public.verify_bindings()?; + + let primary = format!("{:X}", secret.primary_key.fingerprint()); + let encryption = format!("{:X}", secret.secret_subkeys[0].fingerprint()); + if primary != key.primary_fingerprint || encryption != key.encryption_subkey_fingerprint { + return Err(test_error(format!("fingerprint mismatch for {}", key.name))); + } + if secret.to_public_key() != public { + return Err(test_error(format!( + "public and secret fixture mismatch for {}", + key.name + ))); + } + + let binary_secret = fixture.read(&key.secret_binary)?; + let binary_public = fixture.read(&key.public_binary)?; + let parsed_secret = SignedSecretKey::from_bytes(Cursor::new(binary_secret))?; + let parsed_public = SignedPublicKey::from_bytes(Cursor::new(binary_public))?; + if parsed_secret != secret || parsed_public != public { + return Err(test_error(format!( + "armored and binary key mismatch for {}", + key.name + ))); + } + Ok(()) +} + +pub fn validate_entry_record(fixture: &FixtureSet, entry: &EntryRecord) -> TestResult { + let ciphertext = fixture.read(Path::new("stores").join(&entry.store).join(&entry.path))?; + let digest = hex::encode(Sha256::digest(&ciphertext)); + if digest != entry.ciphertext_sha256 { + return Err(test_error(format!( + "ciphertext digest mismatch for {}/{}", + entry.store, entry.path + ))); + } + + let expected = fixture.read( + Path::new("expected") + .join(&entry.store) + .join(&entry.plaintext), + )?; + for recipient in &entry.recipients { + let key = fixture.key_by_fingerprint(recipient)?; + let actual = decrypt_entry(fixture, entry, key)?; + if actual != expected { + return Err(test_error(format!( + "plaintext mismatch for {}/{} and recipient {}", + entry.store, entry.path, key.name + ))); + } + } + Ok(()) +} + +pub fn validate_recipient_signature(fixture: &FixtureSet, store: &str, signer: &str) -> TestResult { + let key = fixture.key(signer)?; + let public = fixture.parse_public_key(key)?; + let recipient_path = Path::new("stores").join(store).join(".gpg-id"); + let signature_path = Path::new("stores").join(store).join(".gpg-id.sig"); + let contents = fixture.read(recipient_path)?; + let signature = DetachedSignature::from_bytes(Cursor::new(fixture.read(signature_path)?))?; + signature.verify(&public.primary_key, &contents)?; + Ok(()) +} + +pub fn validate_repository(fixture: &FixtureSet, repository: &RepositoryRecord) -> TestResult { + let root = fixture.path(&repository.template); + let head_ref = fs::read_to_string(root.join("HEAD"))?; + if head_ref != "ref: refs/heads/main\n" { + return Err(test_error(format!( + "unexpected HEAD for {}", + repository.name + ))); + } + let head = fs::read_to_string(root.join("refs/heads/main"))? + .trim() + .to_owned(); + if head != repository.head { + return Err(test_error(format!("head mismatch for {}", repository.name))); + } + + for path in loose_object_paths(&root.join("objects"))? { + let canonical = inflate(&path)?; + let actual = hex::encode(Sha1::digest(&canonical)); + let directory = path + .parent() + .and_then(Path::file_name) + .and_then(|name| name.to_str()) + .ok_or_else(|| test_error("invalid loose object directory"))?; + let file = path + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| test_error("invalid loose object name"))?; + let expected = format!("{directory}{file}"); + if actual != expected { + return Err(test_error(format!( + "invalid loose object {expected} in {}", + repository.name + ))); + } + } + + for commit in &repository.commits { + let canonical = read_git_object(&root, commit)?; + if !canonical.starts_with(b"commit ") { + return Err(test_error(format!( + "{commit} is not a commit in {}", + repository.name + ))); + } + let separator = canonical + .iter() + .position(|byte| *byte == 0) + .ok_or_else(|| test_error("Git object has no header separator"))?; + let body = std::str::from_utf8(&canonical[separator + 1..])?; + let tree = body + .lines() + .find_map(|line| line.strip_prefix("tree ")) + .ok_or_else(|| test_error("commit has no tree"))?; + let tree_object = read_git_object(&root, tree)?; + if !tree_object.starts_with(b"tree ") { + return Err(test_error("commit tree points to a non-tree object")); + } + } + Ok(()) +} + +fn parse_toml(path: PathBuf) -> TestResult +where + T: for<'de> Deserialize<'de>, +{ + Ok(toml::from_str(&fs::read_to_string(path)?)?) +} + +fn copy_tree(source: &Path, destination: &Path) -> TestResult { + if !source.is_dir() { + return Err(test_error(format!( + "fixture source is not a directory: {}", + source.display() + ))); + } + for entry in fs::read_dir(source)? { + let entry = entry?; + let source_path = entry.path(); + let destination_path = destination.join(entry.file_name()); + let kind = entry.file_type()?; + if kind.is_dir() { + fs::create_dir_all(&destination_path)?; + copy_tree(&source_path, &destination_path)?; + } else if kind.is_file() { + fs::copy(source_path, destination_path)?; + } else { + return Err(test_error(format!( + "unsupported checked-in fixture type: {}", + source_path.display() + ))); + } + } + Ok(()) +} + +fn loose_object_paths(objects: &Path) -> TestResult> { + let mut paths = Vec::new(); + for directory in fs::read_dir(objects)? { + let directory = directory?; + if !directory.file_type()?.is_dir() { + continue; + } + let directory_name = directory.file_name(); + if directory_name.to_string_lossy().len() != 2 { + continue; + } + for object in fs::read_dir(directory.path())? { + let object = object?; + if object.file_type()?.is_file() { + paths.push(object.path()); + } + } + } + paths.sort(); + Ok(paths) +} + +fn read_git_object(repository: &Path, id: &str) -> TestResult> { + if id.len() != 40 || !id.bytes().all(|byte| byte.is_ascii_hexdigit()) { + return Err(test_error(format!("invalid Git object ID {id}"))); + } + inflate(&repository.join("objects").join(&id[..2]).join(&id[2..])) +} + +fn inflate(path: &Path) -> TestResult> { + let mut decoder = ZlibDecoder::new(fs::File::open(path)?); + let mut canonical = Vec::new(); + decoder.read_to_end(&mut canonical)?; + Ok(canonical) +} + +fn test_error(message: impl Into) -> Box { + Box::new(std::io::Error::other(message.into())) +} diff --git a/crates/storage/tests/support/mod.rs b/crates/storage/tests/support/mod.rs new file mode 100644 index 0000000..411519e --- /dev/null +++ b/crates/storage/tests/support/mod.rs @@ -0,0 +1 @@ +pub mod compatibility;