From f11e86509b6263d2d92c20f8aefdc2472a1285bf Mon Sep 17 00:00:00 2001 From: core Date: Thu, 27 Nov 2025 20:21:57 -0500 Subject: [PATCH] initial --- .gitignore | 1 + .idea/.gitignore | 8 + .idea/modules.xml | 8 + .idea/sage.iml | 11 + .idea/vcs.xml | 6 + Cargo.lock | 1502 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 23 + src/cmds/createid.rs | 50 ++ src/cmds/import.rs | 62 ++ src/cmds/ls.rs | 45 ++ src/cmds/mod.rs | 4 + src/cmds/show.rs | 32 + src/db.rs | 74 +++ src/identity.rs | 52 ++ src/main.rs | 161 +++++ src/wrapper.rs | 189 ++++++ 16 files changed, 2228 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/modules.xml create mode 100644 .idea/sage.iml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/cmds/createid.rs create mode 100644 src/cmds/import.rs create mode 100644 src/cmds/ls.rs create mode 100644 src/cmds/mod.rs create mode 100644 src/cmds/show.rs create mode 100644 src/db.rs create mode 100644 src/identity.rs create mode 100644 src/main.rs create mode 100644 src/wrapper.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ea8c4bf7f35f6f77f75d92ad8ce8349f6e81ddba --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..13566b81b018ad684f3a35fee301741b2734c8f4 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000000000000000000000000000000000..84b3d9f2b20aa6b39ef4ec6a8daf218dc2015c7a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/sage.iml b/.idea/sage.iml new file mode 100644 index 0000000000000000000000000000000000000000..cf84ae4a69877a117dad3f555c9d8ebf05a4fc20 --- /dev/null +++ b/.idea/sage.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..35eb1ddfbbc029bcab630581847471d7f238ec53 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000000000000000000000000000000000000..9912093edd540280bc6365f2fb78abfe0145bb3d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1502 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "age" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57fc171f4874fa10887e47088f81a55fcf030cd421aa31ec2b370cafebcc608a" +dependencies = [ + "age-core", + "base64", + "bech32", + "chacha20poly1305", + "cookie-factory", + "hmac", + "i18n-embed", + "i18n-embed-fl", + "lazy_static", + "nom", + "pin-project", + "rand", + "rust-embed", + "scrypt", + "sha2", + "subtle", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "age-core" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2bf6a89c984ca9d850913ece2da39e1d200563b0a94b002b253beee4c5acf99" +dependencies = [ + "base64", + "chacha20poly1305", + "cookie-factory", + "hkdf", + "io_tee", + "nom", + "rand", + "secrecy", + "sha2", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" +dependencies = [ + "futures", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[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", + "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", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml 0.5.11", +] + +[[package]] +name = "fluent" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb74634707bebd0ce645a981148e8fb8c7bccd4c33c652aeffd28bf2f96d555a" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash 1.1.0", + "self_cell 0.10.3", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eebbe59450baee8282d71676f3bfed5689aeab00b27545e83e5f14b1195e8b0" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" +dependencies = [ + "thiserror 1.0.69", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[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 = "i18n-config" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e06b90c8a0d252e203c94344b21e35a30f3a3a85dc7db5af8f8df9f3e0c63ef" +dependencies = [ + "basic-toml", + "log", + "serde", + "serde_derive", + "thiserror 1.0.69", + "unic-langid", +] + +[[package]] +name = "i18n-embed" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "669ffc2c93f97e6ddf06ddbe999fcd6782e3342978bb85f7d3c087c7978404c4" +dependencies = [ + "arc-swap", + "fluent", + "fluent-langneg", + "fluent-syntax", + "i18n-embed-impl", + "intl-memoizer", + "log", + "parking_lot", + "rust-embed", + "thiserror 1.0.69", + "unic-langid", + "walkdir", +] + +[[package]] +name = "i18n-embed-fl" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04b2969d0b3fc6143776c535184c19722032b43e6a642d710fa3f88faec53c2d" +dependencies = [ + "find-crate", + "fluent", + "fluent-syntax", + "i18n-config", + "i18n-embed", + "proc-macro-error2", + "proc-macro2", + "quote", + "strsim", + "syn", + "unic-langid", +] + +[[package]] +name = "i18n-embed-impl" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2cc0e0523d1fe6fc2c6f66e5038624ea8091b3e7748b5e8e0c84b1698db6c2" +dependencies = [ + "find-crate", + "i18n-config", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "intl-memoizer" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310da2e345f5eb861e7a07ee182262e94975051db9e4223e909ba90f392f163f" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "io_tee" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b3f7cef34251886990511df1c61443aa928499d598a9473929ab5a90a527304" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nucleo" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5262af4c94921c2646c5ac6ff7900c2af9cbb08dc26a797e18130a7019c039d4" +dependencies = [ + "nucleo-matcher", + "parking_lot", + "rayon", +] + +[[package]] +name = "nucleo-matcher" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf33f538733d1a5a3494b836ba913207f14d9d4a1d3cd67030c5061bdd2cac85" +dependencies = [ + "memchr", + "unicode-segmentation", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +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", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.17", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rust-embed" +version = "8.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "sage" +version = "0.1.0" +dependencies = [ + "age", + "anyhow", + "dirs", + "nucleo", + "pico-args", + "regex", + "serde", + "tempfile", + "toml 0.9.8", + "urlencoding", +] + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "self_cell" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" +dependencies = [ + "self_cell 1.2.1", +] + +[[package]] +name = "self_cell" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "serde_core", + "zerovec", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash 2.1.1", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unic-langid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658" +dependencies = [ + "serde", + "tinystr", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[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 = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[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 = "zerocopy" +version = "0.8.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "serde", + "zerofrom", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..5a17318ad23b5edf8aa96b2cc5c0fd6b2ed849a2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "sage" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde = { version = "1", features = ["derive"] } +toml = "0.9" + +age = "0.11" + +pico-args = { version = "0.5", features = ["combined-flags"] } + +anyhow = "1" + +dirs = "6" + +regex = "1.12" + +nucleo = "0.5" +urlencoding = "2" + +tempfile = "3" \ No newline at end of file diff --git a/src/cmds/createid.rs b/src/cmds/createid.rs new file mode 100644 index 0000000000000000000000000000000000000000..468236abeb240dd9a1670dc879cc0722bed862bd --- /dev/null +++ b/src/cmds/createid.rs @@ -0,0 +1,50 @@ +use std::path::Path; +use std::process::exit; +use age::secrecy::ExposeSecret; +use regex::Regex; +use crate::db::Database; +use crate::identity::{IdKeyData, Identity}; + +pub fn create_id(db_path: &Path, name: String, email: String) -> anyhow::Result<()> { + let mut db = Database::load_or_create(db_path)?; + + // sanity check to ensure this id does not already exist + for key in &db.keys { + if key.name == name && key.email == email { + eprintln!("key '{:?}' (keyid {}) already exists in database", key, key.keys.keyid()?); + eprintln!("if you want to recreate it, remove it first"); + exit(1); + } + } + + // Email is a fucking nightmare + // RFC5322, I hate you + let email_re = Regex::new(r#"(?x) + ^(?P[^@\s]+)@ + ([[:word:]]+\.)* + [[:word:]]+$ + "#).unwrap(); + + if !email_re.is_match(&email) { + eprintln!("`{email}` is not a valid RFC5322 email address"); + exit(1); + } + + let key = age::x25519::Identity::generate(); + let ss = key.to_string(); + let ss = ss.expose_secret(); + + let identity = Identity { + name, + email, + keys: IdKeyData::Local(ss.to_string()) + }; + + db.keys.push(identity.clone()); + // save database + db.write(db_path)?; + + println!("wrote new key {:?} (keyid {}) to database", identity, identity.keys.keyid()?); + + Ok(()) +} \ No newline at end of file diff --git a/src/cmds/import.rs b/src/cmds/import.rs new file mode 100644 index 0000000000000000000000000000000000000000..28381a0f78ad6a03f71858e2f0d93db23e480a64 --- /dev/null +++ b/src/cmds/import.rs @@ -0,0 +1,62 @@ +use std::convert::identity; +use std::path::Path; +use std::process::exit; +use crate::db::Database; +use crate::identity::{unescape, IdKeyData, Identity}; + +pub fn import_key(db_path: &Path, key: String, insert_anyway: bool) -> anyhow::Result<()> { + // decode the key + let components = key.split("/").collect::>(); + if components.len() != 4 || components[0] != "sage" { + eprintln!("not a sage key (looks like sage/key/name/email"); + eprintln!("to import a raw age key without an identity, add it to the database manually"); + exit(1); + } + + let [_sentinel, key, name, email] = components.as_slice() else { unreachable!() }; + + let name = unescape(name.to_string())?; + let email = unescape(email.to_string())?; + + let keys = match &key[0..3] { + "age" => { + IdKeyData::Peer(key.to_string()) + }, + "AGE" => { + IdKeyData::Local(key.to_string()) + }, + _ => { + eprintln!("unsupported age key type; is this a sage key?"); + exit(1); + } + }; + + let identity = Identity { + name, + email, + keys + }; + + let mut db = Database::load_or_create(db_path)?; + + for key in &db.keys { + if key.keys.keyid()? == identity.keys.keyid()? { + println!("key {} already exists in database, skipping", key.keys.keyid()?); + return Ok(()); + } + + if key.name == identity.name && key.email == identity.email && !insert_anyway { + eprintln!("database already has a key for {:?}", identity); + eprintln!("to import anyway, run again with --import-anyway"); + exit(1); + } + } + + db.keys.push(identity.clone()); + + db.write(db_path)?; + + println!("imported {:?} (keyid {}) successfully", identity, identity.keys.keyid()?); + + Ok(()) +} \ No newline at end of file diff --git a/src/cmds/ls.rs b/src/cmds/ls.rs new file mode 100644 index 0000000000000000000000000000000000000000..286455f6908b8d36aafb296ccb5c0424b224b67f --- /dev/null +++ b/src/cmds/ls.rs @@ -0,0 +1,45 @@ +use std::path::Path; +use std::process::exit; +use urlencoding::encode; +use crate::db::Database; +use crate::identity::{escape, IdKeyData}; + +pub fn list_keys(db_path: &Path, local_only: bool, peer_only: bool, full_pks: bool) -> anyhow::Result<()> { + if local_only && peer_only { + eprintln!("cannot show only local and only peer keys, pick one"); + exit(1); + } + let show_local = !peer_only; + let show_peer = !full_pks; + + let db = Database::load_or_create(db_path)?; + + let mut displayed = 0; + + for key in &db.keys { + match key.keys { + IdKeyData::Local(_) => { + if !show_local { continue }; + displayed += 1; + println!("local: {:?}", key); + } + IdKeyData::Peer(_) => { + if !show_peer { continue }; + displayed += 1; + println!("peer: {:?}", key); + } + } + + if full_pks { + println!(" pk: sage/{}/{}/{}", key.keys.pk()?, escape(key.name.clone()), escape(key.email.clone())); + } else { + println!(" id: {}", key.keys.keyid()?); + } + } + + if displayed == 0 { + println!("no keys found"); + } + + Ok(()) +} \ No newline at end of file diff --git a/src/cmds/mod.rs b/src/cmds/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..94142315069d58b5d663b17d5b6261329931299c --- /dev/null +++ b/src/cmds/mod.rs @@ -0,0 +1,4 @@ +pub mod createid; +pub mod ls; +pub mod show; +pub mod import; \ No newline at end of file diff --git a/src/cmds/show.rs b/src/cmds/show.rs new file mode 100644 index 0000000000000000000000000000000000000000..b8e1ad594c0e88534c348add92048157f9dad04e --- /dev/null +++ b/src/cmds/show.rs @@ -0,0 +1,32 @@ +use std::path::Path; +use std::process::exit; +use crate::db::Database; +use crate::identity::{escape, IdKeyData}; + +pub fn show_key(db_path: &Path, search: String, expose_secret: bool) -> anyhow::Result<()> { + let db = Database::load_or_create(db_path)?; + + let matches = db.fuzzy_search(search); + + if matches.len() > 1 { + println!("there were multiple best matches, showing all"); + } else if matches.is_empty() { + println!("no matches found"); + exit(0); + } + + for each_match in &matches { + println!("match: {:?}", each_match); + + let show_sk = expose_secret && matches!(each_match.keys, IdKeyData::Local(..)); + + if !show_sk { + println!(" pk: sage/{}/{}/{}", each_match.keys.pk()?, escape(each_match.name.clone()), escape(each_match.email.clone())); + } else { + let IdKeyData::Local(k) = &each_match.keys else { continue }; + println!(" sk: sage/{}/{}/{}", k, escape(each_match.name.clone()), escape(each_match.email.clone())); + } + } + + Ok(()) +} \ No newline at end of file diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000000000000000000000000000000000000..cf8201caadbb830388e28eab4f5e4878306cbf3c --- /dev/null +++ b/src/db.rs @@ -0,0 +1,74 @@ +use std::path::{Path, PathBuf}; +use dirs::config_dir; +use nucleo::{Config, Matcher, Utf32Str}; +use serde::{Deserialize, Serialize}; +use crate::identity::Identity; + +#[derive(Serialize, Deserialize)] +pub struct Database { + pub keys: Vec +} +impl Database { + pub fn load_or_create(path: &Path) -> anyhow::Result { + if !database_exists(path) { + let d = Database { + keys: vec![] + }; + write_database(d, path)?; + } + load_database(path) + } + pub fn write(self, path: &Path) -> anyhow::Result<()> { + write_database(self, path) + } + pub fn fuzzy_search(&self, term: String) -> Vec { + if self.keys.is_empty() { return vec![] } + let mut m = Matcher::new(Config::DEFAULT); + + let haystacks = self.keys.iter().map(|u| { + (u.clone(), format!("{:?} {}", u, u.keys.pk().unwrap_or("".to_string()))) + }).collect::>(); + + let mut scores = haystacks.iter().map(|h| { + ( + m.fuzzy_match(Utf32Str::Ascii(h.1.as_bytes()), Utf32Str::Ascii(term.as_bytes())).unwrap_or(0), + h.0.clone() + ) + }).collect::>(); + + scores.sort_by_key(|u| u.0); + scores.reverse(); + + let mut best_matches = vec![scores[0].clone()]; + for score in &scores[1..] { + if score.0 == best_matches[0].0 { + best_matches.push(score.clone()); + } + } + + best_matches.iter().map(|u| u.1.clone()).collect() + } +} + +fn database_exists(path: &Path) -> bool { + path.exists() +} +fn load_database(path: &Path) -> anyhow::Result { + let f = std::fs::read_to_string(path)?; + let data: Database = toml::from_str(&f)?; + Ok(data) +} +fn write_database(d: Database, path: &Path) -> anyhow::Result<()> { + let s = toml::to_string(&d)?; + let s_with_warning = format!( + "# This file is autogenerated; do not edit! sage v{}\n{}", + env!("CARGO_PKG_VERSION"), + s + ); + std::fs::write(path, s_with_warning)?; + Ok(()) +} + +pub fn db_default() -> PathBuf { + config_dir().unwrap().join("sage.toml") +} \ No newline at end of file diff --git a/src/identity.rs b/src/identity.rs new file mode 100644 index 0000000000000000000000000000000000000000..ee63d40c10bc7b7c55761cb2e4aaa9e71936b1bb --- /dev/null +++ b/src/identity.rs @@ -0,0 +1,52 @@ +use std::fmt::{Debug, Formatter}; +use std::str::FromStr; +use anyhow::bail; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone)] +pub struct Identity { + pub name: String, + pub email: String, + pub keys: IdKeyData +} +#[derive(Serialize, Deserialize, Clone)] +pub enum IdKeyData { + Local(String), + Peer(String) +} + +impl Debug for Identity { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{} <{}>", self.name, self.email) + } +} +impl IdKeyData { + pub fn pk(&self) -> anyhow::Result { + match self { + IdKeyData::Local(id) => { + let k = match age::x25519::Identity::from_str(id) { + Ok(k) => k, + Err(e) => { + bail!(e); + } + }; + Ok(k.to_public().to_string()) + }, + IdKeyData::Peer(pk) => { + Ok(pk.clone()) + } + } + } + pub fn keyid(&self) -> anyhow::Result { + let pk = self.pk()?; + let s0 = &pk[4..12]; + Ok(format!("{s0}")) + } +} + +pub fn escape(i: String) -> String { + urlencoding::encode(&i).to_string() +} +pub fn unescape(i: String) -> anyhow::Result { + Ok(urlencoding::decode(&i)?.to_string()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..c1c7cbc5188895ac5ec6f0e941de871bd41bf6e7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,161 @@ +use std::path::PathBuf; +use std::process::exit; +use dirs::config_dir; +use crate::cmds::createid::create_id; +use crate::db::db_default; +use crate::wrapper::wrapper; + +mod identity; +mod db; +mod cmds; +mod wrapper; + +fn main() -> anyhow::Result<()> { + let mut pargs = pico_args::Arguments::from_env(); + + if pargs.contains(["-h", "--help"]) { + print_help(); + exit(0); + } + + let database = pargs.opt_value_from_str(["-D", "--database"])?.unwrap_or(db_default()); + + let Some(subcommand) = pargs.subcommand()? else { + // run the wrapper + return wrapper(pargs, &database); + }; + + match subcommand.as_str() { + "create-id" => { + let Ok(name) = pargs.free_from_str() else { + eprintln!("name is required, --help for help"); + exit(1); + }; + let Ok(email) = pargs.free_from_str() else { + eprintln!("email is required, --help for help"); + exit(1); + }; + + create_id(&database, name, email)?; + }, + "ls" => { + cmds::ls::list_keys( + &database, + pargs.contains(["-l", "--local"]), + pargs.contains(["-p", "--peer"]), + pargs.contains(["-f", "--full"]), + )?; + }, + "show" => { + let Ok(search) = pargs.free_from_str() else { + eprintln!("search term is required, --help for help"); + exit(1); + }; + cmds::show::show_key( + &database, + search, + pargs.contains(["-e", "--expose-secret"]) + )?; + }, + "import" => { + let Ok(key) = pargs.free_from_str() else { + eprintln!("key is required, --help for help"); + exit(1); + }; + cmds::import::import_key( + &database, + key, + pargs.contains(["-i", "--import-anyway"]) + )?; + }, + unknown => { + eprintln!("Unknown subcommand: {}", unknown); + print_help(); + exit(1); + } + } + + Ok(()) +} + +fn print_help() { + let cf = db_default().display().to_string(); + println!("{}", HELP.to_string().replace("{CONFIGFILE}", cf.as_str())); +} +const HELP: &str = "\ +sage - simple age wrapper for named identities + +USAGE: + sage [OPTIONS] [SUBCOMMAND] + sage [--encrypt] (-r RECIPIENT | -R PATH)... [--armor] [-o OUTPUT] [INPUT] + sage [--encrypt] --passphrase [--armor] [-o OUTPUT] [INPUT] + sage --decrypt [-i PATH]... [-o OUTPUT] [INPUT] + +OPTIONS: + -h, --help Prints help information + -D, --database Set the database file (default {CONFIGFILE}) + + -v, --verbose Enable debug logging + -e, --encrypt Encrypt the input to the output. Default if omitted. + -d, --decrypt Decrypt the input to the output. + -o, --output OUTPUT Write the result to the file at path OUTPUT. + -a, --armor Encrypt to a PEM encoded format. + -p, --passphrase Encrypt with a passphrase. + -r, --recipient RECIPIENT Encrypt to the specified RECIPIENT. Can be repeated. + -R, --recipients-file PATH Encrypt to the recipients listed at PATH. Can be repeated. + -i, --identity IDENTITY Use the specified identity. Can be repeated. + +INPUT defaults to standard input, and OUTPUT defaults to standard output. +If OUTPUT exists, it will be overwritten. + +RECIPIENT can be an age public key generate by age-keygen (\"age1...\") +an SSH public key (\"ssh-ed25519 AAAA...\", \"ssh-rsa AAAA...\"), OR +a fuzzy search term to use the database to locate a key. + +Recipient files contain one or more recipients, one per line. Empty lines +and lines starting with \"#\" are ignored as comments. \"-\" may be uised to +read recipients from standard input. + +An IDENTITY may either be a fuzzy search term to search the database, or +an identity file. + +Identity files contain one or more secret keys (\"AGE-SECRET-KEY-1...\"), +one per line, or an SSH key. Empty lines and lines starting with \"#\" are +ignored as comments. Passphrase encrypted age files can be used as +identity files. Multiple key files can be provided, and any unused ones +will be ignored. \"-\" may be used to read identities from standard input. + +When --encrypt is specified explicitly, -i can also be used to encrypt to an +identity file symmetrically, instead or in addition to normal recipients. + +If an identity is omitted and only one local identity exists in the database, +it will be chosen automatically. + +SUBCOMMANDS: + +create-id + Create a new local identity in the database. + Name for the new identity + Email of the new identity + +ls [OPTIONS] + List keys in the database + OPTIONS: + + -l, --local Show only local (your) keys + -p, --peer Show only peer (other's) keys + -f, --full List full public keys instead of key IDs + +show [OPTIONS] + Search the database (fuzzy) and show a specific key + + OPTIONS: + -e, --expose-secret Output the secret key, if it is a local key + +import [OPTIONS] + Imports the provided key into the database. + If the keyid already exists, it will do nothing. + + OPTIONS: + -i, --insert-anyway Insert even if a matching identity is already present +"; \ No newline at end of file diff --git a/src/wrapper.rs b/src/wrapper.rs new file mode 100644 index 0000000000000000000000000000000000000000..9aa400098da2fcec447eff56ace2f10e36a9827a --- /dev/null +++ b/src/wrapper.rs @@ -0,0 +1,189 @@ +use std::os::unix::prelude::CommandExt; +use std::path::{Path, PathBuf}; +use std::process::{exit, Command, Stdio}; +use std::str::FromStr; +use std::thread::sleep; +use std::time::Duration; +use anyhow::bail; +use pico_args::Arguments; +use tempfile::NamedTempFile; +use crate::db::Database; +use crate::identity::IdKeyData; + +#[derive(Debug)] +enum Mode { + Encrypt, + Decrypt +} + +pub fn wrapper(mut pargs: Arguments, db_path: &Path) -> anyhow::Result<()> { + let verbose = pargs.contains(["-v", "--verbose"]); + let contains_encrypt = pargs.contains(["-e", "--encrypt"]); + let contains_decrypt = pargs.contains(["-d", "--decrypt"]); + + if contains_encrypt && contains_decrypt { + bail!("cannot specify both --encrypt and --decrypt, pick one"); + } + + let mode = if contains_decrypt { Mode::Decrypt } else { Mode::Encrypt }; + let output: Option = pargs.opt_value_from_str(["-o", "--output"])?; + let armor = pargs.contains(["-a", "--armor"]); + let passphrase = pargs.contains(["-p", "--passphrase"]); + + let mut recipients = vec![]; + loop { + let maybe_r: Option = pargs.opt_value_from_str(["-r", "--recipient"])?; + + if let Some(r) = maybe_r { + recipients.push(r); + } else { + break + } + } + + if let Ok(Some(f)) = pargs.opt_value_from_str::<[&str; 2], PathBuf>(["-R", "--recipients-file"]) { + recipients.extend(load_keyfile(&f)?); + } + + let mut identities = vec![]; + loop { + let maybe_i: Option = pargs.opt_value_from_str(["-i", "--identity"])?; + + if let Some(i) = maybe_i { + if let Ok(path) = PathBuf::from_str(&i) && path.exists() { + identities.extend(load_keyfile(&path)?); + } else { + identities.push(i); + } + } else { + break + } + } + + let input: Option = pargs.opt_free_from_str()?; + + if verbose { + println!("resolved args: {:?} output={:?} armor?{:?} passphrase?{:?} recipients=>{:?} identities=>{:?} input={:?}", mode, output, armor, passphrase, recipients, identities, input); + } + + // Resolve recipients and identities to the database + let db = Database::load_or_create(db_path)?; + + // resolve recipients + let resolved_recipients = recipients.iter() + .map(|u| { + // is this already a valid key? + if u.starts_with("age1") { + // regular ol' pubkey + return u.clone() + } + // otherwise, resolve it in the database + let results = db.fuzzy_search(u.clone()); + if results.is_empty() { + eprintln!("no results for recipient {u}"); + exit(1); + } + if results.len() > 1 { + eprintln!("recipient {u} is ambiguous, please narrow it down"); + exit(1); + } + let r = &results[0]; + + if verbose { + eprintln!("resolved rcpt {u} => {:?} (keyid {})", r, r.keys.keyid().unwrap()); + } + + r.keys.pk().unwrap() + }) + .collect::>(); + // resolve identities + let resolved_identities = identities.iter() + .map(|u| { + // is this already a valid key? + if u.starts_with("AGE-SECRET-KEY") { + // regular ol' sk + return u.clone() + } + // otherwise, resolve it in the database + let results = db.fuzzy_search(u.clone()); + if results.is_empty() { + eprintln!("no results for identity {u}"); + exit(1); + } + if results.len() > 1 { + eprintln!("identity {u} is ambiguous, please narrow it down"); + exit(1); + } + let r = &results[0]; + + if verbose { + eprintln!("resolved id {u} => {:?} (keyid {})", r, r.keys.keyid().unwrap()); + } + + match &r.keys { + IdKeyData::Local(sk) => sk.clone(), + IdKeyData::Peer(_) => { + eprintln!("resolved identity is a peer key, not a local key. perhaps revise your search terms?"); + exit(1); + } + } + }) + .collect::>(); + + if verbose { + eprintln!("resolved recipients: {:#?}", resolved_recipients); + } + + // write recipients to a temp file + let r_tmpfile = NamedTempFile::new()?; + std::fs::write(r_tmpfile.path(), resolved_recipients.join("\n"))?; + let i_tmpfile = NamedTempFile::new()?; + std::fs::write(i_tmpfile.path(), resolved_identities.join("\n"))?; + + // execute age + let mut args = vec![]; + args.push(match mode { + Mode::Encrypt => "--encrypt", + Mode::Decrypt => "--decrypt" + }.to_string()); + if let Some(o) = output { + args.push("--output".to_string()); + args.push(o.display().to_string()); + } + if armor { + args.push("--armor".to_string()); + } + if passphrase { + args.push("--passphrase".to_string()); + } + if !resolved_recipients.is_empty() { + args.push("--recipients-file".to_string()); + args.push(r_tmpfile.path().display().to_string()); + } + if !resolved_identities.is_empty() { + args.push("--identity".to_string()); + args.push(i_tmpfile.path().display().to_string()); + } + if let Some(i) = input { + args.push(i.display().to_string()); + } + + if verbose { + eprintln!("exec age {:?}", args); + } + + Err(Command::new("age") + .args(args) + .exec())?; + Ok(()) +} + +fn load_keyfile(path: &Path) -> anyhow::Result> { + let contents = std::fs::read_to_string(path)?; + Ok(contents + .lines() + .filter(|u| !u.is_empty()) + .filter(|u| !u.starts_with("#")) + .map(|u| u.to_string()) + .collect()) +} \ No newline at end of file